oxibase 0.2.2

Autonomous relational database management system 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
---
layout: default
title: Expression Pushdown
parent: Architecture
nav_order: 6
---

# Expression Pushdown

This document explains Oxibase's expression pushdown optimization, which moves filtering operations closer to the data to improve query performance.

## What is Expression Pushdown?

Expression pushdown is a query optimization technique that "pushes" filter expressions (typically from WHERE clauses) down to the lowest possible level in the query execution stack. This minimizes the amount of data that needs to be processed at higher levels of the execution engine.

## Benefits of Expression Pushdown

### Performance Improvements

- **Reduced Data Processing** - Filter data early to avoid unnecessary processing
- **Reduced Memory Usage** - Process only relevant data
- **Improved Cache Efficiency** - Better CPU cache utilization with smaller data sets
- **Optimized I/O** - Less data loaded from storage
- **Improved Parallelism** - Filter operations can be parallelized at lower levels

### Measurable Impacts

Depending on the query, expression pushdown can provide:

- 10-100x reduction in processed data volume
- 2-10x improvement in query execution time
- Significant memory usage reduction

## How Expression Pushdown Works in Oxibase

### Pushdown Levels

Oxibase implements expression pushdown at multiple levels:

1. **Storage Level Pushdown** - Expressions pushed directly to storage
2. **Index Level Pushdown** - Expressions leveraging indexes (B-tree, Hash, Bitmap)
3. **Scan Level Pushdown** - Expressions applied during table scanning
4. **Join Level Pushdown** - Expressions pushed before or into joins

**Note**: Subquery pushdown is not yet implemented.

### The Pushdown Process

When processing a query with filtering conditions:

1. The SQL parser creates an abstract syntax tree (AST)
2. The query analyzer examines filter expressions
3. Pushdown eligibility is determined for each expression
4. Expressions are transformed into a format suitable for lower levels
5. Execution plans are modified to incorporate pushed-down expressions
6. The storage engine applies filters directly when possible

## Expression Types

Not all expressions can be pushed down. Oxibase categorizes expressions by pushdown eligibility:

### Fully Pushable Expressions

These expressions can be pushed all the way to the storage layer:

- **Simple Comparisons** - Equality (=), inequality (!=, <>), greater/less than (>, <, >=, <=)
- **Range Conditions** - BETWEEN, IN
- **Null Checks** - IS NULL, IS NOT NULL
- **String Patterns** - LIKE (with limitations)
- **Logical Operations** - AND, OR, NOT

```sql
-- All filters can be pushed down to storage
SELECT * FROM orders WHERE
    status = 'shipped' AND
    order_date BETWEEN '2022-01-01' AND '2022-12-31' AND
    customer_id IN (1, 2, 3);
```

### Partially Pushable Expressions

These expressions can be partially pushed down:

- **Simple Functions** - ABS(), LOWER(), UPPER()
- **Date/Time Functions** - DATE_TRUNC(), TIME_TRUNC()
- **Type Casts** - CAST()
- **Simple Arithmetic** - +, -, *, /

```sql
-- The date_trunc can be pushed down, making this more efficient
SELECT * FROM orders WHERE
    DATE_TRUNC('month', order_date) = DATE_TRUNC('month', NOW());
```

### Non-Pushable Expressions

These typically cannot be pushed down:

- **Complex Functions** - User-defined functions, complex built-in functions
- **Aggregates** - SUM(), COUNT(), AVG() (though parts of the expression may be pushed)
- **Window Functions** - ROW_NUMBER(), RANK()
- **Subquery References** - Correlated subqueries

```sql
-- The aggregate function prevents full pushdown
SELECT * FROM orders WHERE
    total > (SELECT AVG(total) FROM orders);
```

## Implementation Details

### Expression Translation

Expressions are translated from SQL syntax to storage-level predicates:

1. **Parser** - Parses SQL into an abstract syntax tree
2. **Analyzer** - Identifies pushable expressions
3. **Translator** - Converts SQL expressions to storage predicates
4. **Optimizer** - Determines optimal pushdown strategy
5. **Executor** - Applies the pushdown during execution

### Expression Evaluation System

Oxibase uses a compile-once, execute-many bytecode VM for expression evaluation:

```mermaid
graph LR
    subgraph "Compilation Phase"
        AST["Expression AST<br/>(parser/ast.rs)"]
        Compiler["ExprCompiler<br/>(executor/expression/compiler.rs)"]
        Program["Program<br/>(bytecode instructions)"]
    end

    subgraph "Execution Phase"
        VM["ExprVM<br/>(executor/expression/vm.rs)"]
        Stack["Value Stack"]
        ExecuteCtx["ExecuteContext<br/>(row data, params)"]
    end

    subgraph "High-Level APIs"
        ExprEval["ExpressionEval<br/>(owns Program + VM)"]
        RowFilter["RowFilter<br/>(Arc Program + thread_local VM)"]
        MultiExpr["MultiExpressionEval<br/>(Vec Program + shared VM)"]
    end

    subgraph "Function System"
        FuncRegistry["FunctionRegistry<br/>(functions/registry.rs)"]
        ScalarFuncs["ScalarFunction<br/>(101+ functions)"]
        AggFuncs["AggregateFunction<br/>(COUNT, SUM, AVG, etc.)"]
        WindowFuncs["WindowFunction<br/>(ROW_NUMBER, RANK, etc.)"]
    end

    AST -->|"compile"| Compiler
    Compiler -->|"generates"| Program

    Program -->|"Arc clone"| ExprEval
    Program -->|"Arc clone"| RowFilter
    Program -->|"clone per expr"| MultiExpr

    ExprEval -->|"owns"| VM
    RowFilter -->|"thread_local"| VM
    MultiExpr -->|"owns"| VM

    VM -->|"executes"| Program
    ExecuteCtx -->|"provides"| VM
    VM -->|"manipulates"| Stack

    VM -->|"FunctionCall instruction"| FuncRegistry
    FuncRegistry --> ScalarFuncs
    FuncRegistry --> AggFuncs
    FuncRegistry --> WindowFuncs
```

**Key Design Decisions:**
1. **Zero Recursion**: Linear instruction sequences eliminate stack overflow risks
2. **Immutable Programs**: Bytecode is `Arc`-shared across threads for parallel execution
3. **Thread-Safe Filtering**: `RowFilter` uses thread-local VMs for parallel table scans
4. **Context Separation**: Compilation context (schema) vs execution context (row data) enables pre-compilation

**Instruction Set:**
- `Load(index)` - Load column value onto stack
- `LoadConst(Value)` - Load constant value
- `BinOp(Operator)` - Execute binary operation (Add, Sub, Mul, Div, etc.)
- `UnaryOp(Operator)` - Execute unary operation (Neg, Not)
- `FunctionCall(id, argc)` - Call function from registry
- `JumpIf(offset)` - Conditional jump for CASE expressions
- `Return` - End execution, return top of stack

### Specialized Expression Types

Oxibase implements specialized expression types for efficient pushdown:

- **SimpleExpression** - Basic comparison operations
- **BetweenExpression** - Range checks
- **InListExpression** - Set membership tests
- **LogicalExpression** - Combining expressions with AND/OR
- **NullCheckExpression** - NULL checks
- **RangeExpression** - Optimized range queries
- **NotExpression** - Logical negation

These expression types are implemented in `src/storage/expression/`.

### Storage-Level Implementation

At the storage level, Oxibase implements optimized filtering:

- **Index-Based Filtering** - Filter operations using B-tree, Hash, and Bitmap indexes
- **Parallel Evaluation** - Multi-threaded predicate evaluation using Rayon
- **Bitmap Results** - Bitmap representation of matching positions
- **Efficient Traversal** - Row-based filtering with MVCC visibility checks

## Pushdown Strategies

### Filter Pushdown Levels

Oxibase implements pushdown at multiple architectural levels:

1. **Storage Level Pushdown** - Expressions pushed directly to storage with MVCC visibility checks
2. **Index Level Pushdown** - B-tree, Hash, and Bitmap indexes filter data during index scans
3. **Arena Level Pushdown** - Zero-copy filtering during arena-based scans
4. **Join Level Pushdown** - Filters applied before expensive join operations

### Filter Pushdown

Basic filter pushdown moves WHERE conditions down:

```sql
-- Before pushdown (conceptual)
Table Scan (orders) → Filter (status = 'shipped') → Project

-- After pushdown (conceptual)
Table Scan with Filter (orders, status = 'shipped') → Project
```

### Join Pushdown

Filters are pushed before or into joins:

```sql
-- Before pushdown (conceptual)
Table Scan (orders) → Join with customers → Filter (status = 'shipped')

-- After pushdown (conceptual)
Table Scan with Filter (orders, status = 'shipped') → Join with customers
```

### Index Utilization

Pushed-down expressions leverage indexes:

```sql
-- When an index exists on status, this becomes:
Index Scan (orders_status_idx, 'shipped') → Project
```

### Predicate Simplification

Expressions are simplified when possible:

```sql
-- Before simplification
WHERE age >= 18 AND age <= 65 AND age != 30

-- After simplification
WHERE age BETWEEN 18 AND 65 AND age != 30
```

### LIMIT Pushdown

For queries with LIMIT clauses, pushdown can significantly reduce processing:

```sql
-- Without LIMIT pushdown: scan all rows, then limit
SELECT * FROM products WHERE category = 'electronics' LIMIT 10;

-- With LIMIT pushdown: scan only until 10 matching rows found
-- Uses index to find first 10 matches efficiently
```

### TOP-N Pushdown

For ORDER BY + LIMIT queries, pushdown optimizes sorting:

```sql
-- Without TOP-N pushdown: scan all rows, sort, then limit
SELECT * FROM orders ORDER BY total DESC LIMIT 5;

-- With TOP-N pushdown: use heap to track top 5, scan efficiently
-- 50x+ performance improvement for large datasets
```

## Performance Optimizations

### Zero-Copy Scanning

The `RowArena` provides contiguous memory storage for efficient pushdown:

**Performance characteristics:**
- **50x+ speedup** for full table scans vs. per-row cloning
- Pre-acquire locks once per scan (O(1) instead of O(N))
- Direct slice access via bounds-checked reads
- Cache locality from contiguous layout

### Parallel Predicate Evaluation

Pushdown expressions are evaluated in parallel using Rayon:

**Batch Operations:**

| Operation | Traditional (Per-Row) | Batch API | Improvement |
|-----------|----------------------|-----------|-------------|
| **Filter 1000 rows** | 1000 evaluations | 1 batch evaluation | ~10x reduction |
| **Index scans** | Sequential evaluation | Parallel chunks | CPU-bound speedup |
| **Complex predicates** | N evaluations | Parallel workers | Multi-core scaling |

### Thread-Safe Row Filtering

`RowFilter` uses thread-local VMs for parallel table scans:

```rust
// Thread-local VM ensures no contention
let filter = RowFilter::new(predicate_program);
let matching_rows: Vec<Row> = rows.par_iter()
    .filter(|row| filter.eval(row))
    .collect();
```

### Predicate Pushdown in Practice

**Example: Complex WHERE clause pushdown:**

```sql
SELECT * FROM orders
WHERE status IN ('shipped', 'delivered')
  AND total > 100
  AND customer_id BETWEEN 1000 AND 2000
  AND created_at >= '2023-01-01';
```

**Pushdown execution:**
1. **Index intersection**: Status IN uses Hash/Bitmap index, customer_id BETWEEN uses B-tree
2. **Predicate combination**: AND conditions combined at storage level
3. **Parallel evaluation**: Multiple threads evaluate the complex predicate
4. **Early termination**: Results streamed as soon as available

## Performance Considerations

### When Pushdown Helps Most

Expression pushdown provides the greatest benefits when:

1. **High Selectivity** - Filters eliminate a large percentage of rows
2. **Early Filtering** - Filters can be applied before expensive operations
3. **Index Availability** - Filters can use available indexes
4. **Complex Queries** - Queries with joins, subqueries, or aggregations

### Potential Limitations

Some scenarios may limit pushdown benefits:

1. **Low Selectivity Filters** - If most rows match, pushdown may not help much
2. **Complex Expressions** - Not all expressions can be pushed down
3. **Function-Based Filters** - Functions on columns may prevent index usage

## Query Examples

### Simple Pushdown

```sql
-- Highly efficient: Pushes filter to storage and uses index if available
SELECT * FROM customers WHERE country = 'US';
```

### Multiple Filter Pushdown

```sql
-- All conditions are pushed down and combined at the storage level
SELECT * FROM orders
WHERE status = 'shipped'
  AND order_date > '2022-01-01'
  AND total > 100;
```

### Join Pushdown

```sql
-- Filters are pushed before the join
SELECT c.name, o.order_date
FROM customers c
JOIN orders o ON c.id = o.customer_id
WHERE c.country = 'US' AND o.status = 'shipped';
```

### Function Pushdown

```sql
-- LOWER function can be pushed down
SELECT * FROM products
WHERE LOWER(name) LIKE '%organic%';
```

## Implementation Details

Oxibase's expression pushdown is implemented in several components:

- **src/executor/query.rs** - High-level pushdown decisions
- **src/executor/planner.rs** - Query planning with pushdown
- **src/storage/expression/** - Specialized expression types
- **src/storage/mvcc/table.rs** - Storage-level filtering

### Expression Compilation Architecture

Expressions are compiled once and executed many times:

```mermaid
graph LR
    subgraph "SQL Parser"
        SQL["SELECT * FROM t WHERE x > 5"]
        AST["Abstract Syntax Tree"]
    end

    subgraph "Expression Compiler"
        AST --> Compiler["ExprCompiler"]
        Compiler --> Program["Bytecode Program<br/>(Arc-shared)"]
    end

    subgraph "Execution Contexts"
        Single["ExpressionEval<br/>(single evaluation)"]
        Filter["RowFilter<br/>(thread-local VM)"]
        Multi["MultiExpressionEval<br/>(batch evaluation)"]
    end

    subgraph "VM Execution"
        VM["ExprVM<br/>(stack-based)"]
        Stack["Value Stack"]
        Context["ExecuteContext<br/>(row + params)"]
    end

    Program --> Single
    Program --> Filter
    Program --> Multi

    Single --> VM
    Filter --> VM
    Multi --> VM

    VM --> Stack
    Context --> VM
```

### Pushdown Decision Logic

The optimizer determines pushdown eligibility:

```rust
fn can_pushdown(expr: &Expression) -> PushdownLevel {
    match expr {
        // Storage level - direct to MVCC
        Expression::BinaryOp { op: Eq, .. } => PushdownLevel::Storage,
        Expression::Between { .. } => PushdownLevel::Storage,

        // Index level - leverages indexes
        Expression::InList { .. } if has_index => PushdownLevel::Index,

        // Scan level - during table iteration
        Expression::FunctionCall { name: "lower", .. } => PushdownLevel::Scan,

        // Cannot push down
        Expression::Subquery { .. } => PushdownLevel::None,
        _ => PushdownLevel::None,
    }
}
```

### Filter Pushdown Examples

**Primary Key Lookup Pushdown:**
```sql
-- Direct index lookup, O(1)
SELECT * FROM users WHERE id = 12345;
```
**Execution:** `VersionStore::get_visible_version(12345, txn_id)`

**Index Scan Pushdown:**
```sql
-- B-tree range scan with pushdown
SELECT * FROM orders WHERE total > 1000;
```
**Execution:** `BTreeIndex::scan_range(1000.., txn_id)`

**Bitmap Index Pushdown:**
```sql
-- Bitmap operations for low-cardinality filters
SELECT * FROM products WHERE category IN ('electronics', 'books');
```
**Execution:** `BitmapIndex::bitmap_or(['electronics', 'books'])`

### Monitoring Pushdown Effectiveness

To verify pushdown is working:

```sql
-- View query execution plan
EXPLAIN SELECT * FROM orders WHERE status = 'shipped' AND total > 100;

-- Expected output shows pushdown usage
-- "Filter Pushdown: status = 'shipped' AND total > 100"
-- "Index Utilization: orders_status_idx (Hash), orders_total_idx (B-tree)"
```

### Pushdown Limitations

**Current Limitations:**
- **Subquery pushdown** - Not yet implemented
- **Complex aggregations** - Limited pushdown support
- **Window functions** - No pushdown currently
- **User-defined functions** - Cannot be pushed down

**Workarounds:**
- **Materialized views** - Pre-compute complex expressions
- **Query restructuring** - Rewrite queries to enable pushdown
- **Index design** - Create indexes on computed columns

## Best Practices

To maximize the benefits of expression pushdown:

1. **Use direct column references** - Avoid functions on indexed columns in WHERE clauses
2. **Create appropriate indexes** - Indexes enable better pushdown optimizations
3. **Use simple predicates** - Simple expressions are more likely to be pushed down
4. **Monitor query performance** - Use execution timing to verify optimization effectiveness
5. **Combine multiple conditions** - AND conditions can be pushed down effectively

## Advanced Techniques

### Custom Expressions

Oxibase allows for specialized expression types:

```sql
-- Range expressions are highly optimized
SELECT * FROM time_series
WHERE timestamp BETWEEN '2022-01-01' AND '2022-01-31';
```

### Combined Index and Expression Pushdown

When expressions match available indexes:

```sql
-- With a composite index on (status, order_date), this is very efficient
SELECT * FROM orders
WHERE status = 'shipped' AND order_date > '2022-01-01';
```

### Function Index Pushdown

When functions are used in filtering:

```sql
-- If an index exists on LOWER(email), this pushdown is efficient
SELECT * FROM users WHERE LOWER(email) = 'user@example.com';
```