stoolap 0.4.0

High-performance embedded SQL database with MVCC, time-travel queries, and full ACID compliance
Documentation
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
---
layout: doc
title: API Reference
category: Getting Started
order: 4
---

# Rust API Reference

Complete reference for the Stoolap Rust API (`stoolap` crate).

```toml
[dependencies]
stoolap = "0.4"
```

## Database

The `Database` struct is the main entry point. It is thread-safe and can be shared across threads via cloning.

### Opening a Database

```rust
use stoolap::Database;

// In-memory (unique instance per call)
let db = Database::open_in_memory()?;

// In-memory (shared instance, same DSN returns same engine)
let db = Database::open("memory://")?;

// File-based (persistent)
let db = Database::open("file:///path/to/database")?;

// File-based with configuration
let db = Database::open("file:///path/to/db?sync=full&checkpoint_interval=60")?;
```

`open_in_memory()` creates a unique, isolated instance each time. `open("memory://")` returns the same shared engine for the same DSN.

### Connection String Options

| Parameter | Default | Description |
|-----------|---------|-------------|
| `sync` / `sync_mode` | `normal` | Sync mode: `none`, `normal`, `full` (or `0`, `1`, `2`) |
| `checkpoint_interval` | `60` | Seconds between automatic checkpoint cycles |
| `compact_threshold` | `4` | Sub-target volumes per table before merging |
| `keep_snapshots` | `3` | Backup snapshots to retain per table |
| `wal_flush_trigger` | `32768` | WAL flush trigger size in bytes |
| `wal_buffer_size` | `65536` | WAL buffer size in bytes |
| `wal_max_size` | `67108864` | Max WAL file size before rotation (64 MB) |
| `commit_batch_size` | `100` | Commits to batch before syncing (normal mode) |
| `sync_interval_ms` | `1000` | Minimum ms between syncs (normal mode) |
| `wal_compression` | `on` | LZ4 compression for WAL entries |
| `compression` | -- | Alias that sets both `wal_compression` and `volume_compression` |
| `compression_threshold` | `64` | Minimum bytes before compressing an entry |
| `volume_compression` | `on` | LZ4 compression for cold volume files |
| `checkpoint_on_close` | `on` | Seal all hot rows to volumes on clean shutdown |
| `target_volume_rows` | `1048576` | Target rows per cold volume. Controls compaction split boundary. |

### execute()

Execute DDL or DML statements. Returns the number of rows affected.

```rust
use stoolap::params;

// DDL, no parameters
db.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)", ())?;

// DML with tuple parameters
db.execute("INSERT INTO users VALUES ($1, $2, $3)", (1, "Alice", 30))?;

// DML with params! macro
db.execute("INSERT INTO users VALUES ($1, $2, $3)", params![2, "Bob", 25])?;

// Returns rows affected
let affected = db.execute("UPDATE users SET name = $1 WHERE id = $2", ("Charlie", 1))?;
```

### query()

Execute a SELECT query and iterate over results.

```rust
// Iterate rows
for row in db.query("SELECT * FROM users", ())? {
    let row = row?;
    let id: i64 = row.get(0)?;
    let name: String = row.get_by_name("name")?;
}

// With parameters
for row in db.query("SELECT * FROM users WHERE age > $1", (18,))? {
    let row = row?;
    // ...
}

// Collect into Vec
let users: Vec<_> = db.query("SELECT * FROM users", ())?
    .collect::<Result<Vec<_>, _>>()?;
```

### query_one()

Return a single value. Errors if no rows are returned.

```rust
let count: i64 = db.query_one("SELECT COUNT(*) FROM users", ())?;
let name: String = db.query_one("SELECT name FROM users WHERE id = $1", (1,))?;
```

### query_opt()

Return an optional single value. Returns `None` if no rows match.

```rust
let name: Option<String> = db.query_opt("SELECT name FROM users WHERE id = $1", (999,))?;
match name {
    Some(n) => println!("Found: {}", n),
    None => println!("Not found"),
}
```

### Named Parameters

Use `:name` syntax with `named_params!` macro or the `NamedParams` builder.

```rust
use stoolap::named_params;

// Execute with named params
db.execute_named(
    "INSERT INTO users VALUES (:id, :name, :age)",
    named_params!{ id: 1, name: "Alice", age: 30 }
)?;

// Query with named params
for row in db.query_named(
    "SELECT * FROM users WHERE age > :min_age",
    named_params!{ min_age: 25 }
)? {
    // ...
}

// Query single value with named params
let count: i64 = db.query_one_named(
    "SELECT COUNT(*) FROM users WHERE age > :min_age",
    named_params!{ min_age: 18 }
)?;

// Builder API (alternative to macro)
use stoolap::NamedParams;
let params = NamedParams::new()
    .add("id", 1)
    .add("name", "Alice")
    .add("age", 30);
db.execute_named("INSERT INTO users VALUES (:id, :name, :age)", params)?;
```

### Timeout Methods

Cancel queries that exceed a time limit. Timeout is in milliseconds; use 0 for no timeout.

```rust
// Execute with 5 second timeout
db.execute_with_timeout("DELETE FROM large_table WHERE old = true", (), 5000)?;

// Query with 10 second timeout
for row in db.query_with_timeout("SELECT * FROM large_table", (), 10000)? {
    let row = row?;
    // ...
}
```

### Struct Mapping with FromRow

Map query results directly to structs.

```rust
use stoolap::{Database, FromRow, ResultRow, Result};

struct User {
    id: i64,
    name: String,
    email: Option<String>,
}

impl FromRow for User {
    fn from_row(row: &ResultRow) -> Result<Self> {
        Ok(User {
            id: row.get(0)?,
            name: row.get(1)?,
            email: row.get(2)?,
        })
    }
}

// Query and map to structs
let users: Vec<User> = db.query_as("SELECT id, name, email FROM users", ())?;

// With named params
let users: Vec<User> = db.query_as_named(
    "SELECT id, name, email FROM users WHERE id = :id",
    named_params!{ id: 1 }
)?;
```

### Cached Plans

Parse SQL once and execute many times with zero cache-lookup overhead.

```rust
// Create a cached plan (parse once)
let insert_plan = db.cached_plan("INSERT INTO users VALUES ($1, $2, $3)")?;

// Execute many times (no parsing, no cache lookup)
db.execute_plan(&insert_plan, (1, "Alice", 30))?;
db.execute_plan(&insert_plan, (2, "Bob", 25))?;

// Query with cached plan
let query_plan = db.cached_plan("SELECT * FROM users WHERE id = $1")?;
let rows = db.query_plan(&query_plan, (1,))?;

// Named params with cached plan
let plan = db.cached_plan("INSERT INTO users VALUES (:id, :name, :age)")?;
db.execute_named_plan(&plan, named_params!{ id: 3, name: "Charlie", age: 35 })?;
let rows = db.query_named_plan(
    &db.cached_plan("SELECT * FROM users WHERE id = :id")?,
    named_params!{ id: 3 }
)?;
```

### Utility Methods

```rust
// Close the database (releases file lock immediately)
db.close()?;

// Check if a table exists
if db.table_exists("users")? {
    // ...
}

// Get the DSN this database was opened with
let dsn = db.dsn();  // "memory://" or "file:///path"

// Set default isolation level for new transactions
use stoolap::IsolationLevel;
db.set_default_isolation_level(IsolationLevel::SnapshotIsolation)?;

// Create a point-in-time snapshot (for file-based databases)
db.create_snapshot()?;

// Semantic cache stats
let stats = db.semantic_cache_stats()?;
println!("Hits: {}, Subsumption: {}", stats.hits, stats.subsumption_hits);

// Clear the semantic cache
db.clear_semantic_cache()?;
```

## Prepared Statements

Prepare a statement for repeated execution with different parameters. The SQL is validated at prepare time.

```rust
// Prepare once
let stmt = db.prepare("SELECT * FROM users WHERE id = $1")?;

// Execute multiple times
for id in 1..=10 {
    for row in stmt.query((id,))? {
        let row = row?;
        // ...
    }
}

// Single value
let name: String = stmt.query_one((1,))?;

// Optional value
let name: Option<String> = stmt.query_opt((999,))?;

// DML prepared statement
let insert = db.prepare("INSERT INTO users VALUES ($1, $2, $3)")?;
insert.execute((1, "Alice", 30))?;
insert.execute((2, "Bob", 25))?;

// Get the SQL text
assert_eq!(insert.sql(), "INSERT INTO users VALUES ($1, $2, $3)");
```

Statement holds a weak reference to the Database. It becomes invalid after the Database is dropped.

## Transactions

### Programmatic API

```rust
// Begin with default isolation (ReadCommitted)
let mut tx = db.begin()?;

// Begin with specific isolation level
use stoolap::IsolationLevel;
let mut tx = db.begin_with_isolation(IsolationLevel::SnapshotIsolation)?;

// Execute within transaction
tx.execute("INSERT INTO users VALUES ($1, $2, $3)", (1, "Alice", 30))?;
tx.execute("UPDATE accounts SET balance = balance - $1 WHERE id = $2", (100, 1))?;

// Query within transaction (sees uncommitted changes)
for row in tx.query("SELECT * FROM users", ())? {
    let row = row?;
    // ...
}

// Single value query
let count: i64 = tx.query_one("SELECT COUNT(*) FROM users", ())?;

// Optional value query
let name: Option<String> = tx.query_opt("SELECT name FROM users WHERE id = $1", (999,))?;

// Named parameters
tx.execute_named(
    "INSERT INTO users VALUES (:id, :name, :age)",
    named_params!{ id: 3, name: "Charlie", age: 35 }
)?;

for row in tx.query_named(
    "SELECT * FROM users WHERE age > :min_age",
    named_params!{ min_age: 25 }
)? {
    let row = row?;
    // ...
}

// Commit or rollback
tx.commit()?;
// Or: tx.rollback()?;
```

Transactions auto-rollback on drop if not committed.

### Pre-parsed Statements in Transactions

For batch operations, parse SQL once and execute many times within a transaction.

```rust
// Prepare once (via Database)
let insert = db.prepare("INSERT INTO users VALUES ($1, $2, $3)")?;

let mut tx = db.begin()?;
for (id, name, age) in data {
    tx.execute_prepared(&insert, (id, name, age))?;
}
tx.commit()?;
```

Query with a prepared statement inside a transaction:

```rust
let lookup = db.prepare("SELECT * FROM users WHERE id = $1")?;

let mut tx = db.begin()?;
// Reads within the transaction see uncommitted changes
let rows = tx.query_prepared(&lookup, (42,))?;
tx.commit()?;
```

### SQL-based Transactions

```rust
db.execute("BEGIN", ())?;
db.execute("INSERT INTO users VALUES ($1, $2)", (1, "Alice"))?;
db.execute("COMMIT", ())?;
// Or: db.execute("ROLLBACK", ())?;
```

### Savepoints

```rust
db.execute("BEGIN", ())?;
db.execute("INSERT INTO users VALUES ($1, $2)", (1, "Alice"))?;
db.execute("SAVEPOINT sp1", ())?;
db.execute("INSERT INTO users VALUES ($1, $2)", (2, "Bob"))?;
db.execute("ROLLBACK TO SAVEPOINT sp1", ())?;  // Undo Bob
db.execute("RELEASE SAVEPOINT sp1", ())?;       // Release savepoint
db.execute("COMMIT", ())?;  // Only Alice is inserted
```

### Isolation Levels

```rust
// Programmatic
let mut tx = db.begin_with_isolation(IsolationLevel::SnapshotIsolation)?;

// SQL-based
db.execute("BEGIN TRANSACTION ISOLATION LEVEL SNAPSHOT", ())?;

// Set default for all new transactions
db.set_default_isolation_level(IsolationLevel::SnapshotIsolation)?;
```

The Rust API has two enum variants: `ReadCommitted` (default) and `SnapshotIsolation`. At the SQL level, `SNAPSHOT`, `SERIALIZABLE`, `REPEATABLE READ` all map to `SnapshotIsolation`, and `READ UNCOMMITTED` maps to `ReadCommitted`.

## Working with Rows

### Rows Iterator

`query()` returns a `Rows` struct that implements `Iterator<Item = Result<ResultRow>>`.

```rust
let mut rows = db.query("SELECT * FROM users", ())?;

// Column metadata
let columns: &[String] = rows.columns();
let count: usize = rows.column_count();

// Iterate
for row in rows {
    let row = row?;
    // ...
}

// Or collect
let all: Vec<ResultRow> = db.query("SELECT * FROM users", ())?.collect_vec()?;
```

### Zero-Clone Cursor

For bulk serialization, `advance()` / `current_row()` avoids per-row cloning.

```rust
let mut rows = db.query("SELECT * FROM users", ())?;
while rows.advance() {
    let row: &Row = rows.current_row();
    // Access row by reference, no clone
    if let Some(value) = row.get(0) {
        // ...
    }
}
```

### ResultRow Accessors

```rust
let row = db.query("SELECT id, name, active FROM users", ())?
    .next().unwrap()?;

// By index (0-based)
let id: i64 = row.get(0)?;

// By column name (case-insensitive)
let name: String = row.get_by_name("name")?;

// Optional values (for nullable columns)
let active: Option<bool> = row.get_by_name("active")?;

// Raw Value access
let value: Option<&Value> = row.get_value(0);

// NULL check
if row.is_null(2) {
    println!("active is NULL");
}

// Metadata
let columns: &[String] = row.columns();
let len: usize = row.len();
let empty: bool = row.is_empty();

// Get underlying Row
let inner: &Row = row.as_row();
let owned: Row = row.into_inner();
```

### Type Conversions (FromValue, reading)

Types you can use with `row.get::<T>()`:

| SQL Type | Rust Type |
|----------|-----------|
| INTEGER | `i64`, `i32`, `f64`, `bool`, `String`, `Value` |
| FLOAT | `f64`, `i64`, `i32`, `String`, `Value` |
| TEXT | `String`, `Value` |
| BOOLEAN | `bool`, `Value` |
| TIMESTAMP | `String` (ISO format), `Value` |
| JSON | `String`, `Value` |
| NULL | `Option<T>` for any supported `T` |

### Parameter Types (ToParam, writing)

Types you can pass as SQL parameters:

| Rust Type | SQL Value |
|-----------|-----------|
| `i8`, `i16`, `i32`, `i64` | INTEGER |
| `u8`, `u16`, `u32`, `usize` | INTEGER |
| `f32`, `f64` | FLOAT |
| `bool` | BOOLEAN |
| `&str`, `String`, `Arc<str>` | TEXT |
| `DateTime<Utc>` | TIMESTAMP |
| `Value` | as-is |
| `Option<T>` | T or NULL |

Tuple parameters support up to 12 elements. For more, use `params![]` macro or `Vec<Value>`.

## Error Handling

```rust
use stoolap::{Database, Error};

match db.execute("INSERT INTO users VALUES (1, 'Alice')", ()) {
    Ok(affected) => println!("Inserted {} rows", affected),
    Err(e) => {
        let msg = e.to_string();
        if msg.contains("UNIQUE constraint") {
            println!("Duplicate key");
        } else if msg.contains("write-write conflict") {
            println!("Transaction conflict, retry");
        } else {
            return Err(e.into());
        }
    }
}
```

## Thread Safety

`Database` is `Send + Sync`. Clone to share across threads. Each clone has its own executor with independent transaction state but shares the same storage engine.

```rust
use std::sync::Arc;
use std::thread;

let db = Arc::new(Database::open("memory://")?);
db.execute("CREATE TABLE counter (id INTEGER PRIMARY KEY, value INTEGER)", ())?;
db.execute("INSERT INTO counter VALUES (1, 0)", ())?;

let handles: Vec<_> = (0..4).map(|_| {
    let db = Arc::clone(&db);
    thread::spawn(move || {
        for _ in 0..100 {
            db.execute("UPDATE counter SET value = value + 1 WHERE id = 1", ()).unwrap();
        }
    })
}).collect();

for h in handles {
    h.join().unwrap();
}

let count: i64 = db.query_one("SELECT value FROM counter WHERE id = 1", ())?;
```

## Complete Example

```rust
use stoolap::{Database, FromRow, ResultRow, Result, named_params, params};

struct User {
    id: i64,
    name: String,
    email: Option<String>,
}

impl FromRow for User {
    fn from_row(row: &ResultRow) -> Result<Self> {
        Ok(User {
            id: row.get(0)?,
            name: row.get(1)?,
            email: row.get(2)?,
        })
    }
}

fn main() -> Result<()> {
    let db = Database::open("file:///tmp/myapp.db")?;

    // Schema
    db.execute("
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTO_INCREMENT,
            name TEXT NOT NULL,
            email TEXT,
            created_at TIMESTAMP DEFAULT NOW()
        )
    ", ())?;

    db.execute("CREATE INDEX IF NOT EXISTS idx_email ON users(email)", ())?;

    // Batch insert with cached plan
    let plan = db.cached_plan("INSERT INTO users (name, email) VALUES ($1, $2)")?;
    db.execute_plan(&plan, ("Alice", "alice@example.com"))?;
    db.execute_plan(&plan, ("Bob", "bob@example.com"))?;

    // Query with struct mapping
    let users: Vec<User> = db.query_as("SELECT id, name, email FROM users", ())?;
    for user in &users {
        println!("{}: {} ({:?})", user.id, user.name, user.email);
    }

    // Aggregation
    let count: i64 = db.query_one("SELECT COUNT(*) FROM users", ())?;
    println!("Total users: {}", count);

    // Window function
    for row in db.query("
        SELECT name, ROW_NUMBER() OVER (ORDER BY created_at) as row_num
        FROM users
    ", ())? {
        let row = row?;
        let name: String = row.get_by_name("name")?;
        let num: i64 = row.get_by_name("row_num")?;
        println!("{}: {}", num, name);
    }

    db.close()?;
    Ok(())
}
```

## Method Reference

### Database

| Method | Returns | Description |
|--------|---------|-------------|
| `open(dsn)` | `Result<Database>` | Open or reuse a database by DSN |
| `open_in_memory()` | `Result<Database>` | Open a unique in-memory database |
| `execute(sql, params)` | `Result<i64>` | Execute DDL/DML, return rows affected |
| `query(sql, params)` | `Result<Rows>` | Execute SELECT, return row iterator |
| `query_one(sql, params)` | `Result<T>` | Query single value |
| `query_opt(sql, params)` | `Result<Option<T>>` | Query optional value |
| `execute_named(sql, params)` | `Result<i64>` | Execute with named params |
| `query_named(sql, params)` | `Result<Rows>` | Query with named params |
| `query_one_named(sql, params)` | `Result<T>` | Single value with named params |
| `query_as(sql, params)` | `Result<Vec<T>>` | Query and map to structs |
| `query_as_named(sql, params)` | `Result<Vec<T>>` | Map to structs with named params |
| `execute_with_timeout(sql, params, ms)` | `Result<i64>` | Execute with timeout |
| `query_with_timeout(sql, params, ms)` | `Result<Rows>` | Query with timeout |
| `cached_plan(sql)` | `Result<CachedPlanRef>` | Create a cached execution plan |
| `execute_plan(plan, params)` | `Result<i64>` | Execute cached plan |
| `query_plan(plan, params)` | `Result<Rows>` | Query with cached plan |
| `execute_named_plan(plan, params)` | `Result<i64>` | Execute cached plan with named params |
| `query_named_plan(plan, params)` | `Result<Rows>` | Query cached plan with named params |
| `prepare(sql)` | `Result<Statement>` | Create a prepared statement |
| `begin()` | `Result<Transaction>` | Begin transaction (ReadCommitted) |
| `begin_with_isolation(level)` | `Result<Transaction>` | Begin with isolation level |
| `close()` | `Result<()>` | Close database, release file lock |
| `table_exists(name)` | `Result<bool>` | Check if table exists |
| `dsn()` | `&str` | Get the DSN |
| `set_default_isolation_level(level)` | `Result<()>` | Set default isolation |
| `create_snapshot()` | `Result<()>` | Create point-in-time snapshot |
| `semantic_cache_stats()` | `Result<Stats>` | Get cache statistics |
| `clear_semantic_cache()` | `Result<()>` | Clear query cache |

### Statement

| Method | Returns | Description |
|--------|---------|-------------|
| `execute(params)` | `Result<i64>` | Execute DML |
| `query(params)` | `Result<Rows>` | Query rows |
| `query_one(params)` | `Result<T>` | Single value |
| `query_opt(params)` | `Result<Option<T>>` | Optional value |
| `sql()` | `&str` | Get the SQL text |

### Transaction

| Method | Returns | Description |
|--------|---------|-------------|
| `execute(sql, params)` | `Result<i64>` | Execute DML |
| `query(sql, params)` | `Result<Rows>` | Query rows |
| `query_one(sql, params)` | `Result<T>` | Single value |
| `query_opt(sql, params)` | `Result<Option<T>>` | Optional value |
| `execute_named(sql, params)` | `Result<i64>` | Execute with named params |
| `query_named(sql, params)` | `Result<Rows>` | Query with named params |
| `execute_prepared(stmt, params)` | `Result<i64>` | Execute pre-parsed statement |
| `query_prepared(stmt, params)` | `Result<Rows>` | Query with pre-parsed statement |
| `commit()` | `Result<()>` | Commit |
| `rollback()` | `Result<()>` | Rollback |
| `id()` | `i64` | Transaction ID |

### Rows

| Method | Returns | Description |
|--------|---------|-------------|
| `next()` | `Option<Result<ResultRow>>` | Iterator next |
| `advance()` | `bool` | Zero-clone cursor advance |
| `current_row()` | `&Row` | Current row reference |
| `columns()` | `&[String]` | Column names |
| `column_count()` | `usize` | Number of columns |
| `rows_affected()` | `i64` | DML rows affected |
| `collect_vec()` | `Result<Vec<ResultRow>>` | Collect all rows |
| `close()` | `()` | Close result set |

### ResultRow

| Method | Returns | Description |
|--------|---------|-------------|
| `get(index)` | `Result<T>` | Value by column index |
| `get_by_name(name)` | `Result<T>` | Value by column name |
| `get_value(index)` | `Option<&Value>` | Raw Value reference |
| `is_null(index)` | `bool` | Check if NULL |
| `columns()` | `&[String]` | Column names |
| `len()` | `usize` | Number of columns |
| `is_empty()` | `bool` | Empty check |
| `as_row()` | `&Row` | Reference to inner Row |
| `into_inner()` | `Row` | Consume into Row |