apexbase 1.14.0

High-performance HTAP embedded database with Rust core
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
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
# ApexBase Embedded Rust API

Use ApexBase as a high-performance embedded database directly from Rust — no Python, no FFI overhead.

## Table of Contents

- [Installation]#installation
- [Quick Start]#quick-start
- [Core Types]#core-types
  - [Value]#value
  - [ColumnType]#columntype
  - [DataType]#datatype
- [Database Operations — ApexDB]#database-operations--apexdb
- [Table Operations — Table]#table-operations--table
  - [Write Operations]#write-operations
  - [Read Operations]#read-operations
  - [SQL Execution]#sql-execution
  - [Schema Management]#schema-management
  - [Maintenance]#maintenance
- [ResultSet]#resultset
- [Multi-Database Support]#multi-database-support
- [Durability Levels]#durability-levels
- [Transactions via SQL]#transactions-via-sql
- [Indexes]#indexes
- [Full-Text Search]#full-text-search
- [Vector Search]#vector-search
- [Concurrency & Thread Safety]#concurrency--thread-safety
- [Public Helper Functions]#public-helper-functions
- [Error Handling]#error-handling
- [Complete API Reference]#complete-api-reference
- [Running the Example]#running-the-example
- [Performance Notes]#performance-notes

---

## Installation

### Path dependency (local development)

```toml
[dependencies]
apexbase = { path = "path/to/ApexBase", default-features = false }
```

### Git dependency

```toml
[dependencies]
apexbase = { git = "https://github.com/BirchKwok/ApexBase.git", default-features = false }
```

Disabling the `python` default feature drops the PyO3 / numpy dependencies and reduces compile time:

```toml
# Pure embedded Rust library — no Python bindings compiled
apexbase = { ..., default-features = false }
```

If you also need the PG Wire or Arrow Flight servers:

```toml
apexbase = { ..., default-features = false, features = ["server"] }
apexbase = { ..., default-features = false, features = ["flight"] }
```

---

## Quick Start

```rust
use apexbase::embedded::{ApexDB, Row};
use apexbase::data::Value;
use std::collections::HashMap;

fn main() -> apexbase::Result<()> {
    // Open (or create) a database directory
    let db = ApexDB::open("./my_database")?;

    // Create a table
    let table = db.create_table("users")?;

    // Insert a record
    let id = table.insert([
        ("name".to_string(),  Value::String("Alice".to_string())),
        ("age".to_string(),   Value::Int64(30)),
        ("score".to_string(), Value::Float64(92.5)),
    ].into_iter().collect())?;

    println!("Inserted _id = {id}");

    // SQL query → Arrow RecordBatch
    let rs = table.execute("SELECT * FROM users WHERE age > 25")?;
    let batch = rs.to_record_batch()?;
    println!("{} rows", batch.num_rows());

    // Or convert to Vec<HashMap<String, Value>>
    let rs = table.execute("SELECT name, age FROM users ORDER BY age")?;
    for row in rs.to_rows()? {
        println!("{:?}", row.get("name"));
    }

    Ok(())
}
```

---

## Core Types

| Type | Description |
|------|-------------|
| `ApexDB` | Top-level database handle. Cheaply `Clone`-able (`Arc` internally). |
| `ApexDBBuilder` | Builder for `ApexDB` with durability and drop-if-exists options. |
| `Table` | Table-scoped operations handle. Cheaply `Clone`-able. |
| `ResultSet` | Result of a SQL query or DML statement. |
| `Row` | `HashMap<String, Value>` — a single database row. |
| `Value` | Enum for all column values (see below). |
| `DurabilityLevel` | `Fast` / `Safe` / `Max` — controls fsync behavior. |
| `ColumnType` | Column type enum used in `create_table_with_schema`. |
| `DataType` | Column type enum used in `add_column` / `schema()`. |

### Value

`apexbase::data::Value` — the universal value type for row data:

| Variant | Rust type | SQL type |
|---------|-----------|----------|
| `Value::Int64(i64)` | `i64` | `INT64` / `INTEGER` |
| `Value::Float64(f64)` | `f64` | `FLOAT64` / `DOUBLE` |
| `Value::String(String)` | `String` | `STRING` / `TEXT` / `VARCHAR` |
| `Value::Bool(bool)` | `bool` | `BOOL` / `BOOLEAN` |
| `Value::Binary(Vec<u8>)` | `Vec<u8>` | `BINARY` / `BLOB` |
| `Value::FixedList(Vec<u8>)` | raw LE f32 bytes | `FIXEDLIST` (vector embedding) |
| `Value::Null` || `NULL` |

```rust
use apexbase::data::Value;

let v_int   = Value::Int64(42);
let v_float = Value::Float64(3.14);
let v_str   = Value::String("hello".to_string());
let v_bool  = Value::Bool(true);
let v_bytes = Value::Binary(vec![0u8, 1, 2, 3]);
let v_null  = Value::Null;
```

### ColumnType

`apexbase::storage::on_demand::ColumnType` — used when pre-defining a schema with `create_table_with_schema`:

| Variant | Description |
|---------|-------------|
| `ColumnType::Int64` | 64-bit signed integer |
| `ColumnType::Float64` | 64-bit IEEE 754 float |
| `ColumnType::String` | UTF-8 string (plain or dict-encoded on disk) |
| `ColumnType::Bool` | Boolean (bit-packed) |
| `ColumnType::Binary` | Arbitrary byte array |
| `ColumnType::FixedList` | Fixed-size float32 vector (embedding storage) |

### DataType

`apexbase::data::DataType` — used in schema introspection (`schema()`, `column_type()`) and column management (`add_column`):

| Variant | Description |
|---------|-------------|
| `DataType::Int64` | 64-bit integer |
| `DataType::Float64` | 64-bit float |
| `DataType::String` | UTF-8 string |
| `DataType::Bool` | Boolean |
| `DataType::Binary` | Byte array |

---

## Database Operations — ApexDB

### Opening a Database

```rust
use apexbase::embedded::ApexDB;

// Default: Fast durability, no drop — creates the directory if absent
let db = ApexDB::open("./data")?;

// Builder pattern for full control
use apexbase::storage::DurabilityLevel;

let db = ApexDB::builder("./data")
    .durability(DurabilityLevel::Safe)
    .drop_if_exists(true)   // wipe all existing .apex files first
    .build()?;
```

### Table DDL

```rust
// Create an empty table (schema inferred from first insert)
let table = db.create_table("events")?;

// Create with a predefined schema — guarantees column order, avoids inference
use apexbase::storage::on_demand::ColumnType;

let table = db.create_table_with_schema("orders", &[
    ("order_id".to_string(), ColumnType::Int64),
    ("product".to_string(),  ColumnType::String),
    ("price".to_string(),    ColumnType::Float64),
    ("shipped".to_string(),  ColumnType::Bool),
])?;

// Open an existing table
let table = db.table("events")?;

// Drop a table (deletes .apex + companion files)
db.drop_table("events")?;

// List all tables in the current database (sorted)
let names: Vec<String> = db.list_tables();

// Current base directory path
let dir = db.base_dir();
```

### Database-level SQL

```rust
// Execute SQL without a specific table context (cross-table queries, DDL, etc.)
let rs = db.execute("SELECT COUNT(*) FROM users")?;
let rs = db.execute("CREATE INDEX idx_age ON users (age)")?;
let rs = db.execute("SELECT u.name, o.product FROM users u JOIN orders o ON u._id = o.user_id")?;
```

### Cache Invalidation

```rust
// Invalidate all engine caches (useful after external writes to the same directory)
db.invalidate_cache();
```

---

## Table Operations — Table

### Write Operations

```rust
use apexbase::embedded::Row;
use apexbase::data::Value;
use std::collections::HashMap;

// ── Insert ────────────────────────────────────────────────────────────────────

// Single record → returns assigned _id (u64, auto-increment)
let mut rec: Row = HashMap::new();
rec.insert("name".to_string(), Value::String("Bob".to_string()));
rec.insert("age".to_string(),  Value::Int64(25));
let id: u64 = table.insert(rec)?;

// Using iterator collect shorthand
let id = table.insert([
    ("name".to_string(), Value::String("Carol".to_string())),
    ("age".to_string(),  Value::Int64(32)),
].into_iter().collect())?;

// Batch insert (most efficient for many rows) → Vec<u64> of _ids
let records: Vec<Row> = (0..1000).map(|i| {
    [
        ("n".to_string(), Value::Int64(i)),
        ("v".to_string(), Value::Float64(i as f64 * 1.5)),
    ].into_iter().collect()
}).collect();
let ids: Vec<u64> = table.insert_batch(&records)?;

// Insert from Arrow RecordBatch (fastest for Arrow-native pipelines)
use arrow::record_batch::RecordBatch;
let ids: Vec<u64> = table.insert_arrow(&batch)?;

// ── Delete ────────────────────────────────────────────────────────────────────

// Delete by _id → true if the row existed
let existed: bool = table.delete(id)?;

// Delete multiple rows → returns count deleted
let deleted: usize = table.delete_batch(&[1, 2, 3, 4, 5])?;

// SQL DELETE with WHERE clause
table.execute("DELETE FROM users WHERE age < 18")?;

// ── Replace (overwrite whole row) ────────────────────────────────────────────

let mut updated: Row = HashMap::new();
updated.insert("name".to_string(),  Value::String("Bob 2.0".to_string()));
updated.insert("age".to_string(),   Value::Int64(26));
let existed: bool = table.replace(id, updated)?;
```

### Read Operations

```rust
// Point lookup by _id → Option<Row>
let row: Option<Row> = table.retrieve(id)?;
if let Some(r) = row {
    println!("{:?}", r.get("name"));
}

// Batch lookup by _ids → Arrow RecordBatch (V4 mmap fast-path)
let batch: RecordBatch = table.retrieve_many(&[1, 2, 3])?;

// Row count — O(1) for V4-format tables (reads footer metadata only)
let n: u64 = table.count()?;

// Check if a row exists
let exists: bool = table.exists(id)?;

// Absolute path to the .apex file
let path: &Path = table.path();
```

### SQL Execution

The full SQL engine (JIT, mmap fast-paths, zone-map pruning) is available on `Table::execute`.  
The table is the default context — use its name unqualified in SQL.

```rust
// SELECT with filter
let rs = table.execute("SELECT name, score FROM users WHERE score > 80")?;

// Aggregation
let rs = table.execute(
    "SELECT city, AVG(score) AS avg_score FROM users GROUP BY city HAVING COUNT(*) > 2"
)?;

// ORDER BY + LIMIT
let rs = table.execute("SELECT * FROM users ORDER BY score DESC LIMIT 10")?;

// Subqueries
let rs = table.execute(
    "SELECT * FROM users WHERE age > (SELECT AVG(age) FROM users)"
)?;

// CTEs
let rs = table.execute(
    "WITH seniors AS (SELECT * FROM users WHERE age >= 30)
     SELECT city, COUNT(*) FROM seniors GROUP BY city"
)?;

// Window functions
let rs = table.execute(
    "SELECT name, ROW_NUMBER() OVER (PARTITION BY city ORDER BY score DESC) AS rn FROM users"
)?;

// DML: UPDATE
table.execute("UPDATE users SET active = true WHERE last_login > '2024-01-01'")?;

// DML: INSERT ... ON CONFLICT (upsert)
table.execute(
    "INSERT INTO users (name, age) VALUES ('Alice', 31)
     ON CONFLICT (name) DO UPDATE SET age = 31"
)?;

// DDL: CREATE TABLE AS
table.execute("CREATE TABLE seniors AS SELECT * FROM users WHERE age >= 30")?;

// EXPLAIN / EXPLAIN ANALYZE
let plan = table.execute("EXPLAIN SELECT * FROM users WHERE age > 25")?;

// File reading directly in SQL (no import step)
let rs = table.execute(
    "SELECT city, COUNT(*) FROM read_csv('/data/users.csv') GROUP BY city LIMIT 10"
)?;
```

### Schema Management

```rust
use apexbase::data::DataType;

// Schema as (column_name, DataType) pairs — preserves column order
let schema: Vec<(String, DataType)> = table.schema()?;
for (col, dtype) in &schema {
    println!("{col}: {dtype:?}");
}

// Column names only
let cols: Vec<String> = table.columns()?;

// Type of a specific column
let dtype: Option<DataType> = table.column_type("age")?;

// Add a new column — all existing rows are set to NULL
table.add_column("active", DataType::Bool)?;

// Drop a column
table.drop_column("temporary_col")?;

// Rename a column
table.rename_column("old_name", "new_name")?;
```

### Maintenance

```rust
// Flush in-memory writes to disk (important for Safe/Max durability)
table.flush()?;
```

---

## ResultSet

`ResultSet` is the return type of every `execute()` call. It holds either an Arrow `RecordBatch`, a scalar `i64`, or an empty result with schema.

```rust
let rs = table.execute("SELECT * FROM users WHERE age > 25")?;

// Number of result rows
println!("{} rows", rs.num_rows());

// Column names in result order
println!("{:?}", rs.columns());   // ["name", "age", "score", ...]

// Check for empty result
if rs.is_empty() { return Ok(()); }

// Convert to Arrow RecordBatch — zero-copy for the Data variant
let batch: RecordBatch = rs.to_record_batch()?;

// Convert to Vec<Row> (HashMap<String, Value>)
let rs = table.execute("SELECT * FROM users LIMIT 10")?;
let rows: Vec<Row> = rs.to_rows()?;
for row in &rows {
    println!("name={:?}  age={:?}", row.get("name"), row.get("age"));
}

// Scalar result — for COUNT(*), SUM, etc.
let rs = table.execute("SELECT COUNT(*) FROM users")?;
if let Some(count) = rs.scalar() {
    println!("count = {count}");
}
```

### ResultSet Methods

| Method | Return type | Description |
|--------|-------------|-------------|
| `to_record_batch()` | `Result<RecordBatch>` | Convert to Arrow `RecordBatch` (zero-copy for `Data` variant) |
| `to_rows()` | `Result<Vec<Row>>` | Convert to `Vec<HashMap<String, Value>>` |
| `num_rows()` | `usize` | Number of result rows |
| `columns()` | `Vec<String>` | Column names in result order |
| `scalar()` | `Option<i64>` | For scalar results (COUNT, SUM, …) |
| `is_empty()` | `bool` | `true` when result has 0 rows |

---

## Multi-Database Support

A single `ApexDB` handle can manage multiple isolated databases (sub-directories). Each named database has its own set of `.apex` files.

```rust
// Switch to a named sub-database (creates the sub-directory if needed)
db.use_database("production")?;
let prod_users = db.create_table("users")?;

// Switch to another named database
db.use_database("staging")?;
let stage_users = db.table("users")?;   // different file from prod

// Cross-database SQL — standard db.table syntax
let rs = db.execute("SELECT * FROM production.users")?;
let rs = db.execute(
    "SELECT u.name, e.event
     FROM production.users u
     JOIN analytics.events e ON u._id = e.user_id"
)?;

// Revert to root database
db.use_database("")?;
```

---

## Durability Levels

| Level | `fsync` | Use Case |
|-------|---------|----------|
| `Fast` (default) | Never | Bulk import, analytics, reconstructible data |
| `Safe` | On `flush()` | Most production write workloads |
| `Max` | Every write | Financial records, critical ledger data |

```rust
use apexbase::storage::DurabilityLevel;

let db = ApexDB::builder("./data")
    .durability(DurabilityLevel::Safe)
    .build()?;

// Insert then explicitly flush to disk
let id = table.insert(row)?;
table.flush()?;    // ensures data survives a process crash
```

---

## Transactions via SQL

ApexBase supports OCC-based transactions with SAVEPOINT / ROLLBACK TO:

```rust
// Begin a transaction
table.execute("BEGIN")?;

table.execute("INSERT INTO users (name, age) VALUES ('Tx1', 20)")?;

// Savepoint
table.execute("SAVEPOINT sp1")?;
table.execute("INSERT INTO users (name, age) VALUES ('Tx2', 21)")?;

// Partial rollback — undo Tx2 only
table.execute("ROLLBACK TO sp1")?;

// Commit — persists Tx1 only
table.execute("COMMIT")?;

// Or rollback everything
table.execute("BEGIN")?;
table.execute("INSERT INTO users (name, age) VALUES ('Abandoned', 99)")?;
table.execute("ROLLBACK")?;
```

Transactions apply to the table context of the `Table` handle. Use `db.execute()` for multi-table transactions.

---

## Indexes

```rust
// B-tree index for range queries and equality lookups
table.execute("CREATE INDEX idx_age ON users (age)")?;

// Unique index for upsert / deduplication
table.execute("CREATE UNIQUE INDEX idx_name ON users (name)")?;

// Queries automatically use indexes when the optimizer detects them
let rs = table.execute("SELECT * FROM users WHERE age = 30")?;   // index scan
let rs = table.execute("SELECT * FROM users WHERE age BETWEEN 20 AND 40")?;  // range scan

// Drop an index
table.execute("DROP INDEX idx_age ON users")?;

// Rebuild all indexes on a table
table.execute("REINDEX users")?;
```

---

## Full-Text Search

```rust
// Create a FTS index on one or more string columns
table.execute("CREATE FTS INDEX ON articles (title, content)")?;

// Search with exact matching
let rs = table.execute("SELECT * FROM articles WHERE MATCH('rust programming')")?;

// Fuzzy search — tolerates typos
let rs = table.execute(
    "SELECT title FROM articles WHERE FUZZY_MATCH('databse')"
)?;

// Combine FTS with regular predicates
let rs = table.execute(
    "SELECT * FROM articles
     WHERE MATCH('machine learning') AND published_at > '2024-01-01'
     ORDER BY _id DESC LIMIT 20"
)?;

// FTS also works in aggregations
let rs = table.execute("SELECT COUNT(*) FROM articles WHERE MATCH('deep learning')")?;

// Manage the index
table.execute("SHOW FTS INDEXES")?;
table.execute("ALTER FTS INDEX ON articles DISABLE")?;  // suspend, keep files
table.execute("DROP FTS INDEX ON articles")?;           // remove index + files
```

---

## Vector Search

ApexBase provides SIMD-accelerated (NEON / AVX2) nearest-neighbour search. Vectors are stored as `FixedList` columns.

```rust
use apexbase::data::Value;
use std::collections::HashMap;

// Store vectors — encode float32 values as raw LE bytes
fn encode_f32_vec(floats: &[f32]) -> Vec<u8> {
    floats.iter().flat_map(|f| f.to_le_bytes()).collect()
}

// Create a table with a vector column
let items = db.create_table_with_schema("items", &[
    ("label".to_string(), ColumnType::String),
    ("vec".to_string(),   ColumnType::FixedList),
])?;

// Insert records with 4-dimensional float32 vectors
for (label, v) in [("a", [0.1f32, 0.2, 0.3, 0.4]),
                   ("b", [0.9, 0.8, 0.7, 0.6])] {
    let mut r = HashMap::new();
    r.insert("label".to_string(), Value::String(label.to_string()));
    r.insert("vec".to_string(),   Value::FixedList(encode_f32_vec(&v)));
    items.insert(r)?;
}

// SQL TopK — array literal syntax
let rs = items.execute(
    "SELECT explode_rename(topk_distance(vec, [0.1, 0.2, 0.3, 0.4], 5, 'l2'), '_id', 'dist')
     FROM items"
)?;
let rows = rs.to_rows()?;
for row in &rows {
    println!("_id={:?}  dist={:?}", row.get("_id"), row.get("dist"));
}

// SQL vector distance functions
let rs = items.execute(
    "SELECT label, array_distance(vec, [0.1, 0.2, 0.3, 0.4]) AS dist
     FROM items ORDER BY dist LIMIT 5"
)?;
```

**Supported distance metrics:** `'l2'` / `'euclidean'`, `'l2_squared'`, `'l1'` / `'manhattan'`, `'linf'` / `'chebyshev'`, `'cosine'` / `'cosine_distance'`, `'dot'` / `'inner_product'`

---

## Concurrency & Thread Safety

Both `ApexDB` and `Table` are `Clone + Send + Sync`. The underlying storage engine uses fine-grained `RwLock`s, and Rayon powers parallel aggregation and vector scans.

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

let db = Arc::new(ApexDB::open("./data")?);
let table = Arc::new(db.create_table("events")?);

let handles: Vec<_> = (0..4).map(|thread_id| {
    let t = Arc::clone(&table);
    thread::spawn(move || {
        // Concurrent reads are fully parallel (shared mmap)
        let rs = t.execute("SELECT COUNT(*) FROM events").unwrap();
        println!("thread {thread_id}: count = {:?}", rs.scalar());
    })
}).collect();

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

**Notes:**
- `ApexDB::clone()` and `Table::clone()` are `O(1)` — they share the same `Arc<DbInner>`.
- Reads (`execute`, `retrieve`, `retrieve_many`, `count`) are lock-free on V4 mmap-only tables.
- Writes are serialized per-table via an internal write lock in the storage engine.
- `use_database()` modifies shared state; avoid calling it from multiple threads concurrently.

---

## Public Helper Functions

Two public functions are exported from `apexbase::embedded`:

```rust
use apexbase::embedded::{record_batch_to_rows, arrow_value_at};
use arrow::array::ArrayRef;

// Convert a full RecordBatch to Vec<Row>
let rows: Vec<Row> = record_batch_to_rows(&batch)?;

// Extract a single Value at a specific row index from any ArrayRef
let val: Value = arrow_value_at(&column_ref, row_index);
```

**`record_batch_to_rows(batch: &RecordBatch) → Result<Vec<Row>>`**  
Iterates over all columns and rows, calling `arrow_value_at` for each cell.

**`arrow_value_at(arr: &ArrayRef, row: usize) → Value`**  
Handles: `Int8/16/32/64`, `UInt8/16/32/64`, `Float32/64`, `Boolean`, `Utf8`, `LargeUtf8`, `Binary`, `LargeBinary`. Returns `Value::Null` for null entries or unrecognized types.

---

## Error Handling

All methods return `apexbase::Result<T>` which is `Result<T, ApexError>`.

```rust
use apexbase::ApexError;

match db.table("nonexistent") {
    Err(ApexError::TableNotFound(name)) => eprintln!("No table: {name}"),
    Err(ApexError::TableExists(name))   => eprintln!("Already exists: {name}"),
    Err(ApexError::Io(e))               => eprintln!("IO error: {e}"),
    Err(e)                              => eprintln!("Other: {e}"),
    Ok(table)                           => { /* use table */ }
}

// Use ? for ergonomic propagation
fn process(db: &ApexDB) -> apexbase::Result<()> {
    let table = db.table("orders")?;
    let rs = table.execute("SELECT * FROM orders WHERE price > 100")?;
    let rows = rs.to_rows()?;
    println!("{} expensive orders", rows.len());
    Ok(())
}
```

---

## Complete API Reference

### ApexDB

| Method | Return type | Description |
|--------|-------------|-------------|
| `ApexDB::open(path)` | `Result<ApexDB>` | Open/create database with `Fast` durability |
| `ApexDB::builder(path)` | `ApexDBBuilder` | Return builder for custom options |
| `create_table(name)` | `Result<Table>` | Create new empty table |
| `create_table_with_schema(name, schema)` | `Result<Table>` | Create table with predefined schema |
| `table(name)` | `Result<Table>` | Open existing table |
| `drop_table(name)` | `Result<()>` | Delete table + companion files |
| `list_tables()` | `Vec<String>` | Sorted list of all tables in current db dir |
| `execute(sql)` | `Result<ResultSet>` | Database-level SQL (no default table context) |
| `use_database(name)` | `Result<()>` | Switch to named sub-database (`""` = root) |
| `base_dir()` | `PathBuf` | Current base directory path |
| `invalidate_cache()` | `()` | Evict all engine caches for current directory |

### ApexDBBuilder

| Method | Return type | Description |
|--------|-------------|-------------|
| `durability(level)` | `ApexDBBuilder` | Set durability level (default: `Fast`) |
| `drop_if_exists(flag)` | `ApexDBBuilder` | If `true`, wipe existing `.apex` files on open |
| `build()` | `Result<ApexDB>` | Build and open the database |

### Table

#### Write

| Method | Return type | Description |
|--------|-------------|-------------|
| `insert(row)` | `Result<u64>` | Insert one record; returns assigned `_id` |
| `insert_batch(rows)` | `Result<Vec<u64>>` | Batch insert; returns all assigned `_id`s |
| `insert_arrow(batch)` | `Result<Vec<u64>>` | Insert Arrow `RecordBatch`; fastest for Arrow data |
| `replace(id, row)` | `Result<bool>` | Overwrite row by `_id`; `true` if existed |
| `delete(id)` | `Result<bool>` | Delete row by `_id`; `true` if existed |
| `delete_batch(ids)` | `Result<usize>` | Delete multiple rows; returns count deleted |

#### Read

| Method | Return type | Description |
|--------|-------------|-------------|
| `retrieve(id)` | `Result<Option<Row>>` | Point lookup by `_id` |
| `retrieve_many(ids)` | `Result<RecordBatch>` | Batch lookup by `_id`s (V4 mmap fast-path) |
| `count()` | `Result<u64>` | Active row count (O(1) for V4 tables) |
| `exists(id)` | `Result<bool>` | Check if row with `_id` exists |
| `path()` | `&Path` | Absolute path to the `.apex` file |

#### SQL

| Method | Return type | Description |
|--------|-------------|-------------|
| `execute(sql)` | `Result<ResultSet>` | Run SQL with this table as default context |

#### Schema

| Method | Return type | Description |
|--------|-------------|-------------|
| `schema()` | `Result<Vec<(String, DataType)>>` | Full schema in column order |
| `columns()` | `Result<Vec<String>>` | Column names in schema order |
| `column_type(name)` | `Result<Option<DataType>>` | Type of a specific column |
| `add_column(name, dtype)` | `Result<()>` | Add new column (existing rows → NULL) |
| `drop_column(name)` | `Result<()>` | Drop a column |
| `rename_column(old, new)` | `Result<()>` | Rename a column |

#### Maintenance

| Method | Return type | Description |
|--------|-------------|-------------|
| `flush()` | `Result<()>` | Flush in-memory writes to disk |

---

## Running the Example

```bash
cargo run --example embedded --no-default-features
```

The example at `examples/embedded.rs` demonstrates all 16 steps:
- Opening a database with the builder
- Creating a table with predefined schema
- Inserting individual rows
- SQL queries (filter, aggregate, GROUP BY)
- Bulk insert (1 000 rows)
- Delete and replace by `_id`
- `retrieve_many` (Arrow batch read)
- Schema introspection (`columns()`)
- Add / drop columns
- Multi-table operations
- Database-level SQL (`db.execute`)

---

## Performance Notes

- **Point lookups** (`retrieve`) — O(log n) via V4 RCIX index, ~24 µs warm.
- **Batch reads** (`retrieve_many`) — single footer lock + one mmap slice per row-group via V4 mmap fast-path.
- **Bulk insert** (`insert_batch`) — routes through `engine.write()`, auto-selects delta append or full V4 rewrite based on schema.
- **Arrow insert** (`insert_arrow`) — bypasses `HashMap` construction; preferred for Arrow-native pipelines.
- **SQL queries** — same Arrow-native JIT engine as the Python API: Cranelift JIT, vectorized SIMD filters, zone-map pruning, mmap on-demand scans.
- **Count** (`count()`) — O(1) for V4 tables — reads only the footer metadata.
- **Concurrency** — reads are parallel on V4 mmap-only tables (no lock contention); writes are serialized per table.
- **Vector search** — SIMD-accelerated (NEON fp16 on ARM, AVX2+F16C on x86_64); 3–4× faster than DuckDB at 1M rows × dim=128.