Query Classification
The classifier parses each incoming statement to extract:- OpType —
READ,WRITE,DDL,TRANSACTION, orOTHER - SubType — specific operation (e.g.,
SELECT,INSERT,UPDATE,DELETE,ALTER,BEGIN) - Tables — all tables referenced in the query
- Primary Keys — extractable PKs from WHERE clauses (e.g.,
WHERE id = 42) - Feature flags —
IsJoin,HasAggregate,HasSetOp,IsComplexRead,HasReturning,HasOnConflict,HasWindowFunc,HasDistinct,HasCursor,IsMetadataQuery
Classification by Engine
Statement Types (SQL Engines)
Unsupported Operations by Engine
Routing Decision Matrix
The router takes the classification output plus the current delta/tombstone/schema state and selects one of 12 execution strategies.Strategies
Decision Table (SQL Engines)
Decision Table (Redis)
Redis uses the same strategy model, but classification is command-based rather than SQL-based.Decision Table (Firestore)
Firestore classification is gRPC method-based.Read Operations
Prod Direct
When a SELECT touches only tables with no local mutations, the query passes straight through to prod. Zero overhead beyond the proxy hop.Merged Read (Single Table)
When a SELECT touches a table with delta or tombstone entries:1
Shadow Query
Execute the original query against shadow. Returns the local subset of results.
2
Prod Query (over-fetched)
Execute against prod with adjusted LIMIT:
original_limit + |deltas| + |tombstones|. If schema divergence exists, the query is rewritten first (remove predicates on shadow-only columns, revert renamed columns).3
Filter Prod Results
Discard rows where
(table, pk) exists in the Delta Map (shadow has the authoritative version) or the Tombstone Set (locally deleted).4
Adapt Prod Results
Per the Schema Registry: inject NULLs for added columns, strip dropped columns, apply renames and type casts.
5
Merge and Deduplicate
Combine shadow results with filtered/adapted prod results. Deduplicate by PK — shadow wins on conflict.
6
Re-apply Ordering and Limits
Sort by original ORDER BY, apply LIMIT/OFFSET to produce the final result set.
delta_count + tombstone_count to the LIMIT to compensate for rows that will be filtered out.
Redis merged reads use a simpler per-key routing model instead of the SQL merge pipeline. Each key is checked against the delta map and tombstone set individually. MGET batches per-key routing. SCAN uses a two-phase cursor: Phase 0 scans prod (filtering delta/tombstoned keys), Phase 1 scans shadow for new/modified keys.
Firestore merged reads use document-path-based deduplication. GetDocument routes per-document (tombstone → NOT_FOUND, delta → shadow, else → prod). ListDocuments and RunQuery over-fetch from prod, query shadow, merge by document path, re-evaluate WHERE filters, and re-sort.
Join Patch
When a SELECT joins tables and at least one has local mutations:1
Execute on Prod
Run the JOIN as-is on prod. Produces correct results for all non-delta rows.
2
Identify Affected Rows
For each row in the result, check if any primary key from a delta table is in the Delta Map or Tombstone Set. Partition into clean, delta, and dead rows.
3
Discard Tombstoned Rows
Remove rows referencing locally-deleted PKs.
4
Patch Delta Rows
Fetch shadow versions of delta rows and replace the delta columns in the joined result.
5
Execute on Shadow
Run the JOIN on shadow to catch locally-inserted rows that should appear in the result.
6
Merge and Deduplicate
Combine patched prod results with shadow results. Deduplicate by composite key (all joined table PKs), then re-apply ORDER BY and LIMIT.
Aggregates
Aggregate queries (COUNT, SUM, AVG, MIN, MAX with GROUP BY) can’t be merged by simply combining prod and shadow results — aggregation must be re-applied on the combined data. Approach:- Build a row-level base query (strip aggregates, keep GROUP BY columns and source columns)
- Run the base query through the merged read pipeline to get all rows
- Materialize into a temp table on shadow
- Re-aggregate in the proxy (in-memory grouping + function application)
- Apply HAVING filters on re-aggregated results
Firestore aggregations (COUNT, SUM, AVG only) decompose to a base RunQuery through the merged pipeline, then re-compute aggregations in memory. No temp tables — Firestore has no SQL execution engine.
Complex Reads
CTEs (WITH clause):- Non-recursive CTEs: Materialize dirty CTEs independently into temp tables
- Recursive CTEs: Materialize dirty base tables, rewrite query to reference temp tables
- Recursively rewrite subqueries, replacing dirty subquery results with temp tables
- Materialize referenced dirty base tables (not the LATERAL body itself)
- Preserve LATERAL semantics through table materialization
Set Operations
UNION, INTERSECT, EXCEPT are decomposed into leaf SELECT nodes. Each leaf runs through the merged read pipeline independently, then in-memory set operations are applied:- UNION: Combine + deduplicate
- UNION ALL: Combine without dedup
- INTERSECT: Keep rows in both sets
- EXCEPT: Keep rows only in first set
Window Functions
Window functions require the complete dataset to compute correctly — they can’t be decomposed.- Build a base SELECT (same FROM/WHERE, no window functions)
- Run base through merged read pipeline
- Materialize result into temp table on shadow
- Rewrite original query to read from temp table
- Execute on shadow (leverages the database’s native window implementation)
Cursors
- DECLARE CURSOR: Classifies inner query; materializes if affected tables
- FETCH: Reads from cursor on the appropriate backend
- CLOSE: Closes cursor on both backends
Write Operations
All writes execute on shadow. Production is never modified.INSERT
Simple INSERT (Shadow Write):- Enforce FK constraints (validate parent rows exist)
- Execute INSERT on shadow
- Capture RETURNING clause to extract inserted PKs for precise delta tracking
- Add all inserted PKs to the Delta Map
- Persist state
- Detect conflict key columns
- Check prod for rows matching conflict keys
- Hydrate matching prod rows into shadow
- Execute upsert on shadow
- Track all affected rows (hydrated + newly inserted) in Delta Map
RETURNING. INSERT PK extraction uses a 3-tier approach: (1) extract from VALUES clause, (2) use LAST_INSERT_ID(), (3) fall back to information_schema PK lookup.
MySQL-specific: INSERT ... ON DUPLICATE KEY UPDATE and REPLACE INTO route through the hydrate-and-write path.
UPDATE
Point Update (PK extractable from WHERE, e.g.,WHERE id = 42):
- Enforce FK constraints on SET values
- Check if row exists in shadow — if not, hydrate from prod
- Execute UPDATE on shadow
- Add affected PK to Delta Map
WHERE status = 'pending'):
- Query prod for all matching rows (with schema rewriting for prod compatibility)
- Hydrate all matching rows into shadow
- Rewrite UPDATE WHERE clause with concrete PK values
- Execute on shadow
- Track all affected PKs in Delta Map
- Extract subquery predicates from FROM clause
- Hydrate referenced tables before applying the update
Redis writes: SET/MSET route to shadow directly (Shadow Write). Commands that modify existing keys (INCR, HSET, LPUSH, SADD, ZADD, etc.) use DUMP/RESTORE to hydrate the key from prod to shadow before executing (Hydrate and Write).
Firestore writes: CreateDocument routes to shadow directly. UpdateDocument hydrates the document from prod first. Commit and BatchWrite hydrate per-write. Document transforms (serverTimestamp, increment, arrayUnion, arrayRemove) are tracked as deltas.
DELETE
Point Delete (PK extractable):- Enforce FK RESTRICT/NO ACTION constraints (reject if child rows exist)
- Execute DELETE on shadow
- Add tombstone for each affected PK
- Enforce FK CASCADE (recursively tombstone/delete child rows)
- Return correct row count (including prod-only rows)
- Pre-query prod for matching PKs
- Compute total count: shadow actual deletes + prod-only matches
- Tombstone all matching PKs
- Execute DELETE on shadow
TRUNCATE
- Execute on shadow
- Mark table as fully shadowed in Schema Registry — all subsequent reads skip prod entirely
- Clear all tombstones for the table
- CASCADE: recursively mark FK-related tables as fully shadowed
Redis: FLUSHDB/FLUSHALL mark the entire database as fully shadowed. All subsequent reads go to shadow only.
DDL Handling
DDL operations execute on shadow only. Production schema is never modified.- Strip FK constraints from DDL before shadow execution (the proxy enforces FKs instead)
- Extract and store FK metadata in Schema Registry
- Execute modified DDL on shadow
- Update Schema Registry with the structural change
Redis and Firestore have no DDL. Redis is schemaless (key-value); Firestore is schemaless (document). The Schema Registry is unused for these engines except for the
IsFullyShadowed flag (set by FLUSHDB in Redis).Transaction Management
Lifecycle
Prod uses REPEATABLE READ isolation to ensure consistent reads across the transaction — the merged read always sees a stable prod snapshot.
Engine-Specific Transaction Behavior
Staged Deltas
Within a transaction, all delta/tombstone additions are staged — held in a per-transaction buffer. This prevents phantom deltas from failed transactions: if a transaction rolls back, the Delta Map and Tombstone Set remain unchanged. Autocommit mode (implicit single-statement transactions) applies deltas immediately — no staging needed.Savepoints
Savepoints are fully supported (SQL engines) with stack-based snapshot management:- SAVEPOINT name: Capture current delta/tombstone/schema/insert-count state, push onto stack
- RELEASE SAVEPOINT name: Pop snapshot from stack (changes persist)
- ROLLBACK TO SAVEPOINT name: Restore state from snapshot, keep savepoint on stack for re-use
Foreign Key Enforcement
Why FKs are Stripped
Shadow starts empty. INSERTs referencing prod rows via foreign key would fail because the referenced row doesn’t exist in shadow. The proxy enforces referential integrity instead.Parent Row Validation (INSERT/UPDATE)
Validation order (fast-path first):- Delta map check: If parent PK in delta → exists in shadow
- Tombstone check: If parent PK tombstoned → REJECT
- Shadow query: Check shadow for parent row
- Prod query: Fall back to prod if not found in shadow
Referential Action Enforcement (DELETE)
- RESTRICT / NO ACTION: Reject parent delete if child rows exist (checks shadow + prod)
- CASCADE: Tombstone child rows when parent deleted, recursively cascade
- SET NULL / SET DEFAULT: Falls through to shadow enforcement
Redis and Firestore have no foreign key concept. FK enforcement is SQL-engine-specific.

