expr

package
v0.18.52 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 6, 2026 License: AGPL-3.0 Imports: 36 Imported by: 0

Documentation

Overview

Package expr provides a typed expression engine for evaluating SQL expressions against record batches. It replaces the string-based expression parsing with a compiled expression tree built from the SQL parser AST.

Index

Constants

View Source
const (
	// SessionUser is the user name reported by current_user / session_user /
	// user / current_role.
	SessionUser = "wadjet"
	// SessionCatalog is the database name reported by current_catalog /
	// current_database().
	SessionCatalog = "wadjet"
	// SessionSchema is the schema reported by current_schema.
	SessionSchema = "public"
	// ServerVersion is the answer to version(). PostgreSQL drivers parse the
	// leading "PostgreSQL <major>" to decide which protocol features and
	// catalog queries they may use, so the string keeps that prefix.
	ServerVersion = "PostgreSQL 15.0 (Wadjet analytical query engine)"
)

--- Session / catalog information functions ---

PostgreSQL clients (pgJDBC, DataGrip, psql, Superset) open a connection by asking who and where they are: current_user, current_schema, current_database. These are answered here rather than only in the pgwire introspection layer so that a query mixing them with real columns — or selecting three of them at once — executes as an ordinary query with an ordinary result shape.

The values are server constants. ScalarFunc is func([]any) any and DefaultRegistry is process-global, so a scalar function cannot see the calling connection's identity; a per-session answer would need a context-carrying evaluation path that does not exist. The constants match what pgwire reports for an unauthenticated session.

View Source
const ConcatOpFunc = "||"

ConcatOpFunc is the registry name of the `||` OPERATOR's implementation.

It is punctuation on purpose. A registry entry is reachable from SQL only if a query can spell its name, and no SQL identifier — bare or delimited — is `||`: the lexer reads those two bytes as an operator token before any identifier rule sees them. So the operator's NULL-propagating kernels cannot be invoked as a function, and `CONCAT` cannot reach them (#609). The census carries a fixture that attempts both spellings, because "unspellable" is a claim and the protocol's method 10 says a claim gets a fixture rather than a comment.

Variables

View Source
var (
	RetBool      = Ret{/* contains filtered or unexported fields */}
	RetInt32     = Ret{/* contains filtered or unexported fields */}
	RetInt64     = Ret{/* contains filtered or unexported fields */}
	RetFloat64   = Ret{/* contains filtered or unexported fields */}
	RetString    = Ret{/* contains filtered or unexported fields */}
	RetBytes     = Ret{/* contains filtered or unexported fields */}
	RetArray     = Ret{/* contains filtered or unexported fields */}
	RetMap       = Ret{/* contains filtered or unexported fields */}
	RetTimestamp = Ret{/* contains filtered or unexported fields */}

	// RetVector is embed()'s declaration. The output *dimension* is a
	// separate, deliberately dynamic answer the registry already carried
	// before this type existed — see RegisterVecReturn / VecReturnDim.
	RetVector = Ret{/* contains filtered or unexported fields */}

	// RetDynamic declares that only the value knows: element_at returns the
	// element type of its argument, json_extract whatever the document held.
	// The planner keeps its own fallback for these. It is an explicit
	// declaration, not an omission — a function whose vec kernel writes a
	// typed slice must never carry it.
	RetDynamic = Ret{/* contains filtered or unexported fields */}
)

The fixed declarations. These name the type the function's Go results are stored as, not the type SQL calls them: the date/time functions below return formatted strings, so they declare RetString.

View Source
var DefaultRegistry = NewFuncRegistry()

DefaultRegistry is the global function registry used by the expression engine.

View Source
var DefaultUDFs = NewUDFStore()

DefaultUDFs is the global UDF store.

Functions

func CheckFilterColumns added in v0.18.5

func CheckFilterColumns(b *batch.RecordBatch, refs []string) error

CheckFilterColumns returns a 42703 error naming the first reference that resolves to no column of b. Callers run it once, on the first batch.

func DecimalCastDest added in v0.18.5

func DecimalCastDest(dest string) (prec, scale int, hasParams, ok bool)

DecimalCastDest reports the (precision, scale) a DECIMAL cast destination names, for the planner's declared-type layer. hasParams is false for a bare DECIMAL/NUMERIC, whose type comes from the operand; ok is false for a destination that is not DECIMAL at all, or whose (p,s) no DECIMAL can hold.

func DecimalResultOf added in v0.18.5

func DecimalResultOf(e Expr, b *batch.RecordBatch) (precision, scale int, ok bool)

DecimalResultOf reports the EXACT fixed-point type an expression produces against this batch, and whether it produces one at all.

It exists for the consumers that materialize a vector from a compiled expression without a plan-time declaration to read — the stage DAG's gather, which re-compiles a wrapped aggregate's expression from its AST and has only the input batch to type it from. Those callers built a FLOAT64 vector and nulled every box they could not put in it, so `SUM(d) * 2` came back NULL on the DAG and answered on the single-process path (#555 review, R1).

The answer is a pure function of the input SCHEMA — which operand is a DECIMAL column and at what scale — so it is the same for every batch of one query, and a caller may resolve it per batch without the type flapping.

func DecimalScalarFnOp added in v0.18.5

func DecimalScalarFnOp(name string) (batch.DecimalScalarOp, bool)

DecimalScalarFnOp reports the batch-level op a scalar math function maps to, for the planner's declared-type layer. ok=false for a name with no exact fixed-point form.

func EvalDecimalInto added in v0.18.5

func EvalDecimalInto(e Expr, b *batch.RecordBatch, row int, dst *batch.Vector, at int) bool

EvalDecimalInto writes one row's exact value into a DECIMAL vector, or reports that this expression produced NULL. The caller owns the null bit for the false case, the way every other vector writer here does.

func FilterColumnRefs added in v0.18.5

func FilterColumnRefs(n plansql.Node) ([]string, bool)

FilterColumnRefs lists the column references an expression reads, spelled as WRITTEN (`c_row.b` stays `c_row.b`, which is what ResolveColumnRef expects).

ok=false means the expression carries a node whose references this walker cannot enumerate — a subquery, EXISTS, ANY/ALL, a window function, or a node added since. Those are exactly the shapes where a name may legitimately resolve OUTSIDE the batch (a correlated outer reference, a subquery's own inner columns), so the caller must skip the guard rather than guess.

func FilterPredicate

func FilterPredicate(e Expr) func(b *batch.RecordBatch, row int) bool

FilterPredicate compiles a boolean expression into the per-row predicate a filter loop calls, resolving the evaluation protocol ONCE here rather than on every row.

A WHERE admits only TRUE, so its answer is always the two-valued collapse `val && !null`. For the comparisons and set predicates that collapse IS their EvalBool — a wrapper whose whole body is a call to EvalBoolNull and a drop of the second result, which #370 left behind when it made the three-valued form the definition. Reached through an interface the wrapper is a call frame the compiler cannot remove, so the row loop pays it on every row; taking EvalBoolNull directly here answers identically with the frame gone.

The connectives stay on EvalBool deliberately. And/Or collapse each operand BEFORE the operator, so they stop at an UNKNOWN left operand where the three-valued form must evaluate the right one to tell FALSE from UNKNOWN. Same answer either way (Kleene min/max agrees with the collapse), but different work — and a right operand that raises, `1/0`, would start raising. IS NULL / IS TRUE are excluded for the mirror reason: there EvalBool is the definition and EvalBoolNull is the wrapper.

func FuncReturnsInteger added in v0.18.5

func FuncReturnsInteger(name string) bool

FuncReturnsInteger reports whether a registered function always returns an integer, for the planner's declared-type layer. It is the AST-side twin of isIntNative's registry lookup, so the DECLARED type of `length(s) / 2` is the INT64 the runtime actually produces (#636).

func Int64ResultOf added in v0.18.11

func Int64ResultOf(e Expr, b *batch.RecordBatch) bool

Int64ResultOf reports whether an expression produces an INT64 box against this batch — DecimalResultOf's integer sibling, for the same consumers and the same reason.

The stage DAG's gather re-compiles a wrapped aggregate's expression from its AST and has only the input batch to type it from, so it materialized every non-boolean, non-decimal result into a FLOAT64 vector. Since #784 that is a visible divergence rather than a rounding: `SELECT SUM(c_i32 * 2)` is rewritten to `SUM(c_i32) * 2` above the aggregate, PostgreSQL and the single-process path both declare it bigint, and the DAG handed the client a float8 — the same number under a different wire OID depending on which engine ran it.

Like DecimalResultOf, the answer is a pure function of the input SCHEMA — an integer column stays an integer column for every batch of one query — so a caller may resolve it per batch without the type flapping. It is deliberately NARROW: integer arithmetic over integer leaves and nothing else. A shape it does not recognise keeps the float64 materialization it had, which is the direction that cannot turn a right answer into a wrong one.

func IntArithOn

func IntArithOn() bool

IntArithOn exposes the toggle to the planner: projection output types may only declare Int64 for arithmetic when the runtime will actually take the integer path (see inferProjectionTypeCols).

func IsCompileRefusal added in v0.18.2

func IsCompileRefusal(err error) bool

IsCompileRefusal reports whether err is a compile failure that the caller must PROPAGATE rather than fall back around.

The physical planner has six sites that compile an AST and quietly keep going when it will not compile, because a failed compile usually means "this expression is really a reference to an aggregate's output column". Three classes of failure are never that, and all three are the answer to the query: a name nothing implements (#341), a literal that names no value of its type (#505), and a literal that names a value out of its type's range (#646). Naming them together here keeps the six sites from drifting apart as a fourth class arrives.

func IsDecimalScalarFn added in v0.18.5

func IsDecimalScalarFn(name string) bool

IsDecimalScalarFn reports whether a function answers in its argument's own domain and so has an exact DECIMAL form — the seven of ADR-0024 item 3, mod included. The planner asks so its declaration and this node agree about which names take the exact path.

func IsIntegerCastDest added in v0.18.21

func IsIntegerCastDest(dest string) bool

IsIntegerCastDest reports whether a CAST destination names an integer type.

The list is Cast.Eval's own switch, and it is a function rather than a second copy of that switch so the two cannot drift: a destination this says yes to must be one that switch answers an int64 for.

func IsInvalidLiteral added in v0.18.2

func IsInvalidLiteral(err error) bool

IsInvalidLiteral reports whether err is, or wraps, an InvalidLiteralError.

func IsNumericLiteralText added in v0.18.3

func IsNumericLiteralText(s string) bool

IsNumericLiteralText reports whether a QUOTED string literal's content names a value a DECIMAL column can be COMPARED against: the plan-time refusal of a non-numeric constant against a DECIMAL column (#517) must accept and refuse exactly the strings the runtime refusal does, or a query would be refused at one and answered at the other — the two-path defect class the refusal exists to close.

It is the DECIMAL arm of kernel.QuotedLitStatus, which is what every site calls now that the rule covers the whole numeric family (#646); this stays as the type's own predicate, and as the spelling the DECIMAL gates name.

So it is `kernel.DecimalLiteral.Numeric()` itself, the runtime predicate, which since #534 accepts PostgreSQL's NaN and ±Infinity spellings alongside the finite numbers: none of the three is a value a DECIMAL column holds, all three are bounds it can be ordered against, and refusing them here would put the plan-time refusal back in front of a query PostgreSQL answers (ADR-0024 item 6).

func IsNumericRange added in v0.18.5

func IsNumericRange(err error) bool

IsNumericRange reports whether err is, or wraps, a NumericRangeError.

func IsUnknownFunc

func IsUnknownFunc(err error) bool

IsUnknownFunc reports whether err is, or wraps, an UnknownFuncError.

func KnownCastDest added in v0.18.34

func KnownCastDest(name string) bool

KnownCastDest reports whether name is a type this engine has, or one whose text it hands back exactly as PostgreSQL would. It is the question `CAST(x AS name)` asks before anything else, and the answer for a name in neither door's set is PostgreSQL's 42704.

func LiteralChoiceDecimalType added in v0.18.5

func LiteralChoiceDecimalType(text string) (batch.DecimalType, bool)

LiteralChoiceDecimalType is the fixed-point type a numeric LITERAL contributes to a CHOICE construct's DECIMAL fold: its spelling's (p,s) — ADR-0024 item 3 — but only when the BOX compileLit built for it carries that spelling exactly.

The box is the qualification, and it is what separates a choice from arithmetic. Exact arithmetic reads a literal through its source TEXT (litDecimal, ADR-0012 item 6) and is exact for any spelling; a choice construct CHOOSES a value and hands over whatever box the winning arm produced, which for a literal past a double's ~17 significant digits is already rounded. Declaring DECIMAL for `GREATEST(d_wide, 493827160549382.7160549350)` would therefore store a number nobody wrote on the rows the literal wins. Declining leaves that shape exactly where it was — a FLOAT64 declaration and the #361 store refusal — which is loud rather than quietly short of digits.

An INTEGER spelling is exact whenever strconv.ParseInt took it, which is exactly when compileLit put an int64 in the box.

func NumericConstTypeOfText added in v0.18.6

func NumericConstTypeOfText(text string) (batch.TypeID, bool)

NumericConstTypeOfText is numericConstType over a constant's SPELLING alone, which is what the PLANNER has: physical.nodeDeclaredType types a literal from its AST text, long before a compiled *Lit with a box exists.

It is exported so the declared-type fold (expr.CommonDeclType) and the boxed comparison layer resolve a constant's rung through ONE function. They fold the same composite and must not disagree about it: the comparison decides which argument wins and the declaration decides the vector the winner is stored in, and a disagreement between them is a value narrowed or wrapped on the way out (#724).

func NumericDomainResult added in v0.18.21

func NumericDomainResult(name string, args []batch.TypeID) (batch.TypeID, bool)

NumericDomainResult is the type ABS or MOD answers for arguments of the given types, or ok=false when this rule does not apply and the caller keeps the registry's FLOAT64 declaration.

It is the DECLARATION half of the pair; absKeepsDomain and modKeepsDomain below are the value half, and the two are written next to each other so a change to one that is not made to the other is visible.

func NumericDomainScalarFn added in v0.18.21

func NumericDomainScalarFn(name string) (int, bool)

NumericDomainScalarFn reports whether name answers in its argument's own integer or real domain, and how many arguments it takes there.

func ProcessStart added in v0.18.23

func ProcessStart() time.Time

ProcessStart returns the instant this process began — the value pg_postmaster_start_time() reports. Exported so a gate can assert that the wire carries THIS process's start EXACTLY, instead of bounding it against wall-clock time at assertion time. That bound is a statement about how long the rest of a test binary ran, not about the server: at 300 seconds it failed permanently once the -race suite crossed five minutes (#563), and with the bound removed it passes for a server reporting 1970 (#518).

func QuotedLitDecimalType added in v0.18.6

func QuotedLitDecimalType(text string) (batch.DecimalType, bool)

QuotedLitDecimalType is the fixed-point (p,s) a QUOTED literal contributes to a DECIMAL fold: its spelling's, exactly.

It is deliberately NOT LiteralChoiceDecimalType, which is the same question for an UNSUFFIXED numeric constant and carries one extra qualification — that the box compileLit built round-trips the spelling. That qualification is about the box: a choice construct hands over whatever box the winning arm produced, and past a double's ~17 significant digits a numeric literal's box has already lost digits.

A quoted literal has no such box. It arrives as its own TEXT, and the constructs that choose between arms hand that text on unchanged — a DECIMAL value IS its rendered text everywhere in this engine — so the spelling reaches the store intact and the fold may declare a (p,s) wide enough for it. `GREATEST(numeric(15,2), '12.750000000000000001')` therefore keeps every digit, which is what PostgreSQL answers.

ok=false for a spelling the carrier cannot hold at all ('1e39' needs 40 digits): the fold then declares the DECIMAL its typed operands agree on and the store raises 22003 rather than wrapping (ADR-0024 items 1 and 4).

func RefuseNetworkPrefixLiteral added in v0.18.44

func RefuseNetworkPrefixLiteral(typ batch.TypeID, text string) error

RefuseNetworkPrefixLiteral is the OTHER way a network literal can fail to be a value its column can hold, and it is a different answer from a syntax error: `'10/8'` and `'::1/64'` are ordinary `inet` values on the server — NETWORKS — and an IPV4/IPV6 column holds a bare address with no room for a prefix. 0A000 (feature_not_supported), never 22P02, because the text is valid and the engine's TYPE is the limit.

It is asked at PLAN time beside RefuseNumericLiteral and at runtime by exec.networkConstError, and both read kernel.NetworkPrefixLiteral, so one literal cannot be a network at one site and garbage at another. Before #627 round 2 the 0A000 existed at ONE evaluator: the same query refused in a WHERE clause, answered inside a CASE, and on the DAG answered a WRONG NUMBER (the widened parser read the prefix as the address zero).

func RefuseNumericLiteral added in v0.18.5

func RefuseNumericLiteral(typ batch.TypeID, text string) error

RefuseNumericLiteral is the ONE refusal, as an error rather than a panic, so the plan-time binder (physical.refuseLiteralForType) and the row-at-a-time evaluators raise the identical SQLSTATE and the identical message for the identical query. nil means the type accepts the text — or has no rule.

The two SQLSTATEs are PostgreSQL's and they are different answers: 22P02 (invalid_text_representation) for text that names no value of the type, 22003 (numeric_value_out_of_range) for a number the type cannot carry. `real = '1e400'` is the second, not the first, and the WireProtocol oracle checks which one the wire says.

func RegisterFunc

func RegisterFunc(name string, fn ScalarFunc, ret Ret)

RegisterFunc registers a custom scalar function in the default registry. ret declares what the function returns; see Ret.

func ResolveColumnRef added in v0.18.5

func ResolveColumnRef(b *batch.RecordBatch, name string) (idx int, structField string)

ResolveColumnRef is the column lookup every ColRef performs, exported so a caller can ask whether a reference resolves WITHOUT evaluating it — which is what tells an absent column apart from a NULL one. It returns the batch column index (-1 when the name names nothing) and, for a ROW field path, the field name within it.

Four spellings resolve, in order: the name exactly as written; the bare reference the stream spells qualified; a `row.field` path whose qualifier names a ROW column of the batch THAT DECLARES THE FIELD; and only then the bare column left after dropping a table qualifier.

func ResolveFuncName added in v0.18.7

func ResolveFuncName(name string) error

ResolveFuncName reports whether a call's name is one this engine implements, answering with the same UnknownFuncError (42883) the compiler raises for it.

It exists so a check that runs BEFORE compilation can reach the same verdict rather than form a second one: PostgreSQL resolves a function during parse analysis and only then checks grouping coverage, so a validator that walks a grouped query's expressions has to know that an unresolvable name is already settled. Routing that through this one function is what keeps the two decisions a single decision — the WADJET_STRICT_FUNCTIONS hatch and the unimplemented-aggregate wording included.

func ResultIsDecimalText added in v0.18.5

func ResultIsDecimalText(e Expr, b *batch.RecordBatch) bool

ResultIsDecimalText reports whether an expression's boxed result is a DECIMAL rendered as its TEXT, resolved from the expression's DECLARATIONS against this batch rather than from the box.

It exists for the callers that must RE-SPELL a boxed value as SQL text — the coordinator's scalar-subquery substitution, which inlines the value into a filter expression the worker re-parses. A DECIMAL and a STRING both arrive as a Go string, and quoting a DECIMAL there makes it look like a literal a user wrote, which the numeric column it meets then reads with its OWN input function (ADR-0012 item 13): `HAVING COUNT(*) > (SELECT COUNT(*) * 0.3 …)` substituted `'0.0'` and asked bigint to read it, a 22P02 for a query PostgreSQL answers. This is item 8's rule — the declaration, never the box — at the one boundary that turns a value back into text.

func ScalarSubqueryValue added in v0.18.33

func ScalarSubqueryValue(sql string, rows []map[string]any) (any, error)

ScalarSubqueryValue reduces a scalar subquery's RESULT to the one value a scalar subquery is, and is the single place in the engine that decides what "one" means.

PostgreSQL's rule, and now this engine's (ADR-0021 §5):

NO rows      the value is SQL NULL. An absent row is not an error.
ONE row      that row's value.
MORE         SQLSTATE 21000, `more than one row returned by a subquery
             used as an expression`. Never the first row.

Every evaluator used to take `rows[0]` and say nothing. That is a WRONG ANSWER wearing a plausible one — `WHERE n < (SELECT n FROM src)` over a two-row `src` answered against whichever row the runner happened to return first — and on the DML door it was worse than wrong: `DELETE FROM t WHERE n < (SELECT n FROM src)` emptied the table where PostgreSQL raises and deletes nothing.

The MULTI-COLUMN case is deliberately not decided here. PostgreSQL refuses it at analysis time with 42601 (`subquery must return only one column`) and this engine does not; that is a separate gap, and picking a column out of a Go map — which is what the loop below does — is arbitrary for it either way. Nothing here makes that better or worse.

func SetClockForTest added in v0.18.44

func SetClockForTest(f func() time.Time) func()

SetClockForTest replaces the clock every clock function reads and returns a function restoring it. TEST ONLY, and NOT safe for parallel tests in the same process — it is a package var, like exec's spill knobs.

It exists because the defect it gates is a CONDITION, not a query shape: the two functions agree for most of the day and disagree only inside the UTC offset, so a gate that reads the real clock passes on the machine that has the bug for sixteen hours out of twenty-four.

func StampArithMode added in v0.18.34

func StampArithMode(e Expr, integer bool)

StampArithMode tells a compiled arithmetic node what the PLANNER decided its output type is, so the runtime does not decide it a second time.

The two decisions are `physical.intArithAllInt` (which picks the output VECTOR) and `expr.operandIsInt` (which picks the KERNEL), and they were two hand-maintained walks over two representations of one expression. When they disagree the value is not merely mislabelled: a float computed under an INT64 declaration is TRUNCATED into the vector, and at the edge it WRAPS. That is how `LEAST(c_i64, 1.5) * 3` answered 4 for PostgreSQL's 4.5 and `(CASE … ELSE 1.5 END) * <int8 max>` answered MinInt64 (round-1 review, B3).

So the planner stamps, and `intArm.resolve` returns the stamped answer instead of re-deriving one. `integer` is the planner's claim that the output vector is INT64.

The stamp cannot manufacture an integer out of a value that is not one: `BinOp.intArith` still reads both boxes through `toInt64Safe` and returns ok=false for anything else, so a stamp of `true` over a decimal or a float box falls through to the float arm exactly as an unstamped node would. What it removes is the case where the runtime says integer and the planner did not — the direction that used to leave a right value under a declaration nothing enforced.

Only the TOP node of a projection is stamped, which is the only one whose answer meets a materialized vector; a nested node is an input to this decision and keeps deriving its own.

func ToFloat64

func ToFloat64(v any) float64

ToFloat64 converts any numeric value to float64.

func ToInt64

func ToInt64(v any) int64

ToInt64 converts any numeric value to int64.

Types

type And

type And struct {
	Left, Right Expr
}

And is a logical AND.

func (*And) Eval

func (e *And) Eval(b *batch.RecordBatch, row int) any

func (*And) EvalBool

func (e *And) EvalBool(b *batch.RecordBatch, row int) bool

func (*And) EvalBoolNull

func (e *And) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

EvalBoolNull: FALSE AND anything is FALSE; otherwise a NULL operand makes it UNKNOWN. Short-circuits on a FALSE left operand.

type ArrayLitExpr

type ArrayLitExpr struct {
	Elements []Expr
}

ArrayLitExpr evaluates to a []any containing the evaluated elements.

func (*ArrayLitExpr) Eval

func (e *ArrayLitExpr) Eval(b *batch.RecordBatch, row int) any

type Between

type Between struct {
	Expr    Expr
	Low, Hi Expr
	Not     bool
	// contains filtered or unexported fields
}

Between checks if a value is between two bounds.

func NewBetween added in v0.18.1

func NewBetween(e, low, hi Expr, not bool) *Between

NewBetween builds a range test, binding the DECIMAL-column-against- numeric-literals shape and one boxed pair per bound.

func (*Between) Eval

func (e *Between) Eval(b *batch.RecordBatch, row int) any

func (*Between) EvalBool

func (e *Between) EvalBool(b *batch.RecordBatch, row int) bool

func (*Between) EvalBoolNull

func (e *Between) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

EvalBoolNull: BETWEEN is defined as (x >= lo AND x <= hi), so a NULL bound does not force UNKNOWN — the other half can still answer FALSE (`5 BETWEEN NULL AND 2` is false, and NOT BETWEEN flips it to true).

type BinOp

type BinOp struct {
	Left, Right Expr
	Op          string // +, -, *, /, %
	// contains filtered or unexported fields
}

BinOp is a binary arithmetic expression (generic, uses ToFloat64).

func (*BinOp) Eval

func (e *BinOp) Eval(b *batch.RecordBatch, row int) any

type BinOpFloat64

type BinOpFloat64 struct {
	Left, Right Float64Expr
	Op          string
	// contains filtered or unexported fields
}

BinOpFloat64 is a typed binary op that operates on float64 without boxing. Uses a pre-resolved arithOp opcode for the hot EvalFloat64 path to avoid per-row string comparison on the Op field. The opcode is resolved lazily so external callers can construct BinOpFloat64 directly with only Op populated.

opReady is a double-checked atomic flag rather than sync.Once: Once.Do builds a closure and loads the done flag on EVERY row, and that closure keeps resolveOpCode too big to inline. This form is small enough that the compiler inlines resolveOpCode straight into EvalFloat64 (verified with -gcflags='-m'), the same guard BinOpNumeric.resolveMode and ColRef.resolve already use. opReady publishes opCode: set last under opMu, read first (and alone) by EvalFloat64 — concurrent pipeline workers share one *BinOpFloat64 through a captured closure, same as those two.

func (*BinOpFloat64) CloneVec

func (e *BinOpFloat64) CloneVec() *BinOpFloat64

CloneVec creates a deep copy of the BinOpFloat64 tree with fresh scratch buffers. Required for parallel pipeline execution where multiple workers must not share mutable vecBuf state. Stateless leaf nodes (ColRef, Literal) are shared; only BinOpFloat64 nodes (which own vecBuf) are cloned.

func (*BinOpFloat64) Eval

func (e *BinOpFloat64) Eval(b *batch.RecordBatch, row int) any

func (*BinOpFloat64) EvalFloat64

func (e *BinOpFloat64) EvalFloat64(b *batch.RecordBatch, row int) (float64, bool)

func (*BinOpFloat64) EvalFloat64Vec

func (e *BinOpFloat64) EvalFloat64Vec(b *batch.RecordBatch, dst []float64, n int) bool

EvalFloat64Vec evaluates left and right operands in bulk, then applies the arithmetic op in a tight loop. Eliminates ~5 function calls per row.

type BinOpInt64

type BinOpInt64 struct {
	Left, Right Int64Expr
	Op          string
	// contains filtered or unexported fields
}

BinOpInt64 is a typed binary op that operates on int64 without boxing. opCode is resolved lazily through the same double-checked atomic.Bool guard as BinOpFloat64 — see that type for why sync.Once doesn't fit the inliner and this does.

func (*BinOpInt64) Eval

func (e *BinOpInt64) Eval(b *batch.RecordBatch, row int) any

func (*BinOpInt64) EvalFloat64

func (e *BinOpInt64) EvalFloat64(b *batch.RecordBatch, row int) (float64, bool)

EvalFloat64 allows BinOpInt64 to be used as Float64Expr (int→float promotion).

func (*BinOpInt64) EvalInt64

func (e *BinOpInt64) EvalInt64(b *batch.RecordBatch, row int) (int64, bool)

type BinOpNumeric

type BinOpNumeric struct {
	Left, Right numericOperand
	Op          string
	// contains filtered or unexported fields
}

BinOpNumeric is the mode-resolved arithmetic node.

func (*BinOpNumeric) Eval

func (e *BinOpNumeric) Eval(b *batch.RecordBatch, row int) any

func (*BinOpNumeric) EvalDecimalVec added in v0.18.5

func (e *BinOpNumeric) EvalDecimalVec(b *batch.RecordBatch, out *batch.Vector, n int) bool

EvalDecimalVec computes the whole batch into a DECIMAL output vector.

The output's SCALE is read from the vector rather than from this node's own resolved type. They are the same number whenever the planner and the runtime resolved the same operand declarations, which is the ordinary case — and where they are not, the vector's scale is the one the value must be stored at, so computing at it rounds ONCE, in the right place, instead of rounding here and rounding again on the way in.

The precision bound is the vector's own scale plus the carrier's width: a batch.Vector carries no precision (DecimalColumn is Data plus Scale), so the declared bound this node resolved is applied through the checked element path only when the two scales agree. That is the same direction of safety colRefDecimalType takes — a wider bound can only ADMIT a value, never change one — and the boxed path, which the stage DAG always takes, still applies the declared bound in full.

func (*BinOpNumeric) EvalFloat64

func (e *BinOpNumeric) EvalFloat64(b *batch.RecordBatch, row int) (float64, bool)

EvalFloat64 implements Float64Expr for consumers on the float protocol.

func (*BinOpNumeric) EvalInt64

func (e *BinOpNumeric) EvalInt64(b *batch.RecordBatch, row int) (int64, bool)

EvalInt64 implements Int64Expr. Only meaningful in int mode; float mode reports not-ok so callers fall back to EvalFloat64/Eval.

type BoolExpr

type BoolExpr interface {
	EvalBool(b *batch.RecordBatch, row int) bool
}

BoolExpr evaluates a boolean expression (used for WHERE/HAVING/JOIN conditions). SQL's logic is THREE-valued and EvalBool is the two-valued COLLAPSE a filtering context applies: it answers true only for TRUE — FALSE and UNKNOWN rows are both kept out of a WHERE. The third value is carried by BoolNullExpr, and the two protocols must agree: EvalBool ≡ (val && !null) of EvalBoolNull.

type BoolNullExpr

type BoolNullExpr interface {
	EvalBoolNull(b *batch.RecordBatch, row int) (val, null bool)
}

BoolNullExpr is the three-valued boolean protocol (#370): val is the answer and null reports UNKNOWN, in which case val is meaningless. Every boolean operator implements it — it is what lets NOT distinguish UNKNOWN (stays UNKNOWN, row excluded) from FALSE (becomes TRUE, row kept), and what a projection boxes into SQL NULL.

type Case

type Case struct {
	Operand Expr       // optional: CASE <operand> WHEN ...
	Whens   []CaseWhen // WHEN condition THEN result
	Else    Expr       // optional ELSE clause
	// contains filtered or unexported fields
}

Case is a CASE WHEN ... THEN ... ELSE ... END expression.

func (*Case) Eval

func (e *Case) Eval(b *batch.RecordBatch, row int) any

type CaseWhen

type CaseWhen struct {
	Cond   Expr // the condition (or value to compare against operand)
	Result Expr
}

CaseWhen is a single WHEN clause in a CASE expression.

type Cast

type Cast struct {
	Operand  Expr
	DestType string // "int", "float", "string", "date", "timestamp"
	// contains filtered or unexported fields
}

Cast wraps an expression with explicit type conversion.

func (*Cast) Eval

func (e *Cast) Eval(b *batch.RecordBatch, row int) any

type Cmp

type Cmp struct {
	Left, Right Expr
	Op          CmpOp
	// contains filtered or unexported fields
}

Cmp is a comparison expression.

func NewCmp added in v0.18.1

func NewCmp(left, right Expr, op CmpOp) *Cmp

NewCmp builds a comparison, binding the two operand shapes that cannot be answered from the boxed values: a DECIMAL column against a numeric literal, and two DECIMAL columns against each other.

func (*Cmp) Eval

func (e *Cmp) Eval(b *batch.RecordBatch, row int) any

func (*Cmp) EvalBool

func (e *Cmp) EvalBool(b *batch.RecordBatch, row int) bool

func (*Cmp) EvalBoolNull

func (e *Cmp) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

type CmpFloat64

type CmpFloat64 struct {
	Left, Right Float64Expr
	Op          CmpOp
}

CmpFloat64 is a typed comparison that operates on float64 without boxing.

func (*CmpFloat64) Eval

func (e *CmpFloat64) Eval(b *batch.RecordBatch, row int) any

func (*CmpFloat64) EvalBool

func (e *CmpFloat64) EvalBool(b *batch.RecordBatch, row int) bool

func (*CmpFloat64) EvalBoolNull

func (e *CmpFloat64) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

EvalBoolNull: a not-ok typed operand is a NULL (the operands here are provably float-typed at compile time), so the comparison is UNKNOWN.

type CmpInt64

type CmpInt64 struct {
	Left, Right Int64Expr
	Op          CmpOp
}

CmpInt64 is a typed comparison that operates on int64 without boxing.

func (*CmpInt64) Eval

func (e *CmpInt64) Eval(b *batch.RecordBatch, row int) any

func (*CmpInt64) EvalBool

func (e *CmpInt64) EvalBool(b *batch.RecordBatch, row int) bool

func (*CmpInt64) EvalBoolNull

func (e *CmpInt64) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

EvalBoolNull: a not-ok typed operand is a NULL (the operands here are provably int-typed at compile time), so the comparison is UNKNOWN.

type CmpNetworkLit added in v0.18.1

type CmpNetworkLit struct {
	Col  *ColRef
	Lit  string // original literal text (generic-fallback operand)
	Op   CmpOp
	Flip bool // literal was the LEFT operand: evaluate as (lit OP col)
	// contains filtered or unexported fields
}

CmpNetworkLit compares a bare column against a string literal that parses as an IPv4 address, a MAC address, an IPv6 address, or a CIDR network, without per-row parsing or boxing — CmpTemporalLit's counterpart for network types, and for the same reason: ColRef.Eval boxes a TypeIPv4/ TypeMAC column as its raw encoded int64 (the representation arithmetic and column-to-column ordering comparisons depend on — see networkTextFuncs) and a TypeIPv6/TypeCIDR column as rendered TEXT (Vector.GetValue's default case), so `ip_col = '10.0.0.1'` boxed the column as a decimal digit string and the literal as itself, and `ipv6_col < '2001:db8::10'` boxed the column's address as text and compared it LEXICALLY against the literal — neither is the address's own order (issues found via README verification and #492). Column type is unknown at compile time, so the literal is pre-parsed into every encoding here and the right one picked per batch from the column's resolved type; a non-network column (or a network column whose type doesn't match the literal's parse) delegates to the generic compare() with the original operand order, keeping semantics bit-identical with Cmp in every sub-case — matching CmpTemporalLit's own contract. Comparing the pre-parsed encodings (not as formatted strings) is also what keeps ordering (<, >) correct: IPv4's big-endian uint32, MAC's packed 48 bits, and IPv6's raw 16 bytes all sort the same as the address itself, and CIDR's structural key (kernel.CidrSortKey) sorts the same as PostgreSQL's inet order — where a dotted-quad, colon-hex, or CIDR-notation STRING would sort lexically and disagree with it (e.g. "9.0.0.1" > "10.0.0.1" as text).

func (*CmpNetworkLit) Eval added in v0.18.1

func (e *CmpNetworkLit) Eval(b *batch.RecordBatch, row int) any

func (*CmpNetworkLit) EvalBool added in v0.18.1

func (e *CmpNetworkLit) EvalBool(b *batch.RecordBatch, row int) bool

func (*CmpNetworkLit) EvalBoolNull added in v0.18.1

func (e *CmpNetworkLit) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

type CmpOp

type CmpOp int

CmpOp represents a comparison operator.

const (
	CmpEq CmpOp = iota
	CmpNe
	CmpLt
	CmpLe
	CmpGt
	CmpGe
)

type CmpTemporalLit

type CmpTemporalLit struct {
	Col  *ColRef
	Lit  string // original literal text (generic-fallback operand)
	Op   CmpOp
	Flip bool // literal was the LEFT operand: evaluate as (lit OP col)
	// contains filtered or unexported fields
}

CmpTemporalLit compares a bare column against a string literal that parses as a date/timestamp, without per-row parsing, cache lookups, or boxing — the generic path spent 3.2% of SF100 worker CPU inside the date-parse memo's sync.Map.Load (interface-key hashing dominated; 2026-07-25 re-rank). The literal is parsed once into BOTH temporal units at compile time; the unit is chosen from the column's resolved type per batch. Every non-fast sub-case (non-temporal column, the epoch-zero literal guard) delegates to the generic compare() with the original operand order, keeping semantics bit-identical with Cmp.

func (*CmpTemporalLit) Eval

func (e *CmpTemporalLit) Eval(b *batch.RecordBatch, row int) any

func (*CmpTemporalLit) EvalBool

func (e *CmpTemporalLit) EvalBool(b *batch.RecordBatch, row int) bool

func (*CmpTemporalLit) EvalBoolNull

func (e *CmpTemporalLit) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

type Coalesce

type Coalesce struct {
	Args []Expr
	// contains filtered or unexported fields
}

Coalesce returns the first non-null argument.

func (*Coalesce) Eval

func (e *Coalesce) Eval(b *batch.RecordBatch, row int) any

type ColEmptyStr

type ColEmptyStr struct {
	Col      *ColRef
	Not      bool // true for <>
	Fallback *Cmp
}

ColEmptyStr evaluates a column compared for equality or inequality against the empty string literal as a zero-length offsets test. Restricted to TypeString: that is the only type for which the generic Cmp path compares ColRef.Eval's boxed string against "" (a TypeBytes column boxes []byte, which compare() handles through a different branch, and the network/UUID types render their bytes).

NULL handling matches Cmp exactly: a NULL operand makes the comparison UNKNOWN for BOTH = and <> — nil on the boxed path, excluded by EvalBool.

func (*ColEmptyStr) Eval

func (e *ColEmptyStr) Eval(b *batch.RecordBatch, row int) any

func (*ColEmptyStr) EvalBool

func (e *ColEmptyStr) EvalBool(b *batch.RecordBatch, row int) bool

func (*ColEmptyStr) EvalBoolNull

func (e *ColEmptyStr) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

type ColIsNull

type ColIsNull struct {
	Col      *ColRef
	Not      bool
	Fallback *IsNull
}

ColIsNull evaluates `col IS [NOT] NULL` off the null bitmap for a byte-array column, where the generic IsNull node boxes (and for TypeString copies) the value only to test it against nil.

Scoped to TypeString/TypeBytes deliberately: ColRef.Eval returns nil for exactly the null rows of those two types (TypeString via GetString's ok flag, TypeBytes via GetValue's leading null check), so the rewrite is value-identical. Other types keep the generic node.

func (*ColIsNull) Eval

func (e *ColIsNull) Eval(b *batch.RecordBatch, row int) any

func (*ColIsNull) EvalBool

func (e *ColIsNull) EvalBool(b *batch.RecordBatch, row int) bool

func (*ColIsNull) EvalBoolNull

func (e *ColIsNull) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

EvalBoolNull: IS [NOT] NULL never answers UNKNOWN.

type ColRef

type ColRef struct {
	Name string
	// contains filtered or unexported fields
}

ColRef reads a column value from the batch. Caches the column index and type after first resolution for zero-allocation reads on numeric types. Parallel pipeline workers share one *ColRef through the captured expression closures, so the resolution writes are published under a lock and read behind resolved.

func (*ColRef) Eval

func (e *ColRef) Eval(b *batch.RecordBatch, row int) any

func (*ColRef) EvalFloat64

func (e *ColRef) EvalFloat64(b *batch.RecordBatch, row int) (float64, bool)

EvalFloat64 reads the column value as float64 without any boxing. Returns (0, false) if null or column not found. Uses cached column type to dispatch directly to the typed data slice, avoiding the extra function call and redundant type switch in GetNumericFloat64.

func (*ColRef) EvalFloat64Vec

func (e *ColRef) EvalFloat64Vec(b *batch.RecordBatch, dst []float64, n int) bool

EvalFloat64Vec evaluates the column for all rows [0, n) into dst.

func (*ColRef) EvalInt64

func (e *ColRef) EvalInt64(b *batch.RecordBatch, row int) (int64, bool)

EvalInt64 reads the column value as int64 without boxing.

func (*ColRef) EvalString

func (e *ColRef) EvalString(b *batch.RecordBatch, row int) (string, bool)

EvalString reads the column value as string without boxing.

type ColShapeLen

type ColShapeLen struct {
	Col      *ColRef
	Mul      int // 1 for length/octet_length, 8 for bit_length
	Fallback *FuncCall
}

ColShapeLen evaluates length()/octet_length()/bit_length() over a bare column reference by subtracting offsets, never materializing the value. Any column whose stored bytes are not the value ColRef.Eval would box (numeric, temporal, network-rendered, ROW field access) delegates to the generic FuncCall it replaced, so results are unchanged.

func (*ColShapeLen) Eval

func (e *ColShapeLen) Eval(b *batch.RecordBatch, row int) any

func (*ColShapeLen) EvalFloat64

func (e *ColShapeLen) EvalFloat64(b *batch.RecordBatch, row int) (float64, bool)

func (*ColShapeLen) EvalFloat64Vec

func (e *ColShapeLen) EvalFloat64Vec(b *batch.RecordBatch, dst []float64, n int) bool

EvalFloat64Vec fills dst for rows [0, n), reporting whether any row was null (the VecFloat64Expr contract: the caller re-runs EvalFloat64 per row to set the null bits when this returns true).

func (*ColShapeLen) EvalInt64

func (e *ColShapeLen) EvalInt64(b *batch.RecordBatch, row int) (int64, bool)

func (*ColShapeLen) EvalVec

func (e *ColShapeLen) EvalVec(b *batch.RecordBatch, out *batch.Vector, n int)

EvalVec fills out for the whole batch. Mirrors FuncCall.EvalVec's contract: writes Int32Data (the length family is int4, #530), marking nulls in out.Nulls.

type CompileOption added in v0.18.11

type CompileOption func(*compileContext)

A CompileOption is an optional input to the Compile* entry points. It is variadic so that adding one costs no caller a signature change — the entry-point set is already six wide and each new question would otherwise double it.

func WithBudget added in v0.18.19

func WithBudget(budget MemoryAccountant, release func(*InSubquery)) CompileOption

WithBudget charges an uncorrelated InSubquery's membership set to the caller's memory tracker (ADR-0006, #528, #531), and hands the caller each such node so it can Release the charge when the compiled tree's life ends.

It is an OPTION rather than a seventh CompileWith* function because the options carry things a compile site already needs: swapping a call site to an entry point that takes a budget and nothing else silently drops WithSubqueryDeclTypes, and a scalar subquery then compares by the bytes of its box again (#696). Every existing entry point takes opts; this composes with them.

release is REQUIRED and the option refuses a nil one, because the failure it prevents is worse than the bug it fixes: an InSubquery holds its membership map for the life of the compiled tree, so charging without a teardown turns an unaccounted map into a permanently-charged one, and a task that plans several of them runs out of budget for work that has already finished. InSubquery.Release is idempotent and safe on a node that never resolved.

What this does NOT do is bound the ALLOCATION. chargeMemory runs after resolveSlow has built the map, so it makes the set visible to the budget and turns a set that is over budget on its own into a query error; it does not stop a subquery large enough to exhaust the machine from doing so. See chargeMemory's doc.

func WithSetRowBound added in v0.18.33

func WithSetRowBound(n int) CompileOption

WithSetRowBound bounds the membership set an IN-subquery may build, in rows, and refuses past it rather than truncating: a set short by one row is a different answer, and on a write door it deletes the wrong rows.

It is the DML doors' knob (`WADJET_IN_SET_MAX`, the same number the query path gives its own inlining) and it is applied HERE, at the construct, and not in the runner those doors hand the compiler — a runner sees SQL text and cannot tell which construct asked, so bounding there charged EXISTS and a scalar subquery for rows neither reads. n <= 0 leaves the set unbounded.

func WithSubqueryDeclTypes added in v0.18.11

func WithSubqueryDeclTypes(f SubqueryDeclFunc) CompileOption

WithSubqueryDeclTypes supplies the resolver described on compileContext.subqueryDecl (#696).

type Confidence

type Confidence uint8

Confidence says how a resolved type was arrived at: whether the declaration DECIDED it or only GUESSED it. A same-as-argument declaration has to answer even when none of its candidate arguments decided anything, and that answer — its fallback — is a guess. Reporting a guess as fact is what typed

SELECT COALESCE(NULLIF(n_name, 'ALGERIA'), 'fallback') FROM nation

Float64, so every row came back as the integer 0: nullif's argument 0 is a bare column, which decides nothing by design (its type comes from the input schema at runtime), so nullif fell back to its numeric default — and coalesce took that for a decision, stopped, and never consulted the string literal in argument 1 that would have decided it correctly (#331).

The fallback itself is right where there is nothing better: NULLIF(int_col, 1) as a projection is numeric and stays numeric. What Confidence adds is that a caller holding another candidate can tell the two apart.

const (
	// Undecided: nothing here names a type, and the caller keeps its own
	// fallback. RetDynamic answers this way, as does an unregistered name.
	Undecided Confidence = iota
	// Guessed: a polymorphic declaration reached its fallback because no
	// candidate argument decided. Still an answer — it is THE answer at top
	// level — but a caller with a candidate of its own left to ask must
	// prefer that candidate's decision over this.
	Guessed
	// Decided: the declaration names this type outright, or a candidate
	// argument decided it.
	Decided
)

func (Confidence) String

func (c Confidence) String() string

type CorrelatedExistsSubquery

type CorrelatedExistsSubquery struct {
	Runner          SubqueryRunner
	Not             bool
	OuterRefs       []plansql.OuterRef
	OuterTables     map[string]bool
	ParsedInfo      *plansql.SelectInfo
	UnqualOuterCols map[string]string
}

CorrelatedExistsSubquery evaluates a correlated EXISTS subquery per-row.

func (*CorrelatedExistsSubquery) Eval

func (*CorrelatedExistsSubquery) EvalBool

func (e *CorrelatedExistsSubquery) EvalBool(b *batch.RecordBatch, row int) bool

type CorrelatedInSubquery

type CorrelatedInSubquery struct {
	Expr            Expr
	Runner          SubqueryRunner
	Not             bool
	OuterRefs       []plansql.OuterRef
	OuterTables     map[string]bool
	ParsedInfo      *plansql.SelectInfo
	UnqualOuterCols map[string]string
	// SetBound bounds the membership set in ROWS, refusing past it rather
	// than truncating — a set short by one row is a different answer, and on
	// a write door it deletes the wrong rows. Zero is unbounded.
	//
	// It lives on this construct and not in the runner because a runner sees
	// SQL text and cannot tell which construct asked for it. IN is the one
	// that wants a SET; EXISTS wants a row and a scalar subquery is an error
	// past one, and both read a bounded number of rows by construction now
	// (plansql.AppendRowLimit). Bounding all three in the runner charged
	// those two for a set neither builds.
	SetBound int
}

CorrelatedInSubquery checks if a value is in the result set of a correlated subquery.

func (*CorrelatedInSubquery) Eval

func (e *CorrelatedInSubquery) Eval(b *batch.RecordBatch, row int) any

func (*CorrelatedInSubquery) EvalBool

func (e *CorrelatedInSubquery) EvalBool(b *batch.RecordBatch, row int) bool

func (*CorrelatedInSubquery) EvalBoolNull

func (e *CorrelatedInSubquery) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

EvalBoolNull carries SQL's three-valued IN (#370): a NULL probe is UNKNOWN, and a miss against a result set containing a NULL is UNKNOWN — the NOT IN trap, same rule as the uncorrelated InSubquery.

type CorrelatedScalarSubquery

type CorrelatedScalarSubquery struct {
	Runner          SubqueryRunner
	OuterRefs       []plansql.OuterRef // correlated column references
	OuterTables     map[string]bool    // outer table aliases
	ParsedInfo      *plansql.SelectInfo
	UnqualOuterCols map[string]string // unqualified column → table mapping for outer refs
	// Decl is the DECLARED type of the subquery's single output column, and
	// DeclKnown says whether anything resolved it — ScalarSubquery's fields,
	// for the same reason and read by the same classifyOperand arm (#696,
	// #666). A correlated subquery re-runs per row and its declared TYPE does
	// not change with the row, so it is resolved once at compile time exactly
	// as the uncorrelated one is.
	//
	// Without it `d.a > (SELECT AVG(x.a) FROM decpair x WHERE x.id <> d.id)`
	// compared a DECIMAL column against a boxUnknown operand — that is, by the
	// BYTES of its rendered text — and answered 0 rows for PostgreSQL's 4.
	Decl                   batch.TypeID
	DeclKnown              bool
	DecPrecision, DecScale int
}

CorrelatedScalarSubquery evaluates a correlated scalar subquery per-row. Unlike ScalarSubquery, it cannot cache the result because the inner query depends on values from the outer row.

func (*CorrelatedScalarSubquery) Eval

type DanglingSubqueryError added in v0.18.16

type DanglingSubqueryError struct {
	Kind string
	SQL  string
	Refs []plansql.OuterRef
}

DanglingSubqueryError reports a subquery about to be executed STANDALONE whose text still carries a qualified column reference that no FROM clause inside it provides.

That is a correlated subquery the classifier did not recognize as one. Run standalone it does not fail: `expr.ResolveColumnRef` STRIPS the qualifier and retries the bare name, so `sub.g = typemx.g` rebinds to the inner relation's own column and reads constant TRUE, and `y.id = x.id * 2` — where the inner relation has no `id * 2` to rebind to — reads constant FALSE. One misclassification, two different confident wrong answers, decided by whether the two relations happen to share a column name (#734, #679, #535).

plansql.DanglingTableRefs needs no outer scope to see this, which is what lets the check live HERE, at the site that has lost it, rather than depending on the classifier being repaired first.

func (*DanglingSubqueryError) Error added in v0.18.16

func (e *DanglingSubqueryError) Error() string

func (*DanglingSubqueryError) FatalEvalError added in v0.18.16

func (e *DanglingSubqueryError) FatalEvalError() error

FatalEvalError satisfies the marker the pipeline drivers recover on.

func (*DanglingSubqueryError) SQLState added in v0.18.16

func (e *DanglingSubqueryError) SQLState() string

SQLState is PostgreSQL's feature_not_supported. The query is legal SQL that this engine cannot lower, which is what 0A000 says; 42703 would claim the column does not exist, and it does — in the outer query.

type DecimalVecExpr added in v0.18.5

type DecimalVecExpr interface {
	// EvalDecimalVec writes the batch and reports whether it did. FALSE means
	// the exact mode does not apply to this batch after all — the planner
	// declared DECIMAL from the AST and the runtime resolved the operands
	// differently — and the caller must fall back to the boxed checked
	// writer. Writing nothing and saying nothing would leave the output
	// vector's zeros standing, which reads back as the value 0 on every row.
	EvalDecimalVec(b *batch.RecordBatch, out *batch.Vector, n int) bool
}

DecimalVecExpr is an expression that can write EXACT fixed-point results into a DECIMAL output vector for a whole batch at once.

It is separate from VecExpr because exec.Project's DECIMAL arm runs ahead of every vectorized path — the checked per-row writer is the only route with an error channel, and no other vec kernel writes DecimalData. A distinct interface lets the one kernel that does write it skip the box without changing that ordering for anything else.

type DeclType added in v0.18.5

type DeclType struct {
	ID        batch.TypeID
	Precision int
	Scale     int
	DecKnown  bool
	// Untyped marks SQL's `unknown`: an operand that names no type AND
	// produces no value of its own — a bare NULL literal, and nothing else.
	// It rides alongside Undecided because the two are not the same fact and
	// CommonDeclType has to tell them apart: an operand that decided nothing
	// but WILL produce a value at runtime (a scalar subquery, element_at over
	// a container) makes a DECIMAL fold unsafe, because that value arrives at
	// ITS OWN scale and the fold would declare a different one; a NULL never
	// arrives at all, so COALESCE(d, NULL) is a DECIMAL expression exactly as
	// PostgreSQL says it is.
	Untyped bool

	// Exact is the fixed-point (p,s) a NON-DECIMAL numeric operand
	// contributes to a DECIMAL fold, and ExactSet says it has one. It is
	// carried apart from Precision/Scale on purpose: those two ARE the
	// declaration when ID is DECIMAL, and declTypeParts writes them into
	// projection, sort-key and window-key specs, where a precision on an
	// INT64 column would be read as a DECIMAL's.
	//
	// The one operand that needs it is a numeric LITERAL, whose own
	// declaration is INT64 or FLOAT64 (`SELECT 1.5` is a double — ADR-0024's
	// recorded deferral) while its fixed-point contribution is its SPELLING:
	// `0` is DECIMAL(1,0) and `0.5` is DECIMAL(1,1). That is the whole
	// difference between `CASE … THEN d ELSE 0.5 END`, which PostgreSQL types
	// numeric, and `CASE … THEN d ELSE f END` over a FLOAT COLUMN, which it
	// types double precision: both branches declare FLOAT64 here, and only
	// this says which of them is an exact number the user wrote.
	//
	// An INTEGER COLUMN needs no field — its contribution is its whole range
	// at scale 0, a function of the TypeID alone (batch.DecimalTypeOf).
	Exact    batch.DecimalType
	ExactSet bool

	// Lit marks a declaration that came from a CONSTANT rather than from a
	// column, a cast or a computed expression, and FoldID is the type
	// PostgreSQL resolves that constant to inside select_common_type — which
	// is NOT the type it declares on its own here (a bare numeric literal
	// declares INT64 or FLOAT64, ADR-0024's recorded deferral, while
	// PostgreSQL calls `0` an integer and `1.5` a numeric).
	//
	// The two are separate because only the FOLD needs PostgreSQL's rung.
	// `CASE … THEN i32_col ELSE 0 END` is `integer` there, and reading the
	// literal at its own INT64 declaration would widen the call to bigint —
	// a divergence in the OID for a shape TPC-H is full of. FoldID is zero
	// when this layer cannot name the constant's rung, and the ID then
	// stands: that is the wide-literal deferral #555 records.
	Lit    bool
	FoldID batch.TypeID
	// Quoted marks SQL's `unknown`: a QUOTED string literal.
	//
	// PostgreSQL types one `unknown` and resolves it FROM the other operands,
	// so it contributes NO rung to a polymorphic call's fold and is coerced
	// to whatever that fold resolves. Typing it a DECIDED string put a
	// non-numeric decider in every call that held one, CommonDeclType could
	// not fold, and the call fell back to its FIRST argument — a declaration
	// NARROWER than the value the call produces, which the output vector then
	// wrapped rather than narrowed: `GREATEST(bigint, real, double, '1e39')`
	// is double precision in PostgreSQL and was int64's MINIMUM here (#724).
	//
	// The ID stays TypeString, because that IS the answer when nothing else
	// decides: PostgreSQL resolves a composite whose every argument is a
	// quoted literal to `text`, and `SELECT 'x'` is a text column.
	Quoted bool
}

DeclType is a resolved declared type: the vector type a value can be stored in, plus — for a DECIMAL — the (precision, scale) a bare TypeID cannot express.

It is ONE shape, deliberately, and it is the shape the whole declared-type inference layer speaks: expr.Ret.Resolve here, and physical's nodeDeclaredType / colRefDeclaredType / funcReturnType / caseDeclaredType / windowSpecOutputType / declaredProjectionDecl on the planner side. Before ADR-0024 that layer was (batch.TypeID, Confidence) with no room for (p,s), so colRefDeclaredType answered Undecided for every DECIMAL column and everything downstream fell to its non-DECIMAL default — which is #529 (GREATEST/LEAST over DECIMAL), #555 (COALESCE), #586/#587 (window) and #542 (set operations), one defect wearing five hats.

DecKnown distinguishes a resolved (p,s) from the zero value, which a COMPUTED decimal legitimately has none of (#458) — the same shape ProjectExprSpec.TypeKnown and AggSpec.OutputTypeKnown carry, and for the same reason: precision 0 is a sentinel a caller must not take at face value.

func CommonDeclType added in v0.18.5

func CommonDeclType(decided []DeclType, sawUnknown bool) (DeclType, bool)

CommonDeclType answers a polymorphic declaration from the argument types that DECIDED one. It is the shared rule for every construct that CHOOSES BETWEEN operands — COALESCE/NULLIF/IFNULL/IF/GREATEST/LEAST here, and CASE's branches in the physical planner, which calls this so the two can never disagree.

ok=false means DECLINE: the caller must answer as if nothing had decided, which is what it did before a DECIMAL operand could decide anything.

The NUMERIC deciders fold through PostgreSQL's select_common_type ladder — INT32 → INT64 → DECIMAL → FLOAT32 → FLOAT64 — and not through "the first decider wins", which is what this did until #724. The difference is a VALUE, not an OID: `GREATEST(bigint, real, double)` is double precision in PostgreSQL, and declaring it bigint from argument 0 does not narrow the double the call produces, it WRAPS it — 1e39 stored into an int64 vector is int64's MINIMUM, #462's failure mode. The ladder is verified live on postgres:17-alpine for every ordered pair of the six numeric widths and is the same one setOpWiden pins for set operations and joinFoldKinds runs over the compiled tree.

A DECIMAL is not a type on its own: COALESCE over DECIMAL(9,2) and DECIMAL(18,4) has to answer a type that holds BOTH, or the narrower declaration truncates the wider argument's digits on the way into the output vector. So when the ladder lands on DECIMAL, every decider's fixed-point contribution is folded through batch.DecimalCommon — the same rule a set operation reconciles its arms with (ADR-0024 item 2).

A QUOTED literal contributes NO rung. PostgreSQL types one `unknown` and resolves it from the other operands, which is exactly what DeclType.Quoted says here; a composite whose every argument is quoted is `text` there and answers TypeString here.

sawUnknown is the safety clause and it is not optional. A branch that decided nothing still PRODUCES a value at runtime — a scalar subquery, a container element, anything this layer cannot type — and a DECIMAL one arrives as text at ITS OWN scale, not at the fold's. Folding only the branches that spoke declared DECIMAL(9,2) for `COALESCE(a, (SELECT MAX(b) FROM t))`, which then TRUNCATED the subquery's 12.7501 to 12.75 and, at the comparison sites, left the operand unclassifiable so the extremum was picked by BYTE order. A declined fold answers exactly what it answered before ADR-0024 — a loud mismatch or the STRING fallback — which is the only honest answer while the operand has no declaration to fold in.

A DECIMAL beside an INTEGER — a column, or a numeric literal — resolves to numeric in PostgreSQL, and does here (#695, verified live on 17.11: `pg_typeof(CASE WHEN true THEN 1.5::numeric(15,2) ELSE 0 END)` is numeric, and so are COALESCE/GREATEST/LEAST/NULLIF over the same pair). The integer contributes its fixed-point form to the fold — its whole range at scale 0 for a COLUMN, its own spelling for a LITERAL (DeclType.Exact) — and the value materializes through the exact-TEXT box every DECIMAL producer here answers with, never as the already-scaled carrier an integer box means to SetValue (ADR-0018 §4). That was the deferral this function carried until #695: `GREATEST(dec_col, 100)` declared INT64, answered 100 on every row the integer won, and failed at the #361 store guard on the first row the decimal won — data-dependent, which is why it could not stand.

A DECIMAL beside a FLOAT is the float, which is PostgreSQL's rule (both float types are preferred in the numeric category, and only float8 beats float4) — in EITHER argument order now. `COALESCE(numeric, real)` answered real before #724 and `COALESCE(real, numeric)` answered real too, but `GREATEST(numeric(15,2), c_i64, real)` answered bigint, because the first non-DECIMAL decider was the bigint. The rows the DECIMAL arm wins hand over that branch's TEXT, which the float vector then has to read: choice_decimal.go does that at the box, so the declaration and the value agree (#555's float half).

func Decl added in v0.18.5

func Decl(t batch.TypeID) DeclType

Decl builds a declaration for a type that needs no parameters.

func DeclDecimal added in v0.18.5

func DeclDecimal(prec, scale int) DeclType

DeclDecimal builds a DECIMAL declaration with its (precision, scale).

func DeclNumericLit added in v0.18.5

func DeclNumericLit(id batch.TypeID, text string) DeclType

DeclNumericLit builds the declaration of a numeric LITERAL: the type it declares on its own (INT64 for integer digits, FLOAT64 otherwise — ADR-0024's recorded deferral) plus the exact fixed-point (p,s) of its spelling, which is what a DECIMAL fold over it resolves against.

func DeclQuotedLit added in v0.18.6

func DeclQuotedLit(text string) DeclType

DeclQuotedLit is a QUOTED string literal's declaration: SQL's `unknown`.

It names TypeString — which is what the literal is when nothing else in the expression names a type, and what `SELECT 'x'` must allocate — and marks itself Quoted so a polymorphic fold resolves it FROM its neighbours the way PostgreSQL does, instead of letting it decide the whole call's type (#724).

It carries the spelling's fixed-point (p,s) for the same reason a numeric literal does: a fold that lands on DECIMAL has to declare a width that holds the literal too, or the value the call produces on the rows the literal wins does not survive the store.

func DeclUntyped added in v0.18.5

func DeclUntyped() DeclType

DeclUntyped is SQL's `unknown`: a NULL literal, which contributes no type and produces no value. Answered with Undecided confidence, like anything else that names no type.

func (DeclType) Dec added in v0.18.5

func (d DeclType) Dec() batch.DecimalType

Dec returns the (precision, scale) as the rules in batch take them.

func (DeclType) String added in v0.18.5

func (d DeclType) String() string

String renders the declaration the way a type is written in SQL: the type name, with a DECIMAL's (p,s) when it has one.

type ExistsSubquery

type ExistsSubquery struct {
	SQL    string
	Runner SubqueryRunner
	Not    bool
	// contains filtered or unexported fields
}

ExistsSubquery evaluates to true if a subquery returns any rows. Example: WHERE EXISTS (SELECT 1 FROM orders WHERE orders.user_id = users.id) Uncorrelated: executed once and result cached.

func (*ExistsSubquery) Eval

func (e *ExistsSubquery) Eval(b *batch.RecordBatch, row int) any

func (*ExistsSubquery) EvalBool

func (e *ExistsSubquery) EvalBool(_ *batch.RecordBatch, _ int) bool

type Expr

type Expr interface {
	Eval(b *batch.RecordBatch, row int) any
}

Expr evaluates an expression against a record batch row, returning a typed value.

func Compile

func Compile(node plansql.Node) (Expr, error)

Compile converts our AST Node into an Expr tree.

func CompileSelectExpr

func CompileSelectExpr(expr plansql.Node, alias string) (Expr, string, error)

CompileSelectExpr compiles a SELECT column expression from our AST. Returns the compiled expression and the output column name.

func CompileWithBudget added in v0.18.3

func CompileWithBudget(node plansql.Node, runner SubqueryRunner, outerTables map[string]bool, outerCols map[string]string, innerCols plansql.TableColumns, budget MemoryAccountant, release func(*InSubquery), opts ...CompileOption) (Expr, error)

CompileWithBudget is CompileWithScopeResolver plus the WithBudget option, kept as the shape this package's own tests compile through. Production wires the budget through WithBudget on whichever entry point the call site already uses, so the compile keeps the other options it was passing (#531).

budget may be nil, which keeps the pre-#528 unbudgeted behavior every other CompileWith* entry point still has; any *memory.Tracker satisfies MemoryAccountant structurally; see that type's doc for why this package does not import internal/engine/memory to accept one.

outerTables, outerCols and innerCols may be nil for a top-level, non-correlated compile.

release is the teardown hook WithBudget requires, and this entry point does not get to skip it: injecting a no-op here would be the exact state WithBudget refuses to construct, spelled differently. It may be nil only alongside a nil budget, where nothing is charged.

func CompileWithColumnTypes added in v0.18.5

func CompileWithColumnTypes(node plansql.Node, runner SubqueryRunner, colTypes map[string]batch.TypeID, opts ...CompileOption) (Expr, error)

CompileWithColumnTypes compiles with the input's DECLARED column types in hand. See compileContext.colTypes: the types answer one compile-time question — whether an operand pair could be exact fixed-point — which without them has to be deferred to the first batch, at the cost of the vectorized float path for every pair that turns out not to be.

func CompileWithFullScope

func CompileWithFullScope(node plansql.Node, runner SubqueryRunner, outerTables map[string]bool, outerCols map[string]string) (Expr, error)

CompileWithFullScope is like CompileWithScope but also accepts a column-to-table mapping for resolving unqualified column references in correlated subqueries.

func CompileWithRunner

func CompileWithRunner(node plansql.Node, runner SubqueryRunner, opts ...CompileOption) (Expr, error)

CompileWithRunner converts our AST Node into an Expr tree, with support for subquery expressions (scalar subqueries, IN subquery, EXISTS).

func CompileWithScope

func CompileWithScope(node plansql.Node, runner SubqueryRunner, outerTables map[string]bool, opts ...CompileOption) (Expr, error)

CompileWithScope converts our AST Node into an Expr tree with full scope information, enabling correlated subquery detection and per-row execution. outerTables contains the table names and aliases from the outer query.

func CompileWithScopeResolver

func CompileWithScopeResolver(node plansql.Node, runner SubqueryRunner, outerTables map[string]bool, outerCols map[string]string, innerCols plansql.TableColumns, opts ...CompileOption) (Expr, error)

CompileWithScopeResolver is CompileWithFullScope plus a resolver for the column namespace of a subquery's own FROM clause. It is what makes an unqualified name inside a subquery bind to the subquery first, so a name that merely also exists in the outer query does not turn an uncorrelated subquery into a per-row correlated one (issue #334). A nil resolver keeps the weaker table-identifier heuristic.

type Float64Expr

type Float64Expr interface {
	EvalFloat64(b *batch.RecordBatch, row int) (float64, bool)
}

Float64Expr evaluates to float64 without boxing.

type FuncCall

type FuncCall struct {
	Name string
	Args []Expr
	// contains filtered or unexported fields
}

FuncCall represents a scalar function call.

Note: this struct holds NO per-call mutable state. A previous version cached an args buffer on the receiver to avoid per-call allocation, but that was unsafe under parallel pipeline execution: aggPreProject closures (and other wrapped-expression paths) capture the same *FuncCall by pointer rather than cloning it per worker, so concurrent goroutines stomped on the shared args buffer and produced non-deterministic Q02 row counts at SF0.01 (and worse at SF100). The vecFn / prepared lookup caches are still guarded by sync.Once (resolved once per BATCH, off EvalVec — not once per row, so the guard never sat in a row loop). fn is different: it is resolved off Eval, the per-row entry point every one of the 273 scalar functions reaches, so it uses the same double-checked atomic.Bool guard as BinOpFloat64/BinOpInt64's opCode and BinOpNumeric's mode — small enough that resolveFn inlines into Eval (verified with -gcflags='-m'), where sync.Once.Do's closure-plus-load did not.

func (*FuncCall) Eval

func (e *FuncCall) Eval(b *batch.RecordBatch, row int) any

func (*FuncCall) EvalVec

func (e *FuncCall) EvalVec(b *batch.RecordBatch, out *batch.Vector, n int)

EvalVec evaluates the function for an entire batch, writing results to out. Falls back to per-row Eval if no vectorized implementation exists or if argument types can't be resolved to column vectors.

type FuncRegistry

type FuncRegistry struct {
	// contains filtered or unexported fields
}

FuncRegistry is a concurrent-safe registry of scalar functions.

func NewFuncRegistry

func NewFuncRegistry() *FuncRegistry

NewFuncRegistry creates a new empty function registry.

func (*FuncRegistry) Has

func (r *FuncRegistry) Has(name string) bool

Has returns true if a function with the given name exists.

func (*FuncRegistry) Lookup

func (r *FuncRegistry) Lookup(name string) ScalarFunc

Lookup returns the function with the given name, or nil if not found.

func (*FuncRegistry) LookupVec

func (r *FuncRegistry) LookupVec(name string) VecScalarFunc

LookupVec returns the vectorized function with the given name, or nil if not found.

func (*FuncRegistry) Names

func (r *FuncRegistry) Names() []string

Names returns all registered function names.

func (*FuncRegistry) Register

func (r *FuncRegistry) Register(name string, fn ScalarFunc, ret Ret)

Register adds or replaces a scalar function. ret declares the type the function's results are stored as; the planner types projections from it (see Ret). Registering without a declaration does not compile, and registering the zero value panics here rather than letting a mistyped output vector reach a kernel.

func (*FuncRegistry) RegisterVec

func (r *FuncRegistry) RegisterVec(name string, fn VecScalarFunc)

RegisterVec adds a vectorized implementation for a scalar function. A vec kernel writes a typed slice of the output vector, so the function it accelerates must already be registered with the return type that names that slice — registering a kernel for an undeclared function is the exact setup that panicked the server four times, and panics here instead.

func (*FuncRegistry) RegisterVecReturn

func (r *FuncRegistry) RegisterVecReturn(name string, dimFn func() int)

RegisterVecReturn marks a function as returning a VECTOR. dimFn is evaluated lazily (at plan time) to obtain the output dimensionality — embed(), for example, derives it from the configured embedding provider.

func (*FuncRegistry) ReturnType

func (r *FuncRegistry) ReturnType(name string) Ret

ReturnType returns the declared return type of a function. An unregistered name yields the zero Ret, which reports Declared() == false and resolves to "caller keeps its fallback".

func (*FuncRegistry) Unregister

func (r *FuncRegistry) Unregister(name string) bool

Unregister removes a scalar function. Returns true if it existed.

func (*FuncRegistry) VecReturnDim

func (r *FuncRegistry) VecReturnDim(name string) (dim int, ok bool)

VecReturnDim reports whether the named function returns a VECTOR and, if so, its current output dimension. ok is false for non-vector-returning functions.

type In

type In struct {
	Expr   Expr
	Values []Expr
	Not    bool
	// contains filtered or unexported fields
}

In checks if a value is in a set.

func NewIn added in v0.18.1

func NewIn(e Expr, values []Expr, not bool) *In

NewIn builds a set-membership test, binding the DECIMAL-column-against- numeric-literals shape, the FLOAT32-column-against-a-multi-element-list shape, and one boxed pair per member.

func (*In) Eval

func (e *In) Eval(b *batch.RecordBatch, row int) any

func (*In) EvalBool

func (e *In) EvalBool(b *batch.RecordBatch, row int) bool

func (*In) EvalBoolNull

func (e *In) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

EvalBoolNull: `x IN (a, b, NULL)` is the chained OR of comparisons, so a match answers TRUE, and a miss with a NULL anywhere in the list is UNKNOWN — never FALSE. NOT IN is its Kleene negation, which is why `1 NOT IN (2, NULL)` must not answer true: PostgreSQL's reading is "I don't know, so no" (#370).

type InSetTooLargeError added in v0.18.33

type InSetTooLargeError struct {
	SQL   string
	Rows  int
	Bound int
}

InSetTooLargeError reports an IN-subquery whose result is past the row bound the caller set (expr.WithSetRowBound).

It REFUSES rather than truncating because a membership set short by one row is not a smaller answer, it is a different one — and on a write door it deletes the wrong rows. 54000 is program_limit_exceeded, which is what this is: a limit this engine imposes, named in the message so the reader can raise it.

func (*InSetTooLargeError) Error added in v0.18.33

func (e *InSetTooLargeError) Error() string

func (*InSetTooLargeError) FatalEvalError added in v0.18.33

func (e *InSetTooLargeError) FatalEvalError() error

FatalEvalError satisfies the marker the pipeline drivers recover on.

func (*InSetTooLargeError) SQLState added in v0.18.33

func (e *InSetTooLargeError) SQLState() string

SQLState is PostgreSQL's 54000 (program_limit_exceeded).

type InSubquery

type InSubquery struct {
	Expr   Expr
	SQL    string
	Runner SubqueryRunner
	Not    bool
	// Budget charges the membership set resolveSlow builds to the caller's
	// per-task memory tracker (ADR-0006, #528). nil (CompileWithRunner,
	// CompileWithScope, etc.) keeps the map unbudgeted, exactly as before
	// #528 — every shape that decorrelates into a semi join never reaches
	// this type at all (its build side is already budgeted and spillable);
	// only tryDecorrelateInSubquery's DECLINED shapes do, and only a
	// computed inner select item is unbounded (a LIMIT/OFFSET or an
	// ungrouped-aggregate inner item is bounded by construction).
	//
	// Set in production by expr.WithBudget, which the physical planner
	// passes at every compile site that carries a subquery runner (#531).
	// The option also hands the planner each node it budgets, because the
	// release side is the half that matters: a charge with no teardown point
	// makes every uncorrelated IN-subquery in a task hold its bytes for the
	// task's lifetime, and a task that plans several of them runs out of
	// budget for work that has already finished. WithBudget therefore
	// refuses a nil release hook rather than construct that state; the
	// teardown point is PhysicalPlan.Cleanup.
	Budget MemoryAccountant
	// SetBound bounds the membership set in ROWS, refusing past it rather
	// than truncating — a set short by one row is a different answer, and on
	// a write door it deletes the wrong rows. Zero is unbounded.
	//
	// It lives on this construct and not in the runner because a runner sees
	// SQL text and cannot tell which construct asked for it. IN is the one
	// that wants a SET; EXISTS wants a row and a scalar subquery is an error
	// past one, and both read a bounded number of rows by construction now
	// (plansql.AppendRowLimit). Bounding all three in the runner charged
	// those two for a set neither builds.
	SetBound int
	// contains filtered or unexported fields
}

InSubquery checks if a value is in the result set of a subquery. Example: WHERE user_id IN (SELECT user_id FROM active_users) Uncorrelated: executed once and result set cached in a hash set for O(1) lookup.

func (*InSubquery) Eval

func (e *InSubquery) Eval(b *batch.RecordBatch, row int) any

func (*InSubquery) EvalBool

func (e *InSubquery) EvalBool(b *batch.RecordBatch, row int) bool

func (*InSubquery) EvalBoolNull

func (e *InSubquery) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

func (*InSubquery) Release added in v0.18.3

func (e *InSubquery) Release()

Release returns any bytes charged to Budget in resolveSlow, and is idempotent — a caller that does not know whether resolveSlow ever ran, or has already called Release, may call it any number of times.

Its caller is the plan that compiled the tree: the physical planner registers every InSubquery compiled under a budget (expr.WithBudget's release hook) and PhysicalPlan.Cleanup releases them, which is the teardown point #531 needed and #528 left open. Wiring the charge WITHOUT one converts an unbudgeted map into a permanently-charged one — a worse failure than the one #528 set out to fix, because the bytes are returned to the OS by GC and never returned to the tracker — which is why WithBudget refuses to construct a budget with a nil release hook.

type Int64Expr

type Int64Expr interface {
	EvalInt64(b *batch.RecordBatch, row int) (int64, bool)
}

Int64Expr evaluates to int64 without boxing.

type IntervalValue

type IntervalValue struct {
	Years   int
	Months  int
	Days    int
	Hours   int
	Minutes int
	Seconds int
}

IntervalValue represents a SQL INTERVAL (e.g., INTERVAL '30' DAY).

type InvalidLiteralError added in v0.18.2

type InvalidLiteralError struct {
	Input    string // the literal's source text
	DestType string // the type it was being read as, e.g. "numeric"
}

InvalidLiteralError names a constant the compiler refused outright: a literal that cannot be read as a value of the type its context demands.

It is a DISTINCT TYPE for exactly the reason UnknownFuncError is one. The physical planner's compile sites are forgiving by design — a projection whose AST will not compile falls back to copying an input column of the same name — and that fallback is right for every compile failure EXCEPT the ones that are the answer. A refused literal has no column to fall back to, so swallowing it turned `SELECT -'abc'` into `column "-'abc'" does not exist`, which sends the reader hunting a name-resolution bug for a perfectly well-diagnosed 22P02 (#505 review finding). Callers test for it with errors.As — IsCompileRefusal below — and propagate.

func (*InvalidLiteralError) Error added in v0.18.2

func (e *InvalidLiteralError) Error() string

func (*InvalidLiteralError) SQLState added in v0.18.2

func (e *InvalidLiteralError) SQLState() string

SQLState returns PostgreSQL's invalid_text_representation code, the same one raiseInvalidTextRepresentation raises for the per-row version of this refusal. sqlerr.StateOf picks it up through the Coder interface.

type IsBool

type IsBool struct {
	Operand Expr
	Want    bool // TRUE or FALSE spelling
	Not     bool // IS NOT
}

IsBool is `x IS [NOT] TRUE/FALSE`. Distinct from Cmp because it is a NULL-test like IS NULL, not a comparison: NULL IS TRUE answers FALSE and NULL IS NOT TRUE answers TRUE, where a comparison against NULL would be UNKNOWN (#370 — the Cmp spelling was right only while Cmp itself had no UNKNOWN).

func (*IsBool) Eval

func (e *IsBool) Eval(b *batch.RecordBatch, row int) any

func (*IsBool) EvalBool

func (e *IsBool) EvalBool(b *batch.RecordBatch, row int) bool

func (*IsBool) EvalBoolNull

func (e *IsBool) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

type IsDistinctFrom

type IsDistinctFrom struct {
	Left, Right Expr
	Not         bool // true for IS NOT DISTINCT FROM
	// contains filtered or unexported fields
}

IsDistinctFrom implements PostgreSQL's NULL-safe (in)equality, IS [NOT] DISTINCT FROM (#374). Unlike Cmp, it never answers UNKNOWN: NULL participates as a value here rather than propagating, so two NULLs are NOT DISTINCT (equal) and a NULL against a non-NULL value IS DISTINCT. "NULL IS DISTINCT FROM NULL" is FALSE, never NULL — the one case a COALESCE-based workaround gets wrong for a real sentinel value.

func (*IsDistinctFrom) Eval

func (e *IsDistinctFrom) Eval(b *batch.RecordBatch, row int) any

func (*IsDistinctFrom) EvalBool

func (e *IsDistinctFrom) EvalBool(b *batch.RecordBatch, row int) bool

func (*IsDistinctFrom) EvalBoolNull

func (e *IsDistinctFrom) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

EvalBoolNull always reports null=false: DISTINCT FROM is total over NULL inputs, which is the entire point of the operator.

type IsNull

type IsNull struct {
	Operand Expr
	Not     bool // IS NOT NULL
}

IsNull checks if an expression is null.

func (*IsNull) Eval

func (e *IsNull) Eval(b *batch.RecordBatch, row int) any

func (*IsNull) EvalBool

func (e *IsNull) EvalBool(b *batch.RecordBatch, row int) bool

func (*IsNull) EvalBoolNull

func (e *IsNull) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

EvalBoolNull: IS [NOT] NULL never answers UNKNOWN — it is the operator SQL provides to ASK about NULL.

type Like

type Like struct {
	Expr    Expr
	Pattern Expr
	Not     bool
}

Like performs SQL LIKE pattern matching.

func (*Like) Eval

func (e *Like) Eval(b *batch.RecordBatch, row int) any

func (*Like) EvalBool

func (e *Like) EvalBool(b *batch.RecordBatch, row int) bool

func (*Like) EvalBoolNull

func (e *Like) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

EvalBoolNull: LIKE with NULL on either side is UNKNOWN, and NOT LIKE stays UNKNOWN with it (#370). A container-shaped operand is a query ERROR (#522) rather than a value, matched or not — see containerLikeKind.

type Lit

type Lit struct {
	Val any
	// Text is the numeric literal's source text, kept verbatim. Val is the
	// literal boxed for arithmetic — an int64 where one is exact, a float64
	// otherwise — and a float64 carries ~15-16 significant decimal digits
	// where a DECIMAL(38,10) column carries 38, so the box alone cannot say
	// which number was written (#452). Comparisons against a DECIMAL column
	// read this instead and compare in the column's own domain; everything
	// else keeps reading Val and is unchanged. Empty for a non-numeric
	// literal.
	Text string
}

Lit returns a constant value.

func (*Lit) Eval

func (e *Lit) Eval(_ *batch.RecordBatch, _ int) any

func (*Lit) EvalFloat64

func (e *Lit) EvalFloat64(_ *batch.RecordBatch, _ int) (float64, bool)

func (*Lit) EvalFloat64Vec

func (e *Lit) EvalFloat64Vec(_ *batch.RecordBatch, dst []float64, n int) bool

EvalFloat64Vec fills dst[0:n] with the literal value.

func (*Lit) EvalInt64

func (e *Lit) EvalInt64(_ *batch.RecordBatch, _ int) (int64, bool)

type MemoryAccountant added in v0.18.3

type MemoryAccountant interface {
	// Reserve charges n bytes against the budget, returning an error (which
	// InSubquery treats as a query error, never a silent no-op) if doing so
	// would exceed it.
	Reserve(n int64) error
	// Release returns n previously reserved bytes.
	Release(n int64)
}

MemoryAccountant is the minimal per-task memory-budget hook InSubquery uses to charge its uncorrelated membership set (ADR-0006, #528). It is declared here rather than importing internal/engine/memory: expr has no other reason to depend on that package, and *memory.Tracker already has exactly this method set, so a caller that holds one satisfies this interface with no adapter — the seam CompileWithBudget threads through costs no new package dependency.

type MissingOuterColumnError

type MissingOuterColumnError struct {
	Ref       plansql.OuterRef
	Available []string
}

MissingOuterColumnError reports a correlated subquery whose outer column is absent from the batch the outer query hands it — a planning defect (column pruning, projection, or a rename), not a data condition.

func (*MissingOuterColumnError) Error

func (e *MissingOuterColumnError) Error() string

func (*MissingOuterColumnError) FatalEvalError

func (e *MissingOuterColumnError) FatalEvalError() error

FatalEvalError satisfies the marker the pipeline drivers recover on. Expr's Eval/EvalBool have no error return, so a failure that must not be mistaken for a NULL travels as a panic carrying this value and is turned back into a query error at the pipeline boundary (see exec.FatalEvalPanic).

func (*MissingOuterColumnError) SQLState added in v0.18.33

func (e *MissingOuterColumnError) SQLState() string

SQLState is PostgreSQL's 42703 (undefined_column).

The two ways to reach this error are a PLANNING defect — the outer query pruned a column its subquery correlates on — and a reference to a column the outer relation simply does not have, which is what a client sees when it writes `WHERE EXISTS (… WHERE s.id = t.nosuchcol)`. PostgreSQL answers the second with 42703, and a client cannot tell the two apart from the wire, so the code has to be the one the shape a client can actually write deserves. Without it this reached the client with no SQLSTATE at all on the embedded door and the pgwire layer's 42000 fallback on the wire.

type Not

type Not struct {
	Operand Expr
}

Not is a logical NOT.

func (*Not) Eval

func (e *Not) Eval(b *batch.RecordBatch, row int) any

func (*Not) EvalBool

func (e *Not) EvalBool(b *batch.RecordBatch, row int) bool

EvalBool: NOT must see the third value — collapsing first turned NOT (UNKNOWN) into true and admitted rows SQL excludes, which was the dangerous half of #370 (`1 NOT IN (2, NULL)` answering true).

func (*Not) EvalBoolNull

func (e *Not) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

EvalBoolNull: NOT UNKNOWN stays UNKNOWN.

type NumericRangeError added in v0.18.5

type NumericRangeError struct {
	Input    string // the literal's source text
	DestType string // the type it was being read as, e.g. "real"
}

NumericRangeError is InvalidLiteralError's sibling for the OTHER way a literal can fail its column's type: it names a real number the type cannot carry. PostgreSQL raises SQLSTATE 22003 (numeric_value_out_of_range) with its own wording for that, not 22P02 — `'1e400'::real` is "out of range" and `'abc'::real` is "invalid input syntax", and the WireProtocol oracle checks which one the wire says (#646).

It is a distinct type for InvalidLiteralError's reason: the planner's compile sites fall back around an ordinary compile failure, and a refused literal has no column to fall back to, so IsCompileRefusal must name this class too.

func (*NumericRangeError) Error added in v0.18.5

func (e *NumericRangeError) Error() string

Error is PostgreSQL's wording, and the two families word it differently — verified live on postgres:17-alpine:

'3000000000'::integer  ->  value "3000000000" is out of range for type integer
'1e400'::real          ->  "1e400" is out of range for type real

The integer input functions prefix the literal with `value ` and the float ones do not. The message is part of the answer (ADR-0012 item 1), so the distinction is reproduced rather than tidied away; exec.intStatusError gives the identical text for the vectorized path's copy of the same refusal.

func (*NumericRangeError) SQLState added in v0.18.5

func (e *NumericRangeError) SQLState() string

SQLState returns PostgreSQL's numeric_value_out_of_range code, the same one exec.floatConstError raises for the per-row version of this refusal.

type Or

type Or struct {
	Left, Right Expr
}

Or is a logical OR.

func (*Or) Eval

func (e *Or) Eval(b *batch.RecordBatch, row int) any

func (*Or) EvalBool

func (e *Or) EvalBool(b *batch.RecordBatch, row int) bool

func (*Or) EvalBoolNull

func (e *Or) EvalBoolNull(b *batch.RecordBatch, row int) (bool, bool)

EvalBoolNull: TRUE OR anything is TRUE; otherwise a NULL operand makes it UNKNOWN. Short-circuits on a TRUE left operand.

type ParamRef

type ParamRef struct {
	Index int
	// contains filtered or unexported fields
}

ParamRef is an expression node that references a UDF parameter by index.

func (*ParamRef) Eval

func (e *ParamRef) Eval(_ *batch.RecordBatch, _ int) any

type Quantified added in v0.18.20

type Quantified struct {
	Cmps []Expr // one comparison of the left operand against each candidate
	All  bool   // ALL, rather than ANY/SOME
}

Quantified is `left <op> ANY|SOME|ALL (v1, v2, …)`.

The candidate list is fixed at COMPILE time — a value list, or the elements of an `ARRAY[…]` literal — so each candidate is an ordinary comparison and the quantifier is the fold over them. `= ANY (subquery)` and `<> ALL (subquery)` do not come here: they are `IN` and `NOT IN`, and the compiler routes them to InSubquery, which already knows how to stream a subquery's rows.

PostgreSQL's three-valued fold, which is why this is not a plain OR/AND over the comparisons:

ANY  TRUE if any comparison is TRUE; else NULL if any is NULL; else FALSE
ALL  FALSE if any comparison is FALSE; else NULL if any is NULL; else TRUE

An empty candidate list is FALSE for ANY and TRUE for ALL, which is PostgreSQL's answer for the empty array.

func (*Quantified) Eval added in v0.18.20

func (e *Quantified) Eval(b *batch.RecordBatch, row int) any

func (*Quantified) EvalBool added in v0.18.20

func (e *Quantified) EvalBool(b *batch.RecordBatch, row int) bool

func (*Quantified) EvalBoolNull added in v0.18.20

func (e *Quantified) EvalBoolNull(b *batch.RecordBatch, row int) (val, null bool)

type Ret

type Ret struct {
	// contains filtered or unexported fields
}

Ret is a scalar function's declared return type: the vector type its results can be stored in. It is declared where the function is registered, and the planner types a projection from the same declaration the kernel writes through.

Before this existed the two halves lived apart — a function was registered in this package while its return type was asserted by a hand-maintained name list in the physical planner (isNumericFunc). A function missing from that list was typed String, so the projection allocated a Bytes output vector and the function's vec kernel wrote Float64Data/BoolData off the end of a zero-length slice, killing the server process for every connection. That happened four times (temporal extractors, vector distances, the length family, and starts_with/contains/ends_with) before the list was replaced by this declaration (#310).

The zero value is *undeclared* and the registry refuses it: Register's signature makes a missing declaration a compile error, and the zero value makes a field-named literal that skips it a panic at init.

func RetSameAsArg

func RetSameAsArg(fallback batch.TypeID, args ...int) Ret

RetSameAsArg declares a polymorphic return: the type of the first listed argument the caller can decide, or fallback when it can decide none of them — and Resolve marks that fallback as a guess, so a CALLER with candidates of its own keeps looking rather than inheriting it (see Confidence). With no indices every argument is a candidate, which is what coalesce, greatest and least want; nullif mirrors argument 0 only.

func RetTypeOf

func RetTypeOf(t batch.TypeID) Ret

RetTypeOf builds a fixed declaration for a type without a named constant above. Kept for callers registering functions over the network-native types.

func (Ret) Boolean added in v0.18.44

func (r Ret) Boolean() bool

Boolean reports whether a function always returns a BOOLEAN. Only a FIXED declaration answers, for Integer's reason: the operand-classification layer reads it to apply PostgreSQL's boolean input grammar to whatever the result is compared against (#628), and a wrong claim would apply that grammar to a value that is not a boolean.

func (Ret) Control added in v0.18.5

func (r Ret) Control(args ...int) Ret

Control marks argument positions that steer a polymorphic choice without supplying its value. See Ret.ctrl.

func (Ret) Declared

func (r Ret) Declared() bool

Declared reports whether this is a real declaration rather than the zero value. Registration rejects an undeclared Ret.

func (Ret) Integer added in v0.18.5

func (r Ret) Integer() bool

Integer reports whether the function always returns an INTEGER — declared RetInt32 or RetInt64 — which is what makes arithmetic over its result integer arithmetic.

PostgreSQL's `length(s) / 2` is integer division, so it is 2 for a five-character string and not 2.5 (#636). compileBinOp could not see that: it chose the arithmetic node from the operands' COMPILE-TIME shape and a function call had none, so `length(s) / 2` compiled to BinOpFloat64. The declaration is the shape it was missing — and reading it from the registry rather than from a name list is what keeps a function added later from silently answering a fraction.

Only a FIXED declaration answers. A polymorphic one (RetSameAsArg) mirrors an argument whose type is not known until a batch arrives, so claiming integer for it at compile time would be a guess — and a WRONG int claim truncates every value it touches.

func (Ret) Numeric

func (r Ret) Numeric() bool

Numeric reports whether the function always returns a number. It is the registry-backed replacement for the compiler's own hand-maintained numeric name list: a numeric call can be wrapped so it satisfies Float64Expr/ Int64Expr and binary operators over it take the typed path.

func (Ret) OperatorResolved added in v0.18.21

func (r Ret) OperatorResolved() Ret

OperatorResolved marks a declaration whose type comes from the OPERATOR its arguments select rather than from select_common_type over them. NULLIF is the only such construct; see operatorResolvedType.

func (Ret) Resolve

func (r Ret) Resolve(nargs int, argType func(i int) (DeclType, Confidence)) (DeclType, Confidence)

Resolve returns the output type for a call with nargs arguments, and how confidently. argType reports the type of argument i and how confidently the caller decided it; it is consulted only by polymorphic declarations and may be nil.

Undecided means the caller should keep its own fallback: the function is RetDynamic, or the name is not registered at all.

A polymorphic declaration takes the first candidate argument that DECIDED a type. A candidate that only guessed does not end the search — it is remembered, in preference order, and answered only if no later candidate decides. A guess stays a guess all the way up, so an argument that guessed at any depth never displaces an argument that knows.

func (Ret) SameAsArgs added in v0.18.5

func (r Ret) SameAsArgs(nargs int) ([]int, bool)

SameAsArgs reports the argument positions a polymorphic declaration mirrors for the TYPMOD fold, for a call with nargs arguments, and whether the declaration is polymorphic at all.

It exists so PostgreSQL's select_common_typmod runs over the arguments the RESULT is resolved from: NULLIF's result is always argument 0's value, so NULLIF(numeric(9,2), numeric(18,4)) keeps numeric(9,2) while GREATEST over the same pair drops to unconstrained (ADR-0024 item 5).

It is NOT the TYPE fold's candidate list, and conflating the two was a defect: PostgreSQL runs select_common_TYPE over BOTH of NULLIF's arguments — they have to be comparable — so `NULLIF(0, numeric(9,2))` is numeric there and was INT64 here. typeArgs is that list; see Ret.typeAll.

func (Ret) String

func (r Ret) String() string

func (Ret) TypeOverAllArgs added in v0.18.5

func (r Ret) TypeOverAllArgs() Ret

TypeOverAllArgs lets the TYPE fold reach an argument the TYPMOD fold does not, when that argument is a DECIMAL the candidate list's answer cannot hold. See Ret.typeAll and widenToDecimalBeyondCandidates.

type RowCmp added in v0.18.20

type RowCmp struct {
	// contains filtered or unexported fields
}

RowCmp compares two row values field by field, as PostgreSQL does.

`=` and `<>` compare every field; the ordering operators stop at the first field pair that is not equal and answer from it, which is what makes `(1, 2) < (1, 3)` true and `(2, 0) < (1, 9)` false. A NULL anywhere the comparison has to LOOK makes the whole thing NULL — for `=` that is any field, for `<` only the fields up to and including the deciding one.

func (*RowCmp) Eval added in v0.18.20

func (e *RowCmp) Eval(b *batch.RecordBatch, row int) any

func (*RowCmp) EvalBool added in v0.18.20

func (e *RowCmp) EvalBool(b *batch.RecordBatch, row int) bool

func (*RowCmp) EvalBoolNull added in v0.18.20

func (e *RowCmp) EvalBoolNull(b *batch.RecordBatch, row int) (val, null bool)

type ScalarFunc

type ScalarFunc func(args []any) any

ScalarFunc is a scalar function implementation.

type ScalarSubquery

type ScalarSubquery struct {
	SQL    string
	Runner SubqueryRunner
	// Decl is the DECLARED type of the subquery's single output column, and
	// DeclKnown says whether anything resolved it (#696). It carries no value
	// and changes no evaluation: it exists so the boxed comparison can read
	// this operand as the number it IS.
	//
	// A DECIMAL boxes as its rendered TEXT, so without a declaration the pair
	// `a > (SELECT AVG(a) FROM decpair)` had a proven DECIMAL on one side and
	// an unclassifiable box on the other, fell through to compare()'s
	// LEXICOGRAPHIC rule, and answered 0 rows for PostgreSQL's 4 because
	// "12.75" sorts below "7.570000". DecPrecision/DecScale go with a DECIMAL
	// Decl for the same reason every other declaration carries them.
	Decl                   batch.TypeID
	DeclKnown              bool
	DecPrecision, DecScale int
	// contains filtered or unexported fields
}

ScalarSubquery evaluates a subquery that returns a single scalar value. Example: WHERE price > (SELECT AVG(price) FROM products) Uncorrelated: executed once and result cached.

The cache is shared by every parallel pipeline worker — one compiled expression tree is captured by all of them (Pipeline.runParallel) — so it is published the same way ColRef publishes its resolution: written under resolveMu, released by an atomic store, and never read before that store is observed. A plain `if !cached { cached = true; ... }` raced: a worker that saw the flag before the value was written compared against a nil threshold, dropped every row of its batches, and the query answered a different row count on every run (#398).

func (*ScalarSubquery) Eval

func (e *ScalarSubquery) Eval(_ *batch.RecordBatch, _ int) any

type ScalarSubqueryRowsError added in v0.18.33

type ScalarSubqueryRowsError struct {
	SQL  string
	Rows int
}

ScalarSubqueryRowsError reports a scalar subquery that returned more than one row.

It is an ERROR and not a value because every value it could stand in for is a lie about the data, and because the row it would otherwise pick is whichever one the producer happened to emit first — a different answer on a different execution path for the same query. PostgreSQL's own wording and SQLSTATE, because a client branches on the code.

func (*ScalarSubqueryRowsError) Error added in v0.18.33

func (e *ScalarSubqueryRowsError) Error() string

Error is PostgreSQL's own sentence, with what this site knows appended. Rows == 0 is for a caller that knows only "more than one" — it stopped counting, or never had the whole result — and the sentence stands on its own there, which is the part a client reads.

func (*ScalarSubqueryRowsError) FatalEvalError added in v0.18.33

func (e *ScalarSubqueryRowsError) FatalEvalError() error

FatalEvalError satisfies the marker the pipeline drivers recover on, so this reaches the client as a query error rather than taking the process.

func (*ScalarSubqueryRowsError) SQLState added in v0.18.33

func (e *ScalarSubqueryRowsError) SQLState() string

SQLState is PostgreSQL's 21000 (cardinality_violation).

type SubqueryDeclFunc added in v0.18.11

type SubqueryDeclFunc func(sql string) (typ batch.TypeID, precision, scale int, ok bool)

SubqueryDeclFunc resolves a scalar subquery's SQL to the declared type of its single output column. ok=false for a subquery whose output type the caller cannot resolve; the comparison then falls back to the boxed rules it had before.

type SubqueryRunFailedError added in v0.18.16

type SubqueryRunFailedError struct {
	Kind string // "EXISTS", "IN", "scalar"
	SQL  string
	Err  error
}

SubqueryRunFailedError reports a subquery whose standalone execution failed. It is a fatal evaluation error rather than a value because every value it could stand in for is a lie about the data.

func (*SubqueryRunFailedError) Error added in v0.18.16

func (e *SubqueryRunFailedError) Error() string

func (*SubqueryRunFailedError) FatalEvalError added in v0.18.16

func (e *SubqueryRunFailedError) FatalEvalError() error

FatalEvalError satisfies the marker the pipeline drivers recover on, so this reaches the client as a query error rather than taking the process.

func (*SubqueryRunFailedError) SQLState added in v0.18.16

func (e *SubqueryRunFailedError) SQLState() string

SQLState is the WRAPPED failure's, because the reason the subquery could not be run IS the query's error: `WHERE h > (SELECT AVG(h) FROM t WHERE SUM(h) > 0)` fails because the inner statement puts an aggregate in a WHERE, and PostgreSQL 17 answers 42803 for it. Without this the refusal reached the client with no SQLSTATE at all while the same inner statement run on its own carried one — loud, but not yet the error PostgreSQL gives.

Empty when the wrapped failure carries no code, which is what the pgwire layer's own fallback expects.

func (*SubqueryRunFailedError) Unwrap added in v0.18.16

func (e *SubqueryRunFailedError) Unwrap() error

type SubqueryRunner

type SubqueryRunner func(sql string) ([]map[string]any, error)

SubqueryRunner executes a SQL subquery and returns its result rows. Each row is a map of column name to value.

type UDFCall

type UDFCall struct {
	Name     string
	ArgExprs []Expr // caller-supplied argument expressions
	Body     Expr   // compiled UDF body with ParamRef nodes
	// contains filtered or unexported fields
}

UDFCall evaluates a user-defined function by binding arguments, then evaluating the compiled body expression.

func (*UDFCall) Eval

func (e *UDFCall) Eval(b *batch.RecordBatch, row int) any

type UDFDef

type UDFDef struct {
	Name   string   // function name (lowercase)
	Params []string // parameter names (lowercase)
	Body   string   // SQL expression body (e.g. "param1 * 2 + param2")
	Owner  string   // who created this function (empty = system/unowned)
	Locked bool     // if true, only the owner (or admin) can modify/drop
}

UDFDef defines a user-defined function.

type UDFPersister

type UDFPersister func(udfs []UDFDef) error

UDFPersister is called after UDF register/unregister to persist the current state.

type UDFStore

type UDFStore struct {
	// contains filtered or unexported fields
}

UDFStore holds compiled UDF definitions for use by the expression engine. Thread-safe for concurrent reads and writes.

func NewUDFStore

func NewUDFStore() *UDFStore

NewUDFStore creates a new empty UDF store.

func (*UDFStore) CompileUDFCall

func (s *UDFStore) CompileUDFCall(name string, argExprs []Expr) (Expr, error)

CompileUDFCall creates a UDFCall expression node.

func (*UDFStore) Get

func (s *UDFStore) Get(name string) (UDFDef, bool)

Get returns a UDF definition by name.

func (*UDFStore) List

func (s *UDFStore) List() []UDFDef

List returns all registered UDF definitions.

func (*UDFStore) LoadDefs

func (s *UDFStore) LoadDefs(defs []UDFDef) int

LoadDefs registers pre-existing UDF definitions (e.g., from KV on startup). Skips compilation errors silently so one bad UDF doesn't block startup.

func (*UDFStore) RefuseIfDefined added in v0.18.42

func (s *UDFStore) RefuseIfDefined(name string) error

RefuseIfDefined is CREATE FUNCTION's duplicate-name refusal, raised where the store knows the answer rather than at each door that asks.

42723 duplicate_function is PostgreSQL's class for a second CREATE FUNCTION on a name already taken. Both doors that run CREATE FUNCTION — the embedded API and the HTTP query endpoint — did this Get-then-refuse themselves and neither attached a class, so the refusal crossed pgwire as the blanket 42000 and the HTTP door reported none (arc E2's api-reference caveat). A third door gets the class by calling this.

func (*UDFStore) Register

func (s *UDFStore) Register(def UDFDef, isAdmin bool) error

Register compiles and registers a UDF.

func (*UDFStore) SetPersister

func (s *UDFStore) SetPersister(p UDFPersister)

SetPersister sets the function called after UDF mutations to persist state.

func (*UDFStore) Unregister

func (s *UDFStore) Unregister(name, caller string, isAdmin bool) error

Unregister removes a UDF.

type UnaryOp

type UnaryOp struct {
	Operand Expr
	Op      string // -, +
}

UnaryOp is a unary arithmetic expression (negation).

func (*UnaryOp) Eval

func (e *UnaryOp) Eval(b *batch.RecordBatch, row int) any

type UnknownFuncError

type UnknownFuncError struct {
	Name string
	// Aggregate marks a name this engine recognizes as an aggregate from
	// other SQL dialects but does not implement. The distinction matters to
	// the reader: an unimplemented aggregate silently dropped the GROUP BY
	// as well as the value, so the result had the wrong row COUNT.
	Aggregate bool
}

UnknownFuncError names a function the registry cannot resolve.

It is a distinct type because the physical planner's compile sites are forgiving by design: a projection whose AST will not compile falls back to copying an input column of the same name, which is how an aggregate's output column reaches the projection. That fallback is right for every compile failure EXCEPT this one — a name nothing implements has no column to fall back to, so swallowing it converted "unknown function foo" into the far less actionable "column \"foo(x)\" does not exist in the input schema", or, before the check existed, into no message at all. Callers test for this type with errors.As and propagate rather than falling back.

func (*UnknownFuncError) Error

func (e *UnknownFuncError) Error() string

func (*UnknownFuncError) SQLState

func (e *UnknownFuncError) SQLState() string

SQLState returns PostgreSQL's undefined_function code. sqlerr.StateOf picks it up through the Coder interface so the wire reports 42883 rather than the blanket 42000 (#366).

type UnknownTypeError added in v0.18.34

type UnknownTypeError struct{ Name string }

UnknownTypeError names a type name no type in this engine answers to. It is a compile REFUSAL in the sense fatal.go's IsCompileRefusal means: an error that has decided its own PostgreSQL class is an answer, not a hint the planner may fall back from.

func (*UnknownTypeError) Error added in v0.18.34

func (e *UnknownTypeError) Error() string

func (*UnknownTypeError) SQLState added in v0.18.34

func (e *UnknownTypeError) SQLState() string

SQLState returns PostgreSQL's undefined_object code.

type UnrenderableOuterValueError added in v0.18.28

type UnrenderableOuterValueError struct {
	Type batch.TypeID
}

UnrenderableOuterValueError reports an outer-row value with no literal spelling this engine's parser reads back as the same value.

It is fatal, and deliberately: the alternative is substituting a literal that means something else, which turns a query this engine cannot run into one that answers the wrong number.

func (*UnrenderableOuterValueError) Error added in v0.18.28

func (*UnrenderableOuterValueError) FatalEvalError added in v0.18.28

func (e *UnrenderableOuterValueError) FatalEvalError() error

FatalEvalError satisfies the marker the pipeline drivers recover on.

func (*UnrenderableOuterValueError) SQLState added in v0.18.28

func (e *UnrenderableOuterValueError) SQLState() string

SQLState is PostgreSQL's feature_not_supported: the query is legal SQL this engine has no lowering for, which is what 0A000 says.

type VecExpr

type VecExpr interface {
	EvalVec(b *batch.RecordBatch, out *batch.Vector, n int)
}

VecExpr evaluates an expression for an entire batch at once, writing results directly to the output vector. This avoids per-row interface dispatch and boxing.

type VecFloat64Expr

type VecFloat64Expr interface {
	EvalFloat64Vec(b *batch.RecordBatch, dst []float64, n int) bool
}

VecFloat64Expr evaluates an expression for all rows [0, n) at once, writing results to dst. Returns true if any output is null. Eliminates per-row function call overhead (~5 calls/row/expression).

type VecScalarFunc

type VecScalarFunc func(args []*batch.Vector, out *batch.Vector, n int)

VecScalarFunc is a vectorized scalar function that operates on entire columns at once, reading from input vectors and writing to an output vector. This avoids per-row interface dispatch and boxing overhead.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL