0%
Database Internals: Build the Engine
Storage Structures
Durability
Concurrency Control
Access Paths
Distribution
Parser, Binder and Rewriter
Everything an engine does with a query begins with turning a string into a structure, and the boundaries of what that stage can and cannot know explain a surprising number of error messages.
What parsing produces
A tree whose shape mirrors the grammar of the language, with a node for the select list, one for the source, one for the condition and so on, carrying no knowledge of what any name refers to.
Starting with the output rather than the process is the right order. The parse tree is a faithful record of what was written and contains nothing about the database, which is the boundary that matters.
Why the stage is split in two
Breaking text into tokens and then assembling tokens into a tree are different problems with different tools, so engines keep them separate even though the user sees one step.
Naming the split is worth doing because errors from the two halves read differently. A complaint about an unterminated string comes from the first and a complaint about unexpected syntax from the second.
Why the language is awkward to parse
Many words are reserved only in some positions, the same construct can appear in several places with different meanings, and the grammar has grown by accretion over decades.
Explaining the awkwardness matters because it produces visible symptoms. A column named after a word the grammar treats specially will fail in one query and work in another, which looks arbitrary without this background.
Why quoting an identifier changes its treatment
Quoting tells the parser that the token is a name and not a keyword, which removes the ambiguity and also makes the name case-sensitive in most engines.
Mentioning the side effect is necessary because it is the usual source of trouble. A quoted name must be quoted identically everywhere afterwards, which is why quoting defensively creates more problems than it solves.
What the parser can detect
Only that the text does not form a valid sentence in the language. Every error it can report is about shape, never about meaning.
Drawing the boundary sharply is the point of the section. A query referring to a table that does not exist is perfectly well formed, so the parser accepts it without complaint.
Why that makes some error messages unhelpful
A missing comma can make the remaining text parse as something else entirely, so the reported position is where the alternative interpretation failed rather than where the mistake is.
Explaining the mechanism makes the experience less mysterious. The parser reports where it ran out of valid continuations, and that can be many tokens past the actual error.
Why the tree is not kept as written
It is normalised immediately, with shorthand expanded, implicit forms made explicit and equivalent spellings collapsed, so later stages see one representation of each construct.
Noting the normalisation is useful because it hides differences people expect to matter. Two queries written differently can produce identical trees and therefore identical plans, which is usually what is wanted.
What normalisation does not do
It does not reorder anything semantic, resolve any name or make any decision that depends on the data, because none of that information is available yet.
Keeping the stage's limits clear is a theme worth repeating. Normalisation is textual tidying, and every transformation that requires knowing about the database belongs to a later stage.
Why the parser must be fast
It runs on every statement, including the millions of trivial ones a transactional application sends, so its cost is paid on the cheapest queries where it is proportionally largest.
Putting the cost in context explains an optimisation covered later. A statement whose execution takes a hundred microseconds cannot afford a millisecond of parsing, which is the entire motivation for caching.
Why comments and whitespace are discarded
They carry no meaning for execution, so the lexer drops them, which is why a hint written as a comment is invisible unless the engine deliberately preserves and interprets it.
Covering this is worthwhile because hint syntax confuses people. An engine that reads hints from comments is doing something unusual on purpose, and one that does not will silently ignore them.
Why statement separation is its own problem
Deciding where one statement ends is not always obvious when strings, comments and nested constructs can contain the separator, and getting it wrong changes what is executed.
Mentioning this is justified because it has security consequences. Any tool that splits statements by searching for a separator without parsing is making a guess that can be manipulated.
Why the parse tree is a poor representation for planning
It mirrors the syntax, so the same logical operation appears in several shapes depending on how it was written, which makes reasoning about it awkward.
Explaining the mismatch motivates the later conversion. Planning works on a representation organised around operations rather than around grammar, and producing it is part of what the next stages do.
Why some engines parse twice
A quick pass can determine whether a statement matches something already planned, letting the engine skip the full parse entirely for a repeated query.
Mentioning the optimisation here is natural because it sits at this boundary. The check has to be cheap enough to be worth doing on every statement, which limits how clever it can be.
Why the text itself is worth keeping
Diagnostics, logs and plan caches all need to refer back to what was written, so engines retain the original string alongside the tree.
Noting the retention is useful because it appears in operational contexts. A slow query log entry is the retained text, which is why it shows literals and formatting exactly as the application sent them.
Why parameter placeholders are handled here
A placeholder is a token the parser recognises and records a position for, which is what allows the same tree to be reused with different values later.
Introducing placeholders at the parsing stage is where they belong. The value is never part of the tree, which is the structural reason parameterisation is immune to injection rather than merely resistant to it.
Why that immunity is structural
A value supplied as a parameter can never become part of the tree, so no content of a value can change the shape of the statement, regardless of what the value contains.
Making the argument explicitly is worthwhile because it is often stated as a best practice rather than as a consequence. The guarantee comes from when the value arrives, which is after the structure is fixed.
Why string concatenation has no such guarantee
A value spliced into text is parsed, so its contents participate in deciding the structure, and any escaping scheme is an attempt to make that participation harmless.
Stating the contrast completes the argument. Escaping can be done correctly and is a defence that has to keep working, while parameterisation removes the possibility rather than defending against it.
Why the stage is worth understanding at all
It determines which class every error belongs to, which is the first step in diagnosing any query that will not run.
Closing with the practical payoff is right. A syntax complaint means the text is malformed and nothing about the database matters, and any other complaint means parsing succeeded and the problem lies further in.
What to take from parsing
Three things. Parsing produces a tree mirroring the grammar, it knows nothing about what any name refers to, and the only errors it can report are about shape rather than meaning. The language is awkward to parse because words are reserved positionally and the grammar grew by accretion, which is why a column name can fail in one query and work in another. And a parameter placeholder is recorded as a position rather than a value, so a parameter can never become part of the tree, which is the structural reason parameterisation is immune to injection rather than merely resistant.
Parameterisation is immune to injection rather than resistant to it, and the reason is a matter of timing. The structure of the statement is fixed when the text is parsed, and a parameter value arrives afterwards, so nothing a value contains can change the shape of what runs. Escaping, by contrast, splices the value into the text and then tries to make its participation in parsing harmless, which is a defence that has to keep being correct.