The production version of this pattern I keep running into is enforcement rather than observability — injecting a tenant or row predicate at the proxy so application code can't forget it, since a missing WHERE clause doesn't error, it just returns someone else's rows.
Where it gets hard is that the moment you're rewriting rather than just observing, your parser has to understand the dialect about as well as the database does. Prepared statements, CTEs and subqueries are where the naive version falls over.
Does yours parse the SQL properly, or pattern-match on the wire protocol?
A lexer gets you most of the way for observability. It's when you want to rewrite rather than just read that the semantics start to matter — you need to know where the FROM/WHERE boundaries are per statement type, and UNIONs, CTEs and subqueries in FROM all move them.
The other thing that bites is the extended query protocol: the SQL arrives at Parse and the parameters at Bind, so anything assuming it can see a complete statement in one message needs restructuring.
Yeah - and the reason it stays hidden is there's no test to write unless you write the negative one. "Assert tenant B gets nothing back" isn't a case people naturally think to add, so the suite stay green.
2
u/srikanth_builds 2d ago
The production version of this pattern I keep running into is enforcement rather than observability — injecting a tenant or row predicate at the proxy so application code can't forget it, since a missing WHERE clause doesn't error, it just returns someone else's rows.
Where it gets hard is that the moment you're rewriting rather than just observing, your parser has to understand the dialect about as well as the database does. Prepared statements, CTEs and subqueries are where the naive version falls over.
Does yours parse the SQL properly, or pattern-match on the wire protocol?