|
| 1 | +# FOR Clause Parsing in Opteryx |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Opteryx supports temporal filtering using FOR clauses - a non-standard SQL extension that allows querying data at specific points in time or time ranges. |
| 6 | + |
| 7 | +## Syntax |
| 8 | + |
| 9 | +```sql |
| 10 | +-- Single point in time |
| 11 | +SELECT * FROM planets FOR TODAY |
| 12 | +SELECT * FROM planets FOR '2020-01-01' |
| 13 | + |
| 14 | +-- Date range |
| 15 | +SELECT * FROM planets FOR DATES BETWEEN '2020-01-01' AND '2020-12-31' |
| 16 | +SELECT * FROM planets FOR DATES BETWEEN YESTERDAY AND TODAY |
| 17 | + |
| 18 | +-- Named ranges |
| 19 | +SELECT * FROM planets FOR DATES IN THIS_MONTH |
| 20 | +SELECT * FROM planets FOR DATES IN LAST_MONTH |
| 21 | + |
| 22 | +-- Relative ranges |
| 23 | +SELECT * FROM planets FOR DATES SINCE '2020-01-01' |
| 24 | +SELECT * FROM planets FOR LAST 7 DAYS |
| 25 | + |
| 26 | +-- Multiple tables with different temporal filters |
| 27 | +SELECT * FROM planets FOR TODAY |
| 28 | + INNER JOIN satellites FOR YESTERDAY |
| 29 | + ON planets.id = satellites.planet_id |
| 30 | +``` |
| 31 | + |
| 32 | +## Implementation |
| 33 | + |
| 34 | +### Current Approach (Python) |
| 35 | + |
| 36 | +The FOR clause is currently parsed using a Python-based approach in `opteryx/planner/sql_rewriter.py`: |
| 37 | + |
| 38 | +1. **SQL Rewriting** (`do_sql_rewrite`): |
| 39 | + - Uses regex to split SQL into parts while preserving quoted strings |
| 40 | + - Handles special string prefixes (`b""` for binary, `r""` for raw strings) |
| 41 | + - Removes SQL comments |
| 42 | + |
| 43 | +2. **Temporal Extraction** (`extract_temporal_filters`): |
| 44 | + - Uses a state machine to identify table references and their FOR clauses |
| 45 | + - Handles special cases like functions that use FROM keyword (EXTRACT, SUBSTRING, TRIM) |
| 46 | + - Tracks nested subqueries and multiple table references |
| 47 | + - Returns cleaned SQL (without FOR clauses) and a list of temporal filters |
| 48 | + |
| 49 | +3. **AST Binding** (`temporal_range_binder` in `ast_rewriter.py`): |
| 50 | + - Adds temporal information back into the parsed AST |
| 51 | + - Binds start_date and end_date to table references |
| 52 | + - Handles various table reference formats (Table, table_name, parent_name, ShowCreate) |
| 53 | + |
| 54 | +### Why Not Native Parser Support? |
| 55 | + |
| 56 | +Adding native FOR clause support to the SQL parser (sqlparser-rs) would be ideal but faces challenges: |
| 57 | + |
| 58 | +1. **External Dependency**: sqlparser-rs is a third-party crate. Modifying it would require either: |
| 59 | + - Forking the repository (maintenance burden) |
| 60 | + - Contributing changes upstream (slow, may not align with project goals) |
| 61 | + - Using local patches (fragile) |
| 62 | + |
| 63 | +2. **Dialect Limitations**: The sqlparser-rs Dialect trait provides limited extension points: |
| 64 | + - `parse_infix`: For custom infix operators (not applicable) |
| 65 | + - `parse_prefix`: For custom prefix operators (not applicable) |
| 66 | + - `parse_statement`: For custom statements (too coarse-grained) |
| 67 | + - No hook for extending table reference parsing |
| 68 | + |
| 69 | +3. **AST Modifications**: Adding FOR clause support would require: |
| 70 | + - Extending TableFactor::Table with new fields |
| 71 | + - Modifying the parser's `parse_table_factor` function |
| 72 | + - Ensuring serialization/deserialization works with Python |
| 73 | + |
| 74 | +### Future Directions |
| 75 | + |
| 76 | +There are several potential paths forward: |
| 77 | + |
| 78 | +#### Option 1: Rust Implementation of Current Approach |
| 79 | +- Port the Python regex and state machine logic to Rust |
| 80 | +- Expose as a function callable from Python |
| 81 | +- Benefits: Performance, type safety, reduced Python complexity |
| 82 | +- Challenges: Complex porting effort, need to maintain parity |
| 83 | + |
| 84 | +**Status**: Proof-of-concept started in `src/temporal_parser.rs` |
| 85 | + |
| 86 | +#### Option 2: Fork sqlparser-rs |
| 87 | +- Fork the sqlparser-rs repository |
| 88 | +- Add native FOR clause support |
| 89 | +- Use the fork via git dependency in Cargo.toml |
| 90 | +- Benefits: Clean parser integration, proper AST support |
| 91 | +- Challenges: Maintenance burden, staying in sync with upstream |
| 92 | + |
| 93 | +#### Option 3: Use Existing Extension Points |
| 94 | +- Convert FOR clauses to WITH hints during preprocessing |
| 95 | +- Example: `FROM table FOR TODAY` → `FROM table WITH(__TEMPORAL__='TODAY')` |
| 96 | +- sqlparser-rs already supports WITH hints |
| 97 | +- Extract hints after parsing and convert to temporal filters |
| 98 | +- Benefits: Uses standard SQL syntax, minimal changes |
| 99 | +- Challenges: Slightly awkward, requires coordination between preprocessing and post-processing |
| 100 | + |
| 101 | +#### Option 4: Keep Current Approach |
| 102 | +- The current Python implementation works well |
| 103 | +- It's well-tested and handles many edge cases |
| 104 | +- Focus efforts on other improvements |
| 105 | +- Benefits: No risk, proven solution |
| 106 | +- Challenges: Python complexity remains |
| 107 | + |
| 108 | +## Recommendations |
| 109 | + |
| 110 | +For now, the Python implementation should remain the authoritative version because: |
| 111 | + |
| 112 | +1. It's well-tested and handles all edge cases |
| 113 | +2. It's proven in production |
| 114 | +3. The complexity of a complete Rust port is significant |
| 115 | +4. The performance benefit may not justify the porting effort |
| 116 | + |
| 117 | +If native parser support becomes a priority: |
| 118 | + |
| 119 | +1. Start with Option 3 (WITH hints) as a low-risk experiment |
| 120 | +2. If successful, consider Option 2 (fork) for long-term maintainability |
| 121 | +3. Option 1 (Rust port) could be done incrementally as an optimization |
| 122 | + |
| 123 | +## Related Files |
| 124 | + |
| 125 | +- `opteryx/planner/sql_rewriter.py` - Current implementation |
| 126 | +- `opteryx/planner/ast_rewriter.py` - AST binding logic |
| 127 | +- `src/temporal_parser.rs` - Proof-of-concept Rust version |
| 128 | +- `tests/unit/planner/test_temporal_extraction.py` - Test suite |
0 commit comments