feat(engine): extend SelectRunner with describeFunctionArguments
This commit is contained in:
1 parent
842d0aa223
commit
02741c8ab5
3 files changed
+113
-4
No files matched your search
@@ -1630,11 +1630,37 @@ class SelectRunner(private val catalog: Catalog) {
|
|||||||
is ParameterExpression -> "\$${expression.index}"
|
is ParameterExpression -> "\$${expression.index}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun describeFunctionArguments(expression: FunctionExpression): String {
|
||||||
|
if (expression.star) return "*"
|
||||||
|
val distinctPrefix = if (expression.distinct) "DISTINCT " else ""
|
||||||
|
val value = expression.argument?.let { describeExpression(it) } ?: ""
|
||||||
|
val separator = expression.separator ?: return "$distinctPrefix$value"
|
||||||
|
val orderBy = if (expression.orderBy.isNotEmpty()) {
|
||||||
|
" ORDER BY " + expression.orderBy.joinToString(", ") { key -> describeExpression(key.expression) + orderKeySuffix(key) }
|
||||||
|
} else ""
|
||||||
|
return "$distinctPrefix$value, ${describeExpression(separator)}$orderBy"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun orderKeySuffix(key: OrderKey): String {
|
||||||
|
val direction = if (key.descending) " DESC" else ""
|
||||||
|
val nulls = when (key.nullsFirst) {
|
||||||
|
true -> " NULLS FIRST"
|
||||||
|
false -> " NULLS LAST"
|
||||||
|
null -> ""
|
||||||
|
}
|
||||||
|
return direction + nulls
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun describeFilterClause(expression: FunctionExpression): String =
|
||||||
|
expression.filter?.let { " FILTER (WHERE ${describeExpression(it)})" } ?: ""
|
||||||
|
|
||||||
private fun literalText(value: Value): String = when (value) {
|
private fun literalText(value: Value): String = when (value) {
|
||||||
is Value.IntegerValue -> value.value.toString()
|
is Value.IntegerValue -> value.value.toString()
|
||||||
is Value.RealValue -> value.value.toString()
|
is Value.RealValue -> value.value.toString()
|
||||||
is Value.TextValue -> "'${value.value}'"
|
is Value.TextValue -> "'${value.value}'"
|
||||||
is Value.BooleanValue -> value.value.toString()
|
is Value.BooleanValue -> value.value.toString()
|
||||||
|
is Value.DecimalValue -> value.value.toPlainString()
|
||||||
|
is Value.DateValue -> value.value.toString()
|
||||||
is Value.NullValue -> "NULL"
|
is Value.NullValue -> "NULL"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1646,5 +1672,12 @@ class SelectRunner(private val catalog: Catalog) {
|
|||||||
val offset: Int
|
val offset: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
private class HashKeys(val buildExpression: Expression, val probeExpression: Expression)
|
private class HashKeys(val buildExpression: Expression, val probeExpression: Expression, val collation: Collation)
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
const val MAX_RECURSION_ITERATIONS = 10_000
|
||||||
|
|
||||||
|
/** Kinds whose result actually depends on which rows fall inside the frame. */
|
||||||
|
val FRAME_CAPABLE_KINDS = setOf(WindowFunctionKind.Aggregate, WindowFunctionKind.FirstValue, WindowFunctionKind.LastValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,20 +6,46 @@ sealed interface StatementResult
|
|||||||
|
|
||||||
data class SelectResult(val columns: List<String>, val rows: List<List<Value>>) : StatementResult
|
data class SelectResult(val columns: List<String>, val rows: List<List<Value>>) : StatementResult
|
||||||
|
|
||||||
data class InsertResult(val count: Int) : StatementResult
|
data class InsertResult(val count: Int, val returning: SelectResult? = null) : StatementResult
|
||||||
|
|
||||||
data class UpdateResult(val count: Int) : StatementResult
|
data class UpdateResult(val count: Int, val returning: SelectResult? = null) : StatementResult
|
||||||
|
|
||||||
data class DeleteResult(val count: Int) : StatementResult
|
data class DeleteResult(val count: Int, val returning: SelectResult? = null) : StatementResult
|
||||||
|
|
||||||
data class CreateTableResult(val tableName: String) : StatementResult
|
data class CreateTableResult(val tableName: String) : StatementResult
|
||||||
|
|
||||||
data class DropTableResult(val tableName: String) : StatementResult
|
data class DropTableResult(val tableName: String) : StatementResult
|
||||||
|
|
||||||
|
/** [tableNames] is every table actually truncated - the statement's own list plus, under CASCADE, every transitive FOREIGN KEY referencer added to it. */
|
||||||
|
data class TruncateTableResult(val tableNames: List<String>) : StatementResult
|
||||||
|
|
||||||
|
data class MergeResult(
|
||||||
|
val insertedCount: Int,
|
||||||
|
val updatedCount: Int,
|
||||||
|
val deletedCount: Int,
|
||||||
|
val returning: SelectResult? = null
|
||||||
|
) : StatementResult
|
||||||
|
|
||||||
|
data class AlterTableResult(val tableName: String) : StatementResult
|
||||||
|
|
||||||
data class CreateIndexResult(val indexName: String) : StatementResult
|
data class CreateIndexResult(val indexName: String) : StatementResult
|
||||||
|
|
||||||
data class DropIndexResult(val indexName: String) : StatementResult
|
data class DropIndexResult(val indexName: String) : StatementResult
|
||||||
|
|
||||||
|
data class CreateViewResult(val viewName: String) : StatementResult
|
||||||
|
|
||||||
|
data class DropViewResult(val viewName: String) : StatementResult
|
||||||
|
|
||||||
|
data class CreateTriggerResult(val triggerName: String) : StatementResult
|
||||||
|
|
||||||
|
data class DropTriggerResult(val triggerName: String) : StatementResult
|
||||||
|
|
||||||
|
data class CreateSequenceResult(val sequenceName: String) : StatementResult
|
||||||
|
|
||||||
|
data class DropSequenceResult(val sequenceName: String) : StatementResult
|
||||||
|
|
||||||
|
data class AlterSequenceResult(val sequenceName: String) : StatementResult
|
||||||
|
|
||||||
data class ExplainResult(val plan: List<String>) : StatementResult
|
data class ExplainResult(val plan: List<String>) : StatementResult
|
||||||
|
|
||||||
data object BeginResult : StatementResult
|
data object BeginResult : StatementResult
|
||||||
@@ -27,3 +53,14 @@ data object BeginResult : StatementResult
|
|||||||
data object CommitResult : StatementResult
|
data object CommitResult : StatementResult
|
||||||
|
|
||||||
data object RollbackResult : StatementResult
|
data object RollbackResult : StatementResult
|
||||||
|
|
||||||
|
data class SavepointResult(val name: String) : StatementResult
|
||||||
|
|
||||||
|
data class ReleaseSavepointResult(val name: String) : StatementResult
|
||||||
|
|
||||||
|
data class RollbackToSavepointResult(val name: String) : StatementResult
|
||||||
|
|
||||||
|
data class PrepareResult(val name: String) : StatementResult
|
||||||
|
|
||||||
|
/** [name] is `null` exactly when [all] is `true` (`DEALLOCATE ALL`), mirroring [kestrel.sql.ast.DeallocateStatement]. */
|
||||||
|
data class DeallocateResult(val name: String?, val all: Boolean) : StatementResult
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package kestrel.engine
|
||||||
|
|
||||||
|
import kestrel.KestrelException
|
||||||
|
import kestrel.catalog.TableSchema
|
||||||
|
import kestrel.sql.ast.ColumnExpression
|
||||||
|
import kestrel.storage.Collation
|
||||||
|
import kestrel.storage.Row
|
||||||
|
import kestrel.storage.Value
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds `NEW`/`OLD` for the one row a firing trigger is currently processing, against [schema] -
|
||||||
|
* always the trigger's OWN table (the one named in its `ON <table>`), never whatever table a
|
||||||
|
* nested body statement happens to target. [newRow] is null for a DELETE trigger, [oldRow] is null
|
||||||
|
* for an INSERT trigger; both are non-null only inside an UPDATE trigger.
|
||||||
|
*/
|
||||||
|
class TriggerRowContext(private val schema: TableSchema, private val newRow: Row?, private val oldRow: Row?) {
|
||||||
|
fun matches(expression: ColumnExpression): Boolean {
|
||||||
|
val qualifier = expression.qualifier ?: return false
|
||||||
|
return (qualifier.equals("new", ignoreCase = true) && newRow != null) ||
|
||||||
|
(qualifier.equals("old", ignoreCase = true) && oldRow != null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resolve(expression: ColumnExpression): Value {
|
||||||
|
val row = if (expression.qualifier!!.equals("new", ignoreCase = true)) newRow!! else oldRow!!
|
||||||
|
val index = schema.columnIndex(expression.name)
|
||||||
|
?: throw KestrelException("no such column: ${expression.name}")
|
||||||
|
return row.values[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Column-existence check with no row required, for validating a reference before it is ever fired. */
|
||||||
|
fun checkColumnExists(expression: ColumnExpression) {
|
||||||
|
if (schema.columnIndex(expression.name) == null) {
|
||||||
|
throw KestrelException("no such column: ${expression.name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** `NEW.col`/`OLD.col` reads the same table's schema either way, so its declared COLLATE never depends on which side [expression] names. */
|
||||||
|
fun declaredCollation(expression: ColumnExpression): Collation? = schema.column(expression.name)?.collation
|
||||||
|
}
|
||||||
Reference in new issue
Block a user