feat(ast): extend Ast with CallExpression and NextValueExpression
This commit is contained in:
1 parent
e816d4edf1
commit
bff836d5b9
1 file changed
+165
-1
@@ -427,7 +427,152 @@ data class ColumnExpression(val name: String, val qualifier: String? = null) : E
|
|||||||
data class FunctionExpression(
|
data class FunctionExpression(
|
||||||
val name: String,
|
val name: String,
|
||||||
val argument: Expression?,
|
val argument: Expression?,
|
||||||
val star: Boolean
|
val star: Boolean,
|
||||||
|
val separator: Expression? = null,
|
||||||
|
val orderBy: List<OrderKey> = emptyList(),
|
||||||
|
val distinct: Boolean = false,
|
||||||
|
val filter: Expression? = null
|
||||||
|
) : Expression
|
||||||
|
|
||||||
|
data class CallExpression(val name: String, val arguments: List<Expression>) : Expression
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `NEXTVAL('seq')`/`CURRVAL('seq')`. Deliberately their own [Expression] variants rather than
|
||||||
|
* folding into [CallExpression] like every other function call - [sequenceName] is a name resolved
|
||||||
|
* once at parse time (the parser requires the argument to literally be a string literal, not an
|
||||||
|
* arbitrary expression), never a per-row value, which keeps every existing [CallExpression]-shaped
|
||||||
|
* validator (`Executor.validateConstantDefault`, `CheckExpressionColumns`, `GroupAggregation`, ...)
|
||||||
|
* untouched by NEXTVAL's side effect instead of needing a special case bolted onto each one. See
|
||||||
|
* [kestrel.engine.Executor.executeInsertInner]/`executeUpdateInner` for where these two are actually
|
||||||
|
* allowed to evaluate - CHECK constraints, trigger `WHEN` clauses, and SELECT/WHERE all reject them.
|
||||||
|
*/
|
||||||
|
data class NextValueExpression(val sequenceName: String) : Expression
|
||||||
|
|
||||||
|
data class CurrentValueExpression(val sequenceName: String) : Expression
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `MERGE_ACTION()` - takes no arguments (the parser rejects any), so unlike [CallExpression] it
|
||||||
|
* carries no per-call state and is a `data object` rather than a `data class`. Only meaningful inside
|
||||||
|
* a [MergeStatement]'s own [MergeStatement.returning] projection, where it evaluates to `'INSERT'`,
|
||||||
|
* `'UPDATE'`, or `'DELETE'` depending on which action a given RETURNING row came from - see
|
||||||
|
* [kestrel.engine.Executor.evaluateMergeReturning] for where that per-row action is actually bound.
|
||||||
|
* Every other [Expression] consumer (CHECK constraints, WHERE, trigger bodies, plain INSERT/UPDATE/
|
||||||
|
* DELETE RETURNING, ...) treats it as structurally harmless - no column reference to check, nothing
|
||||||
|
* to rename, nothing to depend on - exactly like [NextValueExpression]/[CurrentValueExpression] do,
|
||||||
|
* and instead rejects it the same way they reject NEXTVAL/CURRVAL: by never handing the
|
||||||
|
* [kestrel.engine.Evaluator] that runs it the per-row action string it needs, so evaluation itself
|
||||||
|
* throws.
|
||||||
|
*/
|
||||||
|
data object MergeActionExpression : Expression
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `$1`, `$2`, ... - a positional parameter placeholder, 1-based to match the SQL text. Only ever
|
||||||
|
* produced by the parser inside a [PrepareStatement]'s [PrepareStatement.target] (or, harmlessly,
|
||||||
|
* anywhere else an [Expression] is grammatically legal, since the parser has no context at parse
|
||||||
|
* time to reject it elsewhere) - [kestrel.engine.ParameterSubstitution] replaces every occurrence
|
||||||
|
* with a [LiteralExpression] before the surrounding statement actually runs, so by evaluation time
|
||||||
|
* ([kestrel.engine.Evaluator]) one reaching [kestrel.engine.Evaluator.evaluate] unsubstituted is
|
||||||
|
* always a usage error (typed outside of PREPARE/EXECUTE entirely) and throws there, exactly like
|
||||||
|
* [MergeActionExpression] throws when evaluated outside a MERGE's own RETURNING.
|
||||||
|
*/
|
||||||
|
data class ParameterExpression(val index: Int) : Expression
|
||||||
|
|
||||||
|
data class IsNullExpression(val operand: Expression, val negated: Boolean) : Expression
|
||||||
|
|
||||||
|
data class InExpression(val operand: Expression, val values: List<Expression>, val negated: Boolean) : Expression
|
||||||
|
|
||||||
|
data class BetweenExpression(
|
||||||
|
val operand: Expression,
|
||||||
|
val low: Expression,
|
||||||
|
val high: Expression,
|
||||||
|
val negated: Boolean
|
||||||
|
) : Expression
|
||||||
|
|
||||||
|
data class LikeExpression(val operand: Expression, val pattern: Expression, val negated: Boolean) : Expression
|
||||||
|
|
||||||
|
data class CaseExpression(
|
||||||
|
val operand: Expression?,
|
||||||
|
val branches: List<CaseBranch>,
|
||||||
|
val elseExpression: Expression?
|
||||||
|
) : Expression
|
||||||
|
|
||||||
|
data class CaseBranch(val condition: Expression, val result: Expression)
|
||||||
|
|
||||||
|
data class ScalarSubqueryExpression(val select: SelectStatement) : Expression
|
||||||
|
|
||||||
|
data class ExistsExpression(val select: SelectStatement) : Expression
|
||||||
|
|
||||||
|
data class InSubqueryExpression(
|
||||||
|
val operand: Expression,
|
||||||
|
val select: SelectStatement,
|
||||||
|
val negated: Boolean
|
||||||
|
) : Expression
|
||||||
|
|
||||||
|
data class CastExpression(
|
||||||
|
val operand: Expression,
|
||||||
|
val targetType: ColumnType,
|
||||||
|
val precision: Int? = null,
|
||||||
|
val scale: Int? = null
|
||||||
|
) : Expression
|
||||||
|
|
||||||
|
enum class WindowFunctionKind { RowNumber, Rank, DenseRank, Aggregate, Lag, Lead, FirstValue, LastValue, Ntile }
|
||||||
|
|
||||||
|
/** Where a [FrameBound] sits relative to the current row; ordered exactly as SQL requires a frame's start to precede its end. */
|
||||||
|
enum class FrameBoundType { UnboundedPreceding, Preceding, CurrentRow, Following, UnboundedFollowing }
|
||||||
|
|
||||||
|
/** `UNBOUNDED PRECEDING`/`CURRENT ROW`/`UNBOUNDED FOLLOWING` carry no [offset]; `N PRECEDING`/`N FOLLOWING` evaluate it per row. */
|
||||||
|
data class FrameBound(val type: FrameBoundType, val offset: Expression? = null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Rows] counts physical row positions (`ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING` = the row before
|
||||||
|
* and after, regardless of ties). [Range] measures distance in the value of a single ORDER BY column
|
||||||
|
* (numeric or DATE) - `RANGE BETWEEN 5 PRECEDING AND 5 FOLLOWING` on `ORDER BY amount` includes every
|
||||||
|
* row whose `amount` is within 5 of the current row's, ties included, however many physical rows that
|
||||||
|
* is. [Groups] counts whole peer groups (ties on the OVER's ORDER BY) instead of rows or values -
|
||||||
|
* `GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING` includes every row in the peer group before the
|
||||||
|
* current one, the current peer group, and the one after, however many rows each group has. Unlike
|
||||||
|
* [Range] it works with any ORDER BY (zero, one, or many columns, any type), not just a single
|
||||||
|
* numeric/DATE column.
|
||||||
|
*/
|
||||||
|
enum class FrameUnit { Rows, Range, Groups }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `EXCLUDE` narrows a frame after its [FrameBound]s are resolved, by removing rows tied to the
|
||||||
|
* current row on the OVER's ORDER BY. [NoOthers] (the default - no `EXCLUDE` clause at all) removes
|
||||||
|
* nothing. [CurrentRow] removes only the current row itself. [Group] removes the current row's
|
||||||
|
* entire peer group, including the current row. [Ties] removes every *other* row in the peer group
|
||||||
|
* but keeps the current row - the one exclusion mode where the current row's own value always still
|
||||||
|
* counts. With no ORDER BY the whole partition is one peer group, matching the same convention
|
||||||
|
* already used for the frame's own `CURRENT ROW` bound and for the implicit default frame.
|
||||||
|
*/
|
||||||
|
enum class FrameExclude { NoOthers, CurrentRow, Group, Ties }
|
||||||
|
|
||||||
|
/** `ROWS|RANGE BETWEEN <start> AND <end> [EXCLUDE ...]`. */
|
||||||
|
data class WindowFrame(val unit: FrameUnit, val start: FrameBound, val end: FrameBound, val exclude: FrameExclude = FrameExclude.NoOthers)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `name(...) OVER (PARTITION BY ... ORDER BY ... [ROWS BETWEEN ... AND ...])`. [RowNumber]/[Rank]/
|
||||||
|
* [DenseRank] never take an argument (always `argument == null && star == false`); [Aggregate]
|
||||||
|
* reuses the same COUNT/SUM/AVG/MIN/MAX vocabulary as [FunctionExpression] (including COUNT(*) via
|
||||||
|
* [star]) but computes per row over a partition/frame instead of collapsing the whole group into
|
||||||
|
* one row. [Lag]/[Lead] read [argument] from a row [offset] rows before/after the current one
|
||||||
|
* (default 1), falling back to [defaultValue] (or NULL) past the partition's edge - both are `null`
|
||||||
|
* for every other kind. [FirstValue]/[LastValue] read [argument] from the first/last row of the
|
||||||
|
* current frame; [Ntile] treats [argument] as the bucket count and ignores [offset]/[defaultValue].
|
||||||
|
* [frame] is only meaningful for [Aggregate]/[FirstValue]/[LastValue] - `null` means the default SQL
|
||||||
|
* frame (`RANGE UNBOUNDED PRECEDING AND CURRENT ROW` with an ORDER BY, the whole partition without
|
||||||
|
* one); every other kind rejects an explicit [frame] outright since it would not change their result.
|
||||||
|
*/
|
||||||
|
data class WindowExpression(
|
||||||
|
val name: String,
|
||||||
|
val kind: WindowFunctionKind,
|
||||||
|
val argument: Expression?,
|
||||||
|
val star: Boolean,
|
||||||
|
val partitionBy: List<Expression>,
|
||||||
|
val orderBy: List<OrderKey>,
|
||||||
|
val offset: Expression? = null,
|
||||||
|
val defaultValue: Expression? = null,
|
||||||
|
val frame: WindowFrame? = null
|
||||||
) : Expression
|
) : Expression
|
||||||
|
|
||||||
data class BinaryExpression(
|
data class BinaryExpression(
|
||||||
@@ -438,6 +583,25 @@ data class BinaryExpression(
|
|||||||
|
|
||||||
data class UnaryExpression(val operator: UnaryOperator, val operand: Expression) : Expression
|
data class UnaryExpression(val operator: UnaryOperator, val operand: Expression) : Expression
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `expr COLLATE name` (SQL:1999) - overrides the text-comparison sequence used for [operand]
|
||||||
|
* wherever it participates in a comparison (`=`/`<>`/`<`/`<=`/`>`/`>=`, `IN`, `BETWEEN`, a
|
||||||
|
* searched-`CASE`'s operand match) - see [kestrel.engine.Evaluator]'s own comparison helpers, the
|
||||||
|
* only place a [Collation] other than [Collation.Binary] changes behavior this way. Evaluating
|
||||||
|
* [operand] itself is unaffected ([kestrel.engine.Evaluator.evaluate] just recurses into it) -
|
||||||
|
* COLLATE only ever changes how two already-evaluated values are ordered against each other, never
|
||||||
|
* what value an expression itself produces. Always wins over a column's own declared COLLATE (a
|
||||||
|
* `col TYPE COLLATE name` on its [kestrel.catalog.Column], resolved automatically for a bare
|
||||||
|
* [ColumnExpression] with no [CollateExpression] of its own via
|
||||||
|
* [kestrel.engine.ColumnResolver.declaredCollation]) - explicit beats declared, exactly like
|
||||||
|
* SQLite. `ORDER BY`/window `OVER (... ORDER BY ...)` (see [kestrel.engine.compareOrderKeyValues])
|
||||||
|
* only ever honor an explicit [CollateExpression] on the sort key, never a bare column's declared
|
||||||
|
* COLLATE - a documented, narrower scope cut than before: sorting a `COLLATE NOCASE` column still
|
||||||
|
* needs `ORDER BY col COLLATE NOCASE` spelled out, only comparisons pick the declared one up for
|
||||||
|
* free.
|
||||||
|
*/
|
||||||
|
data class CollateExpression(val operand: Expression, val collation: Collation) : Expression
|
||||||
|
|
||||||
enum class BinaryOperator(val symbol: String) {
|
enum class BinaryOperator(val symbol: String) {
|
||||||
Add("+"), Subtract("-"), Multiply("*"), Divide("/"),
|
Add("+"), Subtract("-"), Multiply("*"), Divide("/"),
|
||||||
Equal("="), NotEqual("<>"), Less("<"), LessEqual("<="),
|
Equal("="), NotEqual("<>"), Less("<"), LessEqual("<="),
|
||||||
|
|||||||
Reference in new issue
Block a user