Documentation
¶
Overview ¶
Package sql provides SQL parsing using a custom recursive descent parser.
Index ¶
- Constants
- func AppendRowLimit(sql string, info *SelectInfo, n int) string
- func CheckSingleStatement(sql string) error
- func CoerceBooleanLiterals(info *SelectInfo) error
- func ExprIdentity(n Node) string
- func ExprIdentityUnqualified(n Node) string
- func FoldIdent(s string) string
- func GroupKeyName(n Node) string
- func HasTopLevelReturning(sql string) bool
- func HasTopLevelWhereToken(sql string) bool
- func IsAggregate(name string) bool
- func LeadingKeyword(sql string) string
- func NormalizeIdentRef(s string) string
- func OuterColumnCandidates(subquerySQL string) []string
- func OutputColumnName(col SelectColumn) string
- func ParseBoolText(s string) (bool, bool)
- func QuoteIdent(name string) string
- func RebuildSQL(info *SelectInfo, rewrittenWhere Node) string
- func RefuseReservedSlotName(name, where string) error
- func RefuseReservedSlotNames(names []string, where string) error
- func RefuseUnsupportedStatement(pq *ParsedQuery) error
- func ReservedSlotFamily(name string) string
- func RevertGroupByAliasesShadowedByInput(info *SelectInfo, provides func(bare string) bool)
- func SetAlertIntervalFloorForTest(d time.Duration) func()
- func SlotName(family SlotFamily, n int) string
- func SplitIdentRef(s string) (qualifier, name string, ok bool)
- func SplitStatements(sql string) []string
- func WindowOutputName(col SelectColumn) string
- func WithRowLimit(sql string, n int) string
- type AlterAlertInfo
- type AlterTableInfo
- type AnalyzeTableInfo
- type AndNode
- type AnyAllExpr
- type ArrayLitNode
- type BetweenExpr
- type BinaryOp
- type CTEDef
- type CaseNode
- type CastNode
- type CmpExpr
- type ColRef
- type ColumnDef
- type CreateAlertInfo
- type CreateFunctionInfo
- type CreateSnapshotInfo
- type CreateTableInfo
- type CreateViewInfo
- type DMLTarget
- type DeleteInfo
- type DescribeInfo
- type DropAlertInfo
- type DropFunctionInfo
- type DropTableInfo
- type DropViewInfo
- type ExistsNode
- type ExplainInfo
- type FrameBound
- type FrameBoundType
- type FrameMode
- type FuncCallNode
- type InExpr
- type InsertInfo
- type IntervalLit
- type IsExpr
- type JoinInfo
- type LikeExpr
- type Lit
- type LiteralKind
- type LiteralPlaceholder
- type MergeInfo
- type MergeWhenClause
- type Node
- func ParseExpression(sql string) (Node, error)
- func ParseExpressionComplete(sql string) (Node, error)
- func ReplaceAggregate(node Node, aggName string) Node
- func ReplaceAllAggregates(node Node, replacements map[string]string) Node
- func ReplaceGroupKeyRefs(node Node, keys map[string]string) Node
- func ReplaceWindowFuncs(node Node, replacements map[*WindowFuncNode]string) Node
- func RewriteExpr(node Node, fn func(Node) (Node, bool)) Node
- func RewriteOuterRefs(node Node, outerTables map[string]bool, vals map[string]any) Node
- func RewriteUnqualifiedOuterRefs(node Node, unqualOuter map[string]string, vals map[string]any) Node
- func Unparen(n Node) Node
- type NotNode
- type OrNode
- type OrderByItem
- type OuterRef
- func DanglingTableRefs(subquerySQL string) []OuterRef
- func FindCorrelatedRefs(subquerySQL string, outerTables map[string]bool) ([]OuterRef, error)
- func FindCorrelatedRefsWithColumns(subquerySQL string, outerTables map[string]bool, outerCols map[string]string) ([]OuterRef, error)
- func FindCorrelatedRefsWithScope(subquerySQL string, outerTables map[string]bool, outerCols map[string]string, ...) ([]OuterRef, error)
- type ParenNode
- type ParsedQuery
- type QueryType
- type SelectColumn
- type SelectInfo
- type SetClause
- type SetOp
- type SlotAllocator
- type SlotFamily
- type StarNode
- type SubqueryNode
- type TableColumns
- type TableRef
- type TokenType
- type TupleNode
- type UnaryOp
- type UnionInfo
- type UpdateInfo
- type WhenClause
- type WindowFrame
- type WindowFuncNode
- type WindowOrderBy
- type WindowOrderItem
- type WindowSpec
Constants ¶
const UnnamedOutputColumn = "?column?"
UnnamedOutputColumn is the name PostgreSQL gives an output column that has no natural one: an operator expression, a literal, a predicate, a scalar subquery over an unnamed item. It is spelled exactly as PostgreSQL spells it, question marks included, because it is what a client reads out of RowDescription and what a query has to quote to refer to the column.
Variables ¶
This section is empty.
Functions ¶
func AppendRowLimit ¶ added in v0.18.33
func AppendRowLimit(sql string, info *SelectInfo, n int) string
AppendRowLimit returns sql bounded to at most n rows, when a LIMIT can be appended to it without changing what it means.
It exists because two of the four subquery constructs do not want a SET. EXISTS asks whether there is A row and a scalar subquery is an ERROR past ONE, so reading the whole result to answer either is work nobody asked for — and on the DML door it was a WRONG REFUSAL: that door bounds what a subquery may return (54000, `WADJET_IN_SET_MAX`), so `DELETE FROM t WHERE EXISTS (SELECT 1 FROM big)` refused at the ten thousandth row of a question one row answers, and a scalar subquery past the bound reported 54000 where this engine's own rule is 21000. Bounding the READ puts each construct back inside the rule it is judged by: IN keeps the bound because IN really does need the set.
The append is declined — sql is returned unchanged — when the query already bounds itself with LIMIT or OFFSET, or is a UNION, because in those spellings a trailing LIMIT is not an append: it is either a syntax error or a second, differently-scoped bound. Those keep whatever behaviour they had.
info must be the parse of sql. Callers that hold one (the correlated evaluators parse once at compile time and rebuild per row) pass it; callers that hold only text use WithRowLimit.
func CheckSingleStatement ¶ added in v0.18.23
CheckSingleStatement refuses a string that carries more than one statement, the way PostgreSQL refuses one at an entry point that answers with a single result.
THE ORDER IS PART OF THE ANSWER, and it is measured against 17.11 rather than remembered. PostgreSQL parses the whole string first, so
INSERT INTO t (id) VALUES (1); INSERT INTO t (id) VALUES (2)
-> 42601 cannot insert multiple commands into a prepared statement
INSERT INTO t (id) VALUES (1); ZZZ NOT SQL
-> 42601 syntax error at or near "ZZZ"
A syntax error anywhere in the string outranks the multi-command refusal, and reporting the multi-command message for the second one would tell a client its SQL was fine when it was not. Both carry 42601.
A single statement costs nothing here: the string is not parsed twice, because the caller is about to parse it anyway.
func CoerceBooleanLiterals ¶ added in v0.18.30
func CoerceBooleanLiterals(info *SelectInfo) error
CoerceBooleanLiterals resolves an UNKNOWN-typed literal used as a truth value, which is the one shape a boolean context accepts that is not already a boolean (#599).
PostgreSQL types a bare quoted literal from the context it meets, so a boolean context runs it through the boolean input function: measured live on postgres:17-alpine, `SELECT 1 WHERE 'true'`, `'yes'`, `'t'` and `' 1 '` all SUCCEED, `WHERE NULL` succeeds and returns nothing, and `WHERE 'abc'` is 22P02 `invalid input syntax for type boolean: "abc"` — NOT the 42804 a typed non-boolean gets.
Wadjet answered 0 rows for all four, because nothing typed the literal at all: `expr.FilterPredicate`'s generic arm takes a failed `v.(bool)` assertion for FALSE. So this runs at parse time, where the literal can simply BECOME the boolean it names.
The grammar is PostgreSQL's `parse_bool_with_len`, the same one `CAST(<string> AS BOOLEAN)` already follows (ADR-0012): case-insensitive, C whitespace trimmed, any non-empty PREFIX of "true"/"false"/"yes"/"no", plus "on"/"off" and the single characters "1" and "0". `'tr'` and `'fals'` ARE values; `'o'` alone is not, because it cannot choose between "on" and "off".
func ExprIdentity ¶ added in v0.18.6
ExprIdentity renders an expression in the canonical form that answers "are these the same expression?".
Three spelling differences are erased, and only these three:
- PARENTHESES. `(g + 1)`, `((g) + 1)` and `g + 1` are one identity. The parse tree already records the grouping, so a ParenNode carries no information the tree does not.
- IDENTIFIER CASE. `G + 1` and `g + 1` are one identity, the way PostgreSQL folds an unquoted identifier.
- WHITESPACE. `g+1` and `g + 1` are one identity, which the AST rendering already gave.
Nothing else is erased. Associativity in particular is NOT: `g - 1 - 2` is `(g - 1) - 2` and `g - (1 - 2)` is itself, and the two identities differ, because the two expressions differ. That is why the rendering is fully parenthesised at every infix node rather than relying on String()'s precedence-free output — dropping a ParenNode and printing `a * b + c` for `a * (b + c)` would make two DIFFERENT expressions one identity, which is the wrong answer in the more dangerous direction.
func ExprIdentityUnqualified ¶ added in v0.18.21
ExprIdentityUnqualified is ExprIdentity with TABLE QUALIFIERS erased as well, so `typemx.g + 1` and `g + 1` are one identity (#738).
It is a SEPARATE function and not a fourth rule in ExprIdentity, because erasing a qualifier needs a SCOPE that this file does not have: `a.x` and `b.x` over a join are two expressions and `t.x` and `x` in a single-relation block are one. Only a caller holding the block's FROM list can tell them apart, and exactly one does — physical.groupCheck, which uses this when the block has ONE source and ExprIdentity when it has more.
Erasing it unconditionally would make two different expressions one identity, which is the failure this file's header calls "the wrong answer in the more dangerous direction".
func FoldIdent ¶ added in v0.18.30
FoldIdent is the case fold PostgreSQL applies to an UNQUOTED identifier: ASCII A-Z becomes a-z and NOTHING else changes.
The ASCII restriction is PostgreSQL's own, measured on postgres:17-alpine with a UTF8 server encoding: `CREATE TABLE t (Ä int)` stores the column as `Ä`, and `SELECT 1 AS Ä` publishes `Ä`. strings.ToLower would fold it to `ä` and invent a name no PostgreSQL client would expect, so the fold is written out byte by byte rather than borrowed from unicode.
func GroupKeyName ¶ added in v0.18.6
GroupKeyName is the column name a GROUP BY term's value is published under by the aggregate, on both execution paths.
A bare column reference keeps its own name with any delimiters stripped, so `GROUP BY "g + 1"` names the column `g + 1` and not the four-token string `"g + 1"` the worker's hash aggregate cannot find in a batch (#725). Anything else — a computed key — is named by its own rendered text with redundant outer parentheses removed, so `GROUP BY (g + 1)` and `GROUP BY g + 1` publish ONE name and a SELECT item spelled either way resolves to it.
Case is PRESERVED: this is a name a batch column is matched against by bytes, so the name must be what the value is actually published under. ExprIdentity, which is only ever compared, is the one that folds case.
func HasTopLevelReturning ¶ added in v0.18.5
HasTopLevelReturning reports whether a DML statement writes a RETURNING clause outside any parentheses.
RETURNING is not a lexer keyword, so every clause that collects raw text swallowed it and then answered differently: bare `DELETE FROM t RETURNING *` took it as the table's ALIAS, `DELETE ... WHERE id = 1 RETURNING *` fed it to the WHERE's complete-parse and called legal SQL a syntax error, and `INSERT ... RETURNING id` dropped it in silence and reported INSERT 1. One check over the whole statement gives all four doors the same answer: it is a legal statement whose feature this server has not implemented, so 0A000 (#686 R2-4).
An unquoted RETURNING is always the clause — PostgreSQL reserves the word, so a column of that name must be double-quoted, and a quoted identifier is not matched here.
func HasTopLevelWhereToken ¶ added in v0.18.5
HasTopLevelWhereToken reports whether sql spells a WHERE keyword outside any parentheses.
It exists for one caller: the backstop in wadjet.BuildDMLPredicate, which has to tell "this DELETE is unconditional because it was written that way" from "this DELETE is unconditional because the parser dropped its WHERE". The two are indistinguishable in DMLTarget.WhereSQL, and the second one empties tables (#686).
The lexer decides, not strings.Contains: a WHERE inside a string literal ('WHERE') or a quoted identifier ("where") is not a clause, and a WHERE belonging to a SUBQUERY (`SET n = (SELECT ... WHERE ...)`) is not this statement's clause either — hence the depth counter.
func IsAggregate ¶
IsAggregate returns true if the function name is a known aggregate.
func LeadingKeyword ¶ added in v0.18.20
LeadingKeyword returns a statement's first keyword, uppercased, with leading whitespace and comments skipped — or "" when the statement has no keyword.
It exists because a TEXT PREFIX is not a classifier. pgwire decided "is this DML" with `strings.HasPrefix(upper, "INSERT ")` — a literal space — so `INSERT\nINTO t …`, `UPDATE\tt SET …` and `/* hint */ UPDATE …` all missed the branch. Multi-line SQL is what every ORM and JDBC prepared statement emits and a `/* … */` prefix is what sqlcommenter prepends, so the miss was the common case, not the corner: those statements fell through to the QUERY path, which meant Describe EXECUTED the write and Execute ran it AGAIN — a duplicated row reported as `SELECT 1` (review B3).
The lexer already knows what a comment and a quoted identifier are, so this asks it rather than re-deciding.
func NormalizeIdentRef ¶
NormalizeIdentRef strips the delimiters from an identifier reference so that it reads as the plain column name the execution layer matches against batch schemas: `"id.orig_h"` → id.orig_h, `"my tbl"."c"` → my tbl.c. Strings that are not a bare identifier reference (expressions, function calls, literals) are returned unchanged.
func OuterColumnCandidates ¶
OuterColumnCandidates returns the column names a subquery may read from the query that ENCLOSES it: every reference it cannot resolve against its own FROM clause. Subqueries nested inside it are walked too, so a correlation two levels down is reported at the top.
It exists for column pruning. A column a correlated subquery reads is a column the outer query NEEDS, even when it appears nowhere in the outer SELECT list or WHERE clause — and the pruning walk had no case for a subquery node at all, so it never saw one. The outer batch then carried no such column, readOuterValues substituted NULL for it, every comparison against that NULL was UNKNOWN, and the query answered 0 rows with no indication anything had gone wrong (issue #347).
It is deliberately over-inclusive where it cannot be sure. An unqualified name is reported even though the subquery's own FROM may well supply it, because deciding that needs a catalog this package does not have. Naming a column the outer relation does not have costs nothing — the caller filters candidates against the scan's own schema (sanitizeScanNeeds) — while missing one it does have is the wrong answer above. A reference qualified by one of the subquery's own tables or aliases is the one case it can rule out, and does.
A subquery that does not parse yields no candidates: the expression compiler parses the same text and declines to build a correlated evaluator for it, and the runtime guard in readOuterValues fails loudly if one is built anyway.
func OutputColumnName ¶ added in v0.18.43
func OutputColumnName(col SelectColumn) string
OutputColumnName is the name PostgreSQL publishes an unaliased SELECT item under — its `FigureColname`, decided from the parsed AST and never from the expression's TEXT.
The whole rule, measured on PostgreSQL 17 over 49 spellings (#732):
g, t.g, (g) → g (the column)
(c_row).b → b (the FIELD)
abs(g), count(*), sum(g) OVER () → abs, count, sum (the function)
CASE …, COALESCE, NULLIF, GREATEST → case, coalesce, …
EXISTS (…) → exists
ARRAY[…] → array
EXTRACT(YEAR FROM d) → extract
CAST(g AS int), g::int → g (the ARGUMENT)
CAST('2020-01-01' AS date) → date (the TYPE, only when the
argument has no name)
(SELECT g FROM … LIMIT 1) → g (the subquery's column)
g + 1, -g, 1, 'abc', g IS NULL,
g = 1, g BETWEEN 1 AND 2,
g IN (1,2), g || 'x', (SELECT 1) → ?column?
A CAST is the one PostgreSQL gets asked about most and the one most often guessed wrong: it is named after its ARGUMENT, and reaches for the type only when the argument itself is unnamed. The brief for arc E3 said "a CAST → the TYPE" and the measurement said otherwise.
Several `?column?` in one SELECT list is legal and is what PostgreSQL does: `SELECT g + 1, g + 2` returns two columns of that name. Output slots have identity by POSITION (#556/#557), so a duplicate published name is not a collision.
It returns "" for a STAR, which has no single name.
func ParseBoolText ¶ added in v0.18.30
ParseBoolText is PostgreSQL's boolean input function, `parse_bool_with_len`. Exported because two doors need the SAME grammar and a second copy is how they drift: a boolean CONTEXT here, and CAST(<string> AS BOOLEAN) in the expression compiler.
func QuoteIdent ¶
QuoteIdent renders an identifier so that re-parsing it yields the same identifier. Names the lexer would otherwise re-read as something else — embedded dots (a flat JSON column such as id.orig_h), spaces, other punctuation, a leading digit, an ASCII UPPER-CASE letter (which the lexer folds, #731), or a keyword spelling — come back double-quoted with any interior quote doubled. Names that already lex as a single unquoted identifier are returned unchanged, so printed SQL for ordinary columns is byte-identical to what it was before delimited identifiers existed.
func RebuildSQL ¶
func RebuildSQL(info *SelectInfo, rewrittenWhere Node) string
RebuildSQL reconstructs a full SELECT SQL string from a SelectInfo, using the provided expression as the WHERE clause instead of the original. This is used by the correlated subquery evaluator to substitute outer values.
func RefuseReservedSlotName ¶ added in v0.18.6
RefuseReservedSlotName returns the 42939 (reserved_name) refusal for a name a user is CREATING that collides with a hidden slot, or nil when it does not. where names the site, so the message says which door saw it.
Call it where a name is MINTED BY THE USER: a SELECT alias, a derived table's or a CTE's output names, and the DDL and ingest doors (CREATE TABLE, the CreateTable API, an Ingester's schema, an INSERT column list that creates a column).
Do NOT call it on a table's STORED columns at read time. Reading is not minting: the column already exists, some binary wrote it, and refusing it makes the table unreadable by every query — including the `SELECT *` that would show the user what is in it — while CREATE TABLE, the Go API and INSERT all still succeeded, so the trap closed behind the user with DROP TABLE the only exit. A stored collision is handled by renumbering the PLANNER's slot instead (renameCollidingSlots), so the two coexist.
func RefuseReservedSlotNames ¶ added in v0.18.6
RefuseReservedSlotNames refuses the first colliding name in names, in sorted order so the message does not depend on map iteration.
func RefuseUnsupportedStatement ¶ added in v0.18.42
func RefuseUnsupportedStatement(pq *ParsedQuery) error
RefuseUnsupportedStatement is the refusal a door owes a parsed statement no handler of that door accepts. It returns nil for a statement that IS a query, so a door calls it once, at the point it dispatches by statement type, immediately after its own handlers have had their turn.
#860: `ALTER TABLE` — and `CREATE VIEW`, `DROP VIEW`, and on the HTTP door the alert and snapshot statements — parse into a ParsedQuery whose type no branch handles, so every door fell through to ExtractSelect and reported `no SELECT info in parsed query`: an internal invariant's wording, with no SQLSTATE on the HTTP door and the blanket 42000 through pgwire. A client cannot branch on that, and the message names nothing it wrote.
The disposition is 0A000 (feature_not_supported), which is already this engine's class for a shape it parses and cannot execute (`RETURNING is not supported`, above), and the message names the STATEMENT. It is raised here rather than inside ExtractSelect because a better message from ExtractSelect would still be produced by planner code, one call site at a time, for a statement no planner should ever have been handed.
func ReservedSlotFamily ¶ added in v0.18.6
ReservedSlotFamily returns the slot prefix name collides with, or "".
The comparison is case-insensitive because column resolution is: a user who writes `__WIN_0` reaches the same slot every consumer looks up.
func RevertGroupByAliasesShadowedByInput ¶ added in v0.18.16
func RevertGroupByAliasesShadowedByInput(info *SelectInfo, provides func(bare string) bool)
RevertGroupByAliasesShadowedByInput applies PostgreSQL's precedence for a bare GROUP BY name: an INPUT COLUMN wins over a SELECT alias.
The parser substitutes such a name with the alias's defining expression unconditionally, and its own doc comment claimed the opposite ("a table column with the same name keeps precedence over the alias") — protocol item 9's exact failure mode, a record describing intended behaviour as present behaviour. There is no precedence check in the parser and there cannot be one: it has no schema and no scope. So the substitution is provisional and this undoes it, called from the layer that knows what the FROM sources provide.
provides reports whether one of this block's own sources carries the bare name. It must answer only where it is CERTAIN: an unenumerable source (a table function, a SELECT *, a table absent from the catalog) has to answer false, which keeps the substitution and the pre-#739 answer.
The wrong-answer shape this closes: `SELECT h AS g, COUNT(*) FROM gcov GROUP BY g, h` grouped by (h, h) and answered 2 rows where PostgreSQL — which groups by (g, h) — answers 6. Both engines answered, and they answered different numbers.
func SetAlertIntervalFloorForTest ¶
SetAlertIntervalFloorForTest lowers the CREATE ALERT interval floor for the duration of a test. Call the returned function (typically with defer) to restore the production floor.
func SlotName ¶ added in v0.18.6
func SlotName(family SlotFamily, n int) string
SlotName is the Nth slot of a family: `SlotName(SlotWindowOutput, 0)` is `__win_0`.
Minting a slot through this rather than through a local fmt.Sprintf is what keeps the reservation and the names in one place — a family whose names are built somewhere else is a family the refusal below does not really cover.
func SplitIdentRef ¶
SplitIdentRef interprets s as an identifier reference and returns its optional qualifier and its name. Quoting is honoured, so the qualifier split happens only at a dot the lexer actually produced:
l_orderkey → ("", "l_orderkey")
o.o_custkey → ("o", "o_custkey")
"id.orig_h" → ("", "id.orig_h") — one delimited name, not qualified
"my tbl"."c" → ("my tbl", "c")
ok is false for anything that is not a bare identifier reference (a function call, an arithmetic expression, a literal, a multi-level path); callers then fall back to their own string handling.
func SplitStatements ¶ added in v0.18.23
SplitStatements splits a SQL string into the statements a client wrote, separated by TOP-LEVEL semicolons.
It exists because PostgreSQL's simple query protocol carries a whole script in one message and runs it as a SEQUENCE, one CommandComplete per statement. Everything here that reached a semicolon before this did `strings.TrimRight(sql, ";")` and nothing else, so a two-statement string was ONE statement to every parser below it — and what happened to the tail then depended on which sub-parser consumed it. `INSERT …; INSERT …` ran the first and silently dropped the second, and `INSERT …; ZZZ NOT SQL` ran the INSERT and silently ignored the garbage, where PostgreSQL runs neither (#711).
THE LEXER DECIDES, not strings.Split. A semicolon is a separator only where it is a semicolon TOKEN at paren depth zero, so
UPDATE t SET name = 'a;b' WHERE id = 1 -- one statement SELECT "a;b" FROM t -- one statement SELECT 1 -- ; not a separator SELECT $$a;b$$ -- one statement SELECT /* ; */ 1 -- one statement
all stay whole: the lexer reads string literals, dollar-quoted strings, double-quoted identifiers and both comment forms, so none of them can emit a TokenSemicolon.
The DEPTH counter is the same guard HasTopLevelWhereToken uses. A semicolon inside parentheses cannot separate statements in any legal SQL, and treating one as a separator would cut a statement in half and report the halves' errors instead of the statement's. When the parens never balance — the input is malformed — depth never returns to zero, nothing is split, and the whole string goes to Parse, which is where a malformed statement's error belongs.
A lex ERROR (an unterminated string literal, say) stops the scan and the unconsumed remainder is returned as one final piece, for the same reason: the error is the statement's, and Parse is what reports it.
A piece with NO STATEMENT IN IT is dropped, and that means whitespace, semicolons AND COMMENTS. `SELECT 1; -- trailing comment` is one statement in PostgreSQL and was one statement here before this function existed; treating the comment as a second statement made that string — and `…; /* banner */`, and a stray `--` from an editor, and the `-- query tag` every ORM appends — a two-statement string that every one-statement door then refused with 42601. Trimming whitespace alone is not enough for the same reason `strings.Split` is not enough one paragraph up: only the lexer knows what a comment is. A piece whose first token is EOF holds nothing to run.
An input with no statement at all yields nil, which callers read as PostgreSQL's EmptyQuery — and PostgreSQL answers a comment-only query string with exactly that.
func WindowOutputName ¶ added in v0.18.6
func WindowOutputName(col SelectColumn) string
WindowOutputName is the name a SELECT-list window column is published under.
The alias where the query gave one, and otherwise the FUNCTION's name, which is what PostgreSQL 17 calls it:
SELECT SUM(a) OVER () FROM t -- PostgreSQL: "sum" SELECT ROW_NUMBER() OVER (ORDER BY id)… -- PostgreSQL: "row_number"
FIVE places name this column and they have to agree: the logical builder's projection (which decides what the operator emits), the embedded API's deriveColumns (the single-process result schema), the binder's blockOutputs (a derived table's namespace), and the two positional-ORDER-BY resolvers. They did not: an unaliased window was `sum(a) OVER (...)` in four of them and the empty string in the fifth, so the projection published nothing, the result schema asked for the text, and `ORDER BY 1` rewrote to a name with parentheses in it that no sort key could resolve.
It lives here rather than in the logical package because the parser cannot import the planner, and the positional resolvers are in the parser.
func WithRowLimit ¶ added in v0.18.33
WithRowLimit is AppendRowLimit for a caller that holds only the subquery text. A statement that does not parse is returned unchanged: this function decides how much to READ, never what is legal, and the compiler and the runner both raise on their own for text they cannot use.
Types ¶
type AlterAlertInfo ¶
AlterAlertInfo holds details for ALTER ALERT ... ENABLE|DISABLE.
type AlterTableInfo ¶
type AlterTableInfo struct {
Table string
Action string // "ADD COLUMN", "DROP COLUMN", "RENAME COLUMN"
ColumnName string
NewColumnName string // for RENAME COLUMN
ColumnType string // for ADD COLUMN
Nullable bool // for ADD COLUMN (default true)
}
AlterTableInfo holds details for an ALTER TABLE statement.
type AnalyzeTableInfo ¶
type AnalyzeTableInfo struct {
Name string
}
AnalyzeTableInfo holds details for an ANALYZE TABLE statement.
type AnyAllExpr ¶
type AnyAllExpr struct {
Left Node
Op string // =, !=, <, <=, >, >=
Modifier string // "ANY", "ALL", "SOME"
Values []Node // value list or single SubqueryNode
}
AnyAllExpr is expr op ANY/ALL/SOME (subquery or values).
func (*AnyAllExpr) String ¶
func (a *AnyAllExpr) String() string
type ArrayLitNode ¶
type ArrayLitNode struct {
Elements []Node
}
ArrayLitNode is ARRAY[expr, expr, ...].
func (*ArrayLitNode) String ¶
func (a *ArrayLitNode) String() string
type BetweenExpr ¶
BetweenExpr is expr [NOT] BETWEEN low AND high.
func (*BetweenExpr) String ¶
func (b *BetweenExpr) String() string
type CTEDef ¶
type CTEDef struct {
Name string // CTE name (lowercased for matching)
SQL string // the CTE body SQL (the SELECT inside the parentheses)
Columns []string // optional column name list
Recursive bool // WITH RECURSIVE
// contains filtered or unexported fields
}
CTEDef represents a Common Table Expression definition.
func (*CTEDef) BodySelect ¶ added in v0.18.35
func (c *CTEDef) BodySelect() (*SelectInfo, error)
BodySelect returns the parsed SELECT body of a CTE definition, memoized on the definition, for the same reason SubSelect memoizes a derived table's.
type CaseNode ¶
type CaseNode struct {
Subject Node // nil for searched CASE
Whens []WhenClause // at least one
Else Node // nil if no ELSE
}
CaseNode is a CASE expression.
type ColRef ¶
type ColRef struct {
Table string
Column string
// Slot marks a reference the PLANNER planted at a hidden slot, as
// opposed to one the user wrote. Nothing a query can contain sets it:
// the parser never does, and re-parsing a rendered expression loses it,
// because it is provenance and not spelling.
//
// It exists because the two are otherwise indistinguishable. A table may
// legitimately store a column called `__win_0` (ADR-0025 rule 1: reading
// such a table is never refused), and then `SUM(plain) OVER () + 0` has
// a `__win_0` reference the nested-window rewrite planted BESIDE a
// `__win_0` the scan really emits. Moving the planner's slot past the
// stored column has to move the first and must not touch the second
// (#750, #694).
Slot bool
}
ColRef is a column reference, optionally qualified (table.column).
func ColumnRefs ¶ added in v0.18.5
ColumnRefs returns every column reference in an expression tree.
It exists for the callers that must resolve names against a schema BEFORE executing — the DML doors, which do not go through the planner and so never had a name-resolution step at all. `UPDATE t SET n = 1 WHERE nosuchcol = 1` answered "UPDATE 0" because the reference evaluated to NULL on every row, where PostgreSQL raises 42703 (#678).
A node type it does not know is an ERROR, never a silent skip. That is the whole reason it lives beside the AST it walks: a node added to ast.go without a case here fails loudly at the one call site that depends on completeness, instead of quietly letting an unresolvable column through. The three nodes that carry RAW SQL rather than a parsed subtree (SubqueryNode, ExistsNode, WindowFuncNode) are refused for the same reason — their columns are not visible from here.
func ColumnRefsOutsideSubqueries ¶ added in v0.18.33
ColumnRefsOutsideSubqueries is ColumnRefs with SubqueryNode and ExistsNode treated as OPAQUE leaves rather than refused.
A DML predicate containing a subquery is compiled with a real subquery runner and an outer scope (#688), so the names INSIDE the subquery are resolved by the subquery's own planning and the names outside it are this door's to check. Refusing the whole tree because one operand is a subquery is what made `DELETE FROM t WHERE id IN (SELECT id FROM s)` a 0A000.
WindowFuncNode stays refused: a window function is not legal in a WHERE clause on any door, and letting one through here would only move the failure later.
type ColumnDef ¶
type ColumnDef struct {
Name string
Type string
Nullable bool // true by default; NOT NULL sets it to false
}
ColumnDef defines a column in a CREATE TABLE statement.
type CreateAlertInfo ¶
type CreateAlertInfo struct {
Name string
QueryText string // raw SELECT text, re-parsed at eval time
Interval time.Duration // validated >= 10s at parse time
WebhookURL string // "" if no webhook sink
Headers map[string]string
InsertInto string // "" if no table sink; at least one sink required
}
CreateAlertInfo holds details for a CREATE ALERT statement.
type CreateFunctionInfo ¶
type CreateFunctionInfo struct {
Name string
Params []string
Body string
Replace bool // CREATE OR REPLACE
Locked bool // WITH LOCK
}
CreateFunctionInfo holds details for a CREATE FUNCTION statement.
type CreateSnapshotInfo ¶
type CreateSnapshotInfo struct{}
CreateSnapshotInfo is the AST for a CREATE SNAPSHOT statement. Empty in v1 — statement takes no arguments.
type CreateTableInfo ¶
CreateTableInfo holds details for a CREATE TABLE statement.
type CreateViewInfo ¶
type CreateViewInfo struct {
Name string
SQL string // the view definition SQL
Replace bool // CREATE OR REPLACE VIEW
}
CreateViewInfo holds details for a CREATE VIEW statement.
type DMLTarget ¶ added in v0.18.5
type DMLTarget struct {
Table string // table name, as the statement spelled it
Qualifier string // schema/catalog qualifier ("public"), "" when unqualified
// Alias is the `[AS] a` the statement gave, "" when it gave none. When it
// is set it HIDES the table name: PostgreSQL answers `DELETE FROM pr AS a
// WHERE pr.id = 1` with 42P01, not with a delete.
Alias string
// WhereSQL is the raw WHERE clause. "" means the statement had NO WHERE
// at all — which is a legal unconditional statement, and is why StmtSQL
// is carried beside it: a clause the parser dropped looks identical here.
WhereSQL string
// StmtSQL is the whole statement, trimmed. The backstop re-lexes it to
// ask whether a WHERE keyword the parsed clause does not account for was
// written.
StmtSQL string
}
DMLTarget is the relation a DELETE or an UPDATE names, together with the statement text that named it.
It is one type shared by both because both doors (the embedded API and the HTTP server) resolve the two identically, and because the STATEMENT TEXT has to travel with the clause for the empty-predicate backstop in wadjet.BuildDMLPredicate to have anything to check (#686).
type DeleteInfo ¶
type DeleteInfo struct {
DMLTarget
}
DeleteInfo holds details for a DELETE statement.
type DescribeInfo ¶
type DescribeInfo struct {
TableName string
}
DescribeInfo holds details for a DESCRIBE/SHOW COLUMNS statement.
type DropAlertInfo ¶
DropAlertInfo holds details for a DROP ALERT statement.
type DropFunctionInfo ¶
DropFunctionInfo holds details for a DROP FUNCTION statement.
type DropTableInfo ¶
DropTableInfo holds details for a DROP TABLE statement.
type DropViewInfo ¶
DropViewInfo holds details for a DROP VIEW statement.
type ExistsNode ¶
ExistsNode is [NOT] EXISTS (SELECT ...).
func (*ExistsNode) String ¶
func (e *ExistsNode) String() string
type ExplainInfo ¶
type ExplainInfo struct {
Verbose bool
Analyze bool
InnerSQL string
// InnerType is the statement type EXPLAIN was asked about. EXPLAIN
// carries the inner statement's SelectInfo and nothing else, so without
// this a door cannot say WHICH statement it cannot explain.
InnerType QueryType
}
ExplainInfo holds details for an EXPLAIN statement.
type FrameBound ¶
type FrameBound struct {
Type FrameBoundType
Offset Node // nil for UNBOUNDED/CURRENT ROW
}
FrameBound describes one end of a window frame.
type FrameBoundType ¶
type FrameBoundType int
FrameBoundType identifies the type of a frame bound.
const ( BoundUnboundedPreceding FrameBoundType = iota BoundPreceding BoundCurrentRow BoundFollowing BoundUnboundedFollowing )
type FuncCallNode ¶
type FuncCallNode struct {
Name string
Args []Node
Distinct bool // COUNT(DISTINCT col)
Star bool // COUNT(*)
// OutputLabel is the name PostgreSQL publishes this call under when the
// SELECT list wrote no alias, for the calls the parser REWRITES into a
// different function: `EXTRACT(YEAR FROM d)` becomes `year(d)` here and
// PostgreSQL still labels the column `extract`. Empty means Name is the
// label, which is the ordinary case (OutputColumnName, #732).
OutputLabel string
}
FuncCallNode is a function call expression.
func FindAllAggregates ¶
func FindAllAggregates(node Node) []*FuncCallNode
FindAllAggregates walks an expression tree and returns all aggregate function calls found. For multi-aggregate expressions like MAX(x) - MIN(x), this returns both aggregates.
func FindNestedAggregate ¶
func FindNestedAggregate(node Node) *FuncCallNode
FindNestedAggregate walks an expression tree and returns the first aggregate function call found, or nil if none exists. This detects aggregates nested inside binary expressions like SUM(x) * 0.0001.
func (*FuncCallNode) String ¶
func (f *FuncCallNode) String() string
type InsertInfo ¶
type InsertInfo struct {
Table string // table name
Columns []string // target column names (empty = all columns)
Values [][]string // rows of value expressions
}
InsertInfo holds details for an INSERT statement.
type IntervalLit ¶
type IntervalLit struct {
Value int
Unit string // "day", "month", "year", "hour", "minute", "second"
}
IntervalLit represents INTERVAL 'N' DAY or INTERVAL 'N days' expressions.
func (*IntervalLit) String ¶
func (i *IntervalLit) String() string
type JoinInfo ¶
type JoinInfo struct {
Type string // join, left join, right join, full outer join, cross join
LeftTable string
RightTable string
RightAlias string
RightTableRef *TableRef // full right-side table ref (includes function info)
Condition string
CondExpr Node
// Using is the column list of a `JOIN ... USING (a, b)`, lower-cased, in
// the order written. The join CONDITION is desugared into Condition /
// CondExpr at parse time (`<left>.a = <right>.a AND ...`), because that
// half needs no catalog; the list is kept because the OUTPUT half does —
// USING merges the joined column into ONE output column under `SELECT *`,
// and deciding which columns a star stands for is a catalog question
// (#655).
Using []string
Lateral bool // LATERAL join — right side can reference left side columns
// FromItem is the index into SelectInfo.Tables of the comma-separated
// FROM item this join EXTENDS. A FROM list is a list of items and an
// explicit JOIN belongs to the item it follows, but the parser flattens
// items into Tables and joins into Joins, which loses that association:
// `FROM a JOIN b ON …, c` and `FROM a, b JOIN c ON …` produce two-entry
// Tables and a one-entry Joins that are otherwise indistinguishable.
// The builder needs it to attach each join to the right item — folding
// the comma tables in first instead planned the former as `(a × c) ⋈ b`,
// which buries a real cross product under the equi-join (#593) and
// leaves the WHERE equality straddling that join's two sides, where the
// key pair resolves to nothing and the query answers zero rows (#594).
// Non-decreasing across Joins, since Tables only grows as parsing
// advances. Zero for a hand-built JoinInfo, which attaches it to the
// first item — the same tree the single-item case has always produced.
FromItem int
}
JoinInfo describes a JOIN clause.
type Lit ¶
type Lit struct {
Value string
Kind LiteralKind
}
Lit is a literal value (string, number, bool, null).
type LiteralKind ¶
type LiteralKind int
LiteralKind identifies the kind of literal value.
const ( LitString LiteralKind = iota LitNumber LitBool LitNull )
type LiteralPlaceholder ¶
type LiteralPlaceholder struct {
Name string
}
LiteralPlaceholder marks a deferred literal whose value is computed at stage-dispatch time from the output of a prerequisite stage. The physical planner inserts these when a filter expression's subquery references a CTE whose distributed-pipeline output would diverge from single-process evaluation; the native-DAG coordinator rewrites the serialized filter expression by string-replacing ":<Name>" with the concrete literal before dispatching the task. String renders as ":<Name>" so any code path that accidentally serializes the expression before substitution produces an unambiguous syntax error instead of silently coercing.
func (*LiteralPlaceholder) String ¶
func (l *LiteralPlaceholder) String() string
type MergeInfo ¶
type MergeInfo struct {
Target string // target table name
TargetQualifier string // schema/catalog qualifier, "" when unqualified
TargetAlias string
Source string // source table/subquery
SourceAlias string
OnCondition string // MERGE ON condition
WhenClauses []MergeWhenClause // WHEN MATCHED / NOT MATCHED clauses
}
MergeInfo holds details for a MERGE statement.
type MergeWhenClause ¶
type MergeWhenClause struct {
Matched bool // true = WHEN MATCHED, false = WHEN NOT MATCHED
Condition string // optional AND condition
Action string // "UPDATE", "DELETE", "INSERT"
SQL string // raw SET/VALUES clause
}
MergeWhenClause represents a WHEN clause in a MERGE statement.
type Node ¶
type Node interface {
String() string
// contains filtered or unexported methods
}
Node is the base interface for all SQL expression AST nodes. Every concrete node type must implement nodeTag (a marker method) and String.
func ParseExpression ¶
ParseExpression parses a single expression from a SQL string. Used for standalone expression parsing (e.g., UDF bodies, WHERE clauses).
It stops where the expression grammar stops and does NOT require that the whole string was consumed, so a caller holding text that must be an expression IN FULL has to use ParseExpressionComplete instead.
func ParseExpressionComplete ¶ added in v0.18.5
ParseExpressionComplete parses one expression and refuses text left over after it.
ParseExpression stops at the first token the expression grammar cannot use and reports success, which for a DML WHERE clause is not a truncated expression but silent data loss: `id > 0 AND name @@ 'zzz'` parses to `id > 0`, and a DELETE carrying it removes every row the SURVIVING PREFIX matches. The conjunct that was dropped is the one that would have NARROWED it. Three spellings found this way — an unsupported operator (@@, #), PostgreSQL's ISNULL suffix, and a stray token after a parenthesised term — each emptied a table the full predicate matched in part or not at all (#686 review).
A WHERE this server cannot read in full is a WHERE whose meaning it does not know, and running the part it did read is the worst available answer (ADR-0019, correctness-fix protocol item 8).
func ReplaceAggregate ¶
ReplaceAggregate replaces the first aggregate function call in the expression tree with a ColRef pointing to the aggregate output column name.
func ReplaceAllAggregates ¶
ReplaceAllAggregates replaces all aggregate function calls in the expression tree with ColRef nodes. The replacements map maps lowercase aggregate expression strings (e.g., "sum(rx_bytes)") to output column names.
func ReplaceGroupKeyRefs ¶ added in v0.18.6
ReplaceGroupKeyRefs rewrites every subexpression that IS one of an aggregate's GROUP BY keys into a reference to the column the aggregate publishes that key under. keys maps ExprIdentity to published name.
This is what makes a HAVING over a computed group key mean anything. Above the aggregate the input columns are gone and only the key's own output column carries the value, so `HAVING g + 1 > 2` written as arithmetic over `g` evaluated to UNKNOWN on every row — and a filter admits only TRUE, so the query returned no rows at all where PostgreSQL returns five (#720).
The walk is TOP-DOWN and stops at the first whole-term match, so the LARGEST expression that is a key is the one replaced: over `GROUP BY g + 1`, the predicate `g + 1 > 2` becomes `"g + 1" > 2` rather than descending to a `g` the aggregate does not emit.
It never enters an aggregate call. Inside one, `SUM(g + 1)`, the expression is evaluated over the aggregate's INPUT rows, where `g` is exactly the column that does exist; replacing it with the grouped output would compute something else entirely.
func ReplaceWindowFuncs ¶ added in v0.18.4
func ReplaceWindowFuncs(node Node, replacements map[*WindowFuncNode]string) Node
ReplaceWindowFuncs replaces each window function call named in replacements with a ColRef to its precomputed output column, leaving the surrounding expression intact. Nodes are matched by pointer identity, not by rendered text, because WindowFuncNode.String() collapses the OVER clause and two distinct windows would otherwise collide. It is the window analogue of ReplaceAllAggregates: after the builder has extracted a nested window into a NodeWindow output column, this rewrites SUM(x) OVER (...) + 1 into __win_0 + 1 so the ordinary projection compiler evaluates the outer expression over the window's result.
func RewriteExpr ¶ added in v0.18.6
RewriteExpr rebuilds an expression, offering every node to fn TOP-DOWN. fn returns (replacement, true) to substitute a node and stop descending into it, or (nil, false) to leave it and have its children visited.
It never enters an aggregate call: inside one the expression is evaluated over the aggregate's INPUT rows, which is a different namespace from the grouped output every caller of this is rewriting for.
A node kind it does not know is returned as it stands rather than guessed at — the conservative answer, and the one every caller relied on before this walk was shared.
func RewriteOuterRefs ¶
RewriteOuterRefs returns a deep copy of the AST with correlated ColRef nodes replaced by literal values from vals. Keys in vals are "table.column" (lowercased).
func RewriteUnqualifiedOuterRefs ¶
func RewriteUnqualifiedOuterRefs(node Node, unqualOuter map[string]string, vals map[string]any) Node
RewriteUnqualifiedOuterRefs replaces unqualified column references that were detected as outer refs (via column mapping). unqualOuter maps column names (lowercased) to their resolved table. vals contains "table.column" → value.
type OrderByItem ¶
type OrderByItem struct {
Column string
// Expr is the parsed form of Column. A sort term that is not a plain
// column reference — `year(d)`, `-id`, `a + b`, an ordinal — can only be
// honoured by evaluating it, so the logical builder needs the tree, not
// just its text (#320). Nil when the item was built without parsing.
Expr Node
Desc bool
NullsFirst *bool // nil = default, true = NULLS FIRST, false = NULLS LAST
// Ordinal is the 1-based select-list POSITION this term was written as,
// or 0 when it was written as a name or an expression.
//
// It survives resolvePositionalRefs' rewrite because a NAME is not an
// address once two output columns share one: `SELECT n_name AS u,
// n_regionkey AS u FROM nation ORDER BY 2, 1` sorted by column ONE on
// every arm, since every resolver below binds the first column carrying
// `u`. The position is known exactly at the rewrite and was thrown away
// there (#557).
Ordinal int
}
OrderByItem describes an ORDER BY element.
type OuterRef ¶
type OuterRef struct {
Table string // outer table alias (lowercased)
Column string // column name (lowercased)
}
OuterRef represents a correlated column reference to an outer query scope.
func DanglingTableRefs ¶
DanglingTableRefs reports the qualified column references in subquerySQL whose table identifier is not defined by any FROM clause within the subquery itself, at any nesting depth. A non-empty result means the subquery is not self-contained: executed standalone, the dangling reference resolves to no column and evaluates NULL, which is how a mis-deferred correlated scalar silently answered 0 on the stage DAG (#359). Unlike FindCorrelatedRefs it needs no outer table list, so it can guard a site that has lost the outer scope. Only qualified references are visible to it — unqualified correlation needs the scoped analysis.
func FindCorrelatedRefs ¶
FindCorrelatedRefs parses a subquery SQL string and returns any column references that refer to tables in outerTables but not to tables defined within the subquery itself. An empty result means the subquery is uncorrelated.
func FindCorrelatedRefsWithColumns ¶
func FindCorrelatedRefsWithColumns(subquerySQL string, outerTables map[string]bool, outerCols map[string]string) ([]OuterRef, error)
FindCorrelatedRefsWithColumns is like FindCorrelatedRefs but also accepts a column-to-table mapping for resolving unqualified column references.
func FindCorrelatedRefsWithScope ¶
func FindCorrelatedRefsWithScope(subquerySQL string, outerTables map[string]bool, outerCols map[string]string, innerCols TableColumns) ([]OuterRef, error)
FindCorrelatedRefsWithScope is FindCorrelatedRefsWithColumns plus the subquery's own column namespace, supplied by innerCols. With it, an unqualified name that the subquery's FROM supplies is resolved there and is not reported as correlated — the actual SQL rule. Without it the analysis falls back to comparing table identifiers, which only rejects the outer reference when the outer table's identifier happens to be spelled the same as one of the subquery's tables (issue #334).
type ParsedQuery ¶
type ParsedQuery struct {
Type QueryType
TableName string
SQL string
Explain *ExplainInfo
Describe *DescribeInfo
CreateFunction *CreateFunctionInfo
DropFunction *DropFunctionInfo
CreateTable *CreateTableInfo
DropTable *DropTableInfo
AnalyzeTable *AnalyzeTableInfo
CreateView *CreateViewInfo
DropView *DropViewInfo
AlterTable *AlterTableInfo
Merge *MergeInfo
Update *UpdateInfo
Delete *DeleteInfo
Insert *InsertInfo
CreateAlert *CreateAlertInfo
DropAlert *DropAlertInfo
AlterAlert *AlterAlertInfo
CreateSnapshot *CreateSnapshotInfo
Windows []WindowSpec // extracted window function specs
CTEs []CTEDef // extracted CTE definitions
SelectInfo *SelectInfo // parsed SELECT info (replaces AST)
}
ParsedQuery represents a parsed SQL query.
func Parse ¶
func Parse(sql string) (*ParsedQuery, error)
Parse parses ONE SQL statement into a ParsedQuery.
A string carrying SEVERAL statements is refused here, which is what makes this function the one-statement entry point every one-statement door needs: `wadjet.DB.Execute`, `wadjet.DB.Query`, the HTTP door and the CLI all reach it, and all of them return exactly one result. Only the pgwire SIMPLE query protocol runs a sequence, and it splits the string with SplitStatements before it gets here (#711). See CheckSingleStatement for what "refused" means and why the order matters.
type QueryType ¶
type QueryType int
QueryType identifies the kind of SQL statement.
const ( QuerySelect QueryType = iota QueryExplain QueryDescribe QueryCreateFunction QueryDropFunction QueryShowFunctions QueryCreateTable QueryDropTable QueryAnalyzeTable QueryShowTables QueryUpdate QueryDelete QueryInsert QueryCreateView QueryDropView QueryAlterTable QueryMerge QueryCreateAlert QueryDropAlert QueryAlterAlert QueryCreateSnapshot QueryUnsupported )
func (QueryType) StatementName ¶ added in v0.18.42
StatementName is the SQL keyword phrase a QueryType stands for, in the spelling a client wrote. It is what a refusal names, so it has to be the statement and not the internal constant.
type SelectColumn ¶
type SelectColumn struct {
Expr string
Alias string
Star bool
IsAgg bool
AggFunc string
AggArg string
AggArgExpr Node // AST for aggregate argument expression
AggArgs []Node // EVERY argument, AggArgExpr included (#353)
AggDistinct bool // COUNT(DISTINCT col)
IsWindow bool // true if this is a window function
WindowSpec *WindowSpec // window function details
ColumnRef string
TableRef string
ASTExpr Node // our AST expression node
// PublishedName is PostgreSQL's name for this output column when the
// SELECT list wrote no alias — its `FigureColname` (#732). It is stamped
// at PARSE time, on the item AS WRITTEN, because the planner REWRITES
// items: the LATERAL empty-input restoration turns `s.item_count` into
// `COALESCE(s.item_count, 0)`, and deriving the name from the rewritten
// AST published `coalesce` for a column PostgreSQL calls `item_count`.
// Empty for a synthetically built column, where OutputColumnName derives
// it from the AST instead.
PublishedName string
}
SelectColumn describes a column in a SELECT clause.
type SelectInfo ¶
type SelectInfo struct {
Tables []TableRef
Joins []JoinInfo
Columns []SelectColumn
Where string
WhereExpr Node
GroupBy []string
GroupByExprs []Node // AST for GROUP BY expressions (parallel to GroupBy)
GroupingSets [][]string // GROUPING SETS / CUBE / ROLLUP (nil = simple GROUP BY)
// GroupByAliasOrigin records, per GROUP BY entry, the bare name the
// parser SUBSTITUTED a SELECT alias's expression for — "" where it did
// not. It exists because the substitution is PROVISIONAL: PostgreSQL
// resolves a bare GROUP BY name against the INPUT COLUMNS FIRST and only
// then against an output alias, and the parser has no schema, so it
// cannot know which. The scope layer can (physical.colScope), and
// RevertGroupByAliasesShadowedByInput undoes the entries the input
// really provides.
//
// Recorded rather than decided-later-from-scratch so that an entry point
// with no catalog — and therefore no scope — keeps exactly the answer it
// had before the rule existed, instead of losing the substitution
// entirely (#739).
GroupByAliasOrigin []string
Having string
HavingExpr Node
Distinct bool
Qualify string
QualifyExpr Node
OrderBy []OrderByItem
Limit string
Offset string
Windows []WindowSpec // window function specs extracted during pre-parse
CTEs []CTEDef // CTE definitions extracted during pre-parse
Union *UnionInfo // non-nil if this is a UNION query
}
SelectInfo contains extracted information from a SELECT statement.
func ExtractSelect ¶
func ExtractSelect(pq *ParsedQuery) (*SelectInfo, error)
ExtractSelect returns the SelectInfo from a parsed query.
type SlotAllocator ¶ added in v0.18.6
type SlotAllocator struct {
// contains filtered or unexported fields
}
SlotAllocator hands out fresh hidden-slot names for ONE query scope.
It exists because `SlotName` is a pure namer and nothing ALLOCATED. Two independent authors then wrote the same bug against it within a day: a slot search that excluded the names already in scope but not the slots it had itself already issued.
- The window renamer, moving a slot past a stored `__win_0` column, took the first name not in the STORED set — `__win_1`, which the query's SECOND window already held. Both wrote `__win_1` and the by-name projection handed window #2 window #1's value. Silent, single-process path only, so a two-path divergence as well as a wrong number.
- The group-key minting, materializing two computed GROUP BY keys, skipped names in scope but not slots issued to earlier keys of the same aggregate. Two keys landed in one column and twelve groups collapsed to three. Silent.
One shape, two authors, because the shared API let each of them write their own search. This is the only way a slot may be obtained; `SlotName` remains for rendering a known index and for tests.
Not safe for concurrent use: an allocator belongs to one query scope, which is planned on one goroutine.
func NewSlotAllocator ¶ added in v0.18.6
func NewSlotAllocator(inScope ...string) *SlotAllocator
NewSlotAllocator returns an allocator for a query scope, seeded with the names that already exist in it. Seeding is case-insensitive, because column resolution is.
func (*SlotAllocator) Issued ¶ added in v0.18.6
func (a *SlotAllocator) Issued() []string
Issued lists the slots this allocator handed out, in order.
func (*SlotAllocator) Next ¶ added in v0.18.6
func (a *SlotAllocator) Next(family SlotFamily) (string, bool)
Next returns the next unused slot of a family and records it as used.
It excludes BOTH the seeded names and every slot this allocator has already issued, which is the whole of its contract.
ok is false only when the family is EXHAUSTED — no free index below the search bound — which needs a scope holding a million names of one family and has no known SQL. A caller that gets false must leave the plan as it was rather than invent a name: an allocator that cannot allocate is a reason to decline an optimization, never a reason to reuse a slot.
Terminates: the family's cursor advances by one per candidate and never rewinds, `taken` grows by at most one per successful call, and the loop is bounded.
func (*SlotAllocator) Seed ¶ added in v0.18.6
func (a *SlotAllocator) Seed(names ...string)
Seed adds more names to the scope. Safe to call after allocation has begun — a name seeded late is excluded from every LATER allocation, and the names already issued stay issued.
type SlotFamily ¶ added in v0.18.6
type SlotFamily string
SlotFamily names one kind of hidden slot. The value is the name prefix, so a family constant and its reservation cannot drift apart.
const ( SlotWindowOutput SlotFamily = "__win_" // a window function's output SlotWindowKey SlotFamily = "__winkey_" // a materialized PARTITION BY / ORDER BY / argument SlotSortKey SlotFamily = "__sortkey_" // a materialized ORDER BY term SlotGroupKey SlotFamily = "__gb_expr_" // a computed GROUP BY key SlotAggInput SlotFamily = "__agg_expr_" // an aggregate's derived argument SlotNestedAgg SlotFamily = "__agg_" // the nested-aggregate rewrite SlotScalar SlotFamily = "__scalar_" // a scalar subquery's answer SlotHaving SlotFamily = "__having_" // a materialized HAVING term SlotTwoLevel SlotFamily = "__tl_" // the two-level distinct rewrite SlotSetOpCount SlotFamily = "__setop_" // set-operation arm counters SlotAvgSum SlotFamily = "__avg_sum" // AVG's decomposed SUM leg SlotAvgCount SlotFamily = "__avg_count" // AVG's decomposed COUNT leg SlotVarState SlotFamily = "__var_state" // STDDEV/VARIANCE partial state SlotCovarState SlotFamily = "__covar_state" // CORR/COVAR partial state SlotGrouping SlotFamily = "__grouping_" // a GROUPING(...) bitmask // The SUFFIX-minted families: their names carry a discriminator rather than // a bare index, so they are rendered with fmt.Sprintf against the constant // rather than through SlotName. They are reserved on the same grounds. SlotPreComputedAgg SlotFamily = "__precomp_agg_" // pre-computed aggregate substitution SlotSubsumeFlag SlotFamily = "__subsume_f" // subsumed-filter marker SlotRowLocator SlotFamily = "__row_loc" // row-locator sentinel SlotRowCountOnly SlotFamily = "__rowcount_only__" SlotDefaultPart SlotFamily = "__default__" )
The slot families the planner mints. Every one is reserved.
type SubqueryNode ¶
type SubqueryNode struct {
SQL string
}
SubqueryNode wraps a subquery as raw SQL.
func (*SubqueryNode) String ¶
func (s *SubqueryNode) String() string
type TableColumns ¶
TableColumns reports the column names of a table named in a subquery's FROM clause, or nil when the table is unknown (a CTE, a table function, a planner with no catalog). It is what lets correlation analysis apply the SQL scoping rule: an unqualified name inside a subquery is resolved against the subquery's own FROM first, and only a name that does NOT resolve there is a reference to the outer query.
type TableRef ¶
type TableRef struct {
Name string
Qualifier string // schema or catalog.schema written before the name
Alias string
IsFunction bool // true for table functions like read_json(...)
FuncArgs []string // positional arguments
FuncNamedArgs map[string]string // named arguments (key=value)
WithOrdinality bool // UNNEST(...) WITH ORDINALITY
ColumnAliases []string // AS alias(col1, col2, ...)
SampleMethod string // TABLESAMPLE method: BERNOULLI, SYSTEM
SamplePercent string // percentage for TABLESAMPLE
// contains filtered or unexported fields
}
TableRef is a reference to a table or table-producing function.
func (*TableRef) SubSelect ¶ added in v0.18.35
func (t *TableRef) SubSelect() (*SelectInfo, error)
SubSelect returns the parsed SELECT body of a DERIVED TABLE reference, memoized on the reference. It returns (nil, nil) when the reference is not a derived table, and the parse error when the body does not parse — callers wrap that in their own message, which is why the error is memoized too.
type TokenType ¶
type TokenType int
TokenType identifies the kind of lexical token.
const ( // Special TokenError TokenType = iota // lexing error (val contains message) TokenEOF // end of input // Literals TokenIdent // identifier — unquoted, or double-quoted (token.quoted is set) TokenString // single-quoted string literal (val has quotes stripped, ” unescaped) TokenNumber // integer or decimal // Punctuation TokenLParen // ( TokenRParen // ) TokenComma // , TokenSemicolon // ; TokenStar // * TokenDot // . TokenLBracket // [ TokenRBracket // ] TokenLBrace // { TokenRBrace // } // Operators TokenPlus // + TokenMinus // - TokenSlash // / TokenPercent // % TokenConcat // || TokenDoubleColon // :: TokenJSONArrow // -> TokenJSONDoubleArrow // ->> TokenEq // = TokenNotEq // != or <> TokenLT // < TokenLTEq // <= TokenGT // > TokenGTEq // >= // Keywords (case-insensitive, val is always uppercase) TokenKWCreate TokenKWOr TokenKWReplace TokenKWFunction TokenKWAs TokenKWDrop TokenKWIf TokenKWExists TokenKWShow TokenKWFunctions TokenKWColumns TokenKWFrom TokenKWExplain TokenKWVerbose TokenKWAnalyze TokenKWDescribe TokenKWDesc TokenKWWith TokenKWLock TokenKWTable TokenKWTables TokenKWNot TokenKWNull TokenKWPartition TokenKWBy // SQL query keywords TokenKWSelect TokenKWWhere TokenKWGroup TokenKWHaving TokenKWOrder TokenKWLimit TokenKWOffset TokenKWAsc TokenKWDistinct TokenKWAll TokenKWUnion TokenKWIntersect TokenKWExcept TokenKWAnd TokenKWIn TokenKWBetween TokenKWLike TokenKWILike TokenKWIs TokenKWTrue TokenKWFalse TokenKWCase TokenKWWhen TokenKWThen TokenKWElse TokenKWEnd TokenKWCast TokenKWJoin TokenKWOn TokenKWInner TokenKWLeft TokenKWRight TokenKWOuter TokenKWFull TokenKWCross TokenKWNatural TokenKWOver TokenKWNulls TokenKWFirst TokenKWLast TokenKWRows TokenKWRange TokenKWUnbounded TokenKWPreceding TokenKWFollowing TokenKWCurrent TokenKWRow TokenKWCube TokenKWRollup TokenKWGrouping TokenKWSets // Clause keywords TokenKWFetch TokenKWView TokenKWAlter TokenKWAdd TokenKWColumn TokenKWRename TokenKWTo // DML keywords TokenKWUpdate TokenKWSet TokenKWDelete TokenKWInsert TokenKWInto TokenKWValues TokenKWMerge TokenKWUsing TokenKWMatched // Alert DDL keywords TokenKWAlert TokenKWEvery TokenKWWebhook TokenKWHeaders TokenKWEnable TokenKWDisable TokenKWSeconds TokenKWMinutes TokenKWHours // Snapshot keywords TokenKWSnapshot // Raw capture TokenRawBody // everything after AS until terminator )
type TupleNode ¶
type TupleNode struct {
Elements []Node
}
TupleNode represents a tuple expression: (a, b, c).
type UnionInfo ¶
type UnionInfo struct {
Left *SelectInfo
Right *SelectInfo
All bool // true for UNION ALL / INTERSECT ALL / EXCEPT ALL (no dedup)
Op SetOp // the set operation type (defaults to UNION for backwards compat)
}
UnionInfo describes a set operation (UNION, INTERSECT, EXCEPT) with left and right sides.
type UpdateInfo ¶
UpdateInfo holds details for an UPDATE statement.
type WhenClause ¶
WhenClause is a single WHEN ... THEN ... clause.
type WindowFrame ¶
type WindowFrame struct {
Mode FrameMode
Start FrameBound
End *FrameBound // nil means "to CURRENT ROW"
}
WindowFrame describes a window frame specification.
type WindowFuncNode ¶
type WindowFuncNode struct {
Func *FuncCallNode
PartitionBy []Node
OrderBy []WindowOrderBy
Frame *WindowFrame
}
WindowFuncNode represents a window function call: FUNC(...) OVER (...)
func FindAllWindowFuncs ¶ added in v0.18.4
func FindAllWindowFuncs(node Node) []*WindowFuncNode
FindAllWindowFuncs walks an expression tree and returns every window function call found, in left-to-right order. It is the window analogue of FindAllAggregates and lets the logical builder detect a window call nested inside a larger expression — SUM(x) OVER (...) + 1, COALESCE(LAG(x) OVER (...), 0), a CASE branch — not just the bare top-level form. It does not recurse into a window node's own argument/OVER subtrees: a window over a window is not a shape this handles, and stopping keeps the returned nodes disjoint so each maps to one output column.
func (*WindowFuncNode) String ¶
func (n *WindowFuncNode) String() string
type WindowOrderBy ¶
WindowOrderBy describes ordering in a window function's OVER clause.
type WindowOrderItem ¶
WindowOrderItem describes a column + direction in a window ORDER BY.
type WindowSpec ¶
type WindowSpec struct {
FuncName string
Args string // raw arg string (e.g., "amount", "*", "")
PartitionBy []string
OrderBy []WindowOrderItem
Alias string // output column name
Frame *WindowFrame // optional frame specification
}
WindowSpec describes a window function specification.