Skip to content

Commit bbb26dd

Browse files
authored
Merge 6fe4d96 into ef2847e
2 parents ef2847e + 6fe4d96 commit bbb26dd

14 files changed

+565
-307
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2828
- Adds support for SQL's CURRENT_USER in the AST, EvaluatingCompiler, experimental planner implementation, and Schema Inferencer.
2929
- Adds the AST node `session_attribute`.
3030
- Adds the function `EvaluationSession.Builder::user()` to add the CURRENT_USER to the EvaluationSession
31+
- Adds support for parsing and planning of `INSERT INTO .. AS <alias> ... ON CONFLICT DO [UPDATE|REPLACE] EXCLUDED WHERE <expr>`
32+
- Adds the `statement.dml` and `dml_operation` node to the experimental PartiQL Physical Plan.
3133

3234
### Changed
3335

36+
- **Breaking**: Adds a new property `as_alias` to the `insert` AST node.
37+
- **Breaking**: Adds new property `condition` to the AST nodes of `do_replace` and `do_update`
38+
- **Breaking**: Adds `target_alias` property to the `dml_insert`, `dml_replace`, and `dml_update` nodes within the
39+
Logical and Logical Resolved plans
40+
- **Breaking**: Adds `condition` property to the `dml_replace` and `dml_update` nodes within the
41+
Logical and Logical Resolved plans
42+
3443
### Deprecated
3544

3645
### Fixed
3746

47+
- Parsing INSERT statements with aliases no longer loses the original table name. Closes #1043.
48+
3849
### Removed
3950

51+
- **Breaking**: Removes node `statement.dml_query` from the experimental PartiQL Physical Plan. Please see the added
52+
`statement.dml` and `dml_operation` nodes.
53+
4054
### Security
4155

4256
## [0.9.3] - 2023-04-12

partiql-lang/src/main/antlr/PartiQL.g4

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ removeCommand
161161
insertCommandReturning
162162
: INSERT INTO pathSimple VALUE value=expr ( AT pos=expr )? onConflictClause? returningClause?;
163163

164+
// TODO: Update grammar to disallow the mixing and matching of `insert`, `insertLegacy`, `onConflict`, and `onConflictLegacy`
165+
// See https://github.com/partiql/partiql-lang-kotlin/issues/1063 for more details.
164166
insertCommand
165167
: INSERT INTO pathSimple VALUE value=expr ( AT pos=expr )? onConflictClause? # InsertLegacy
166168
// See the Grammar at https://github.com/partiql/partiql-docs/blob/main/RFCs/0011-partiql-insert.md#2-proposed-grammar-and-semantics
@@ -197,7 +199,7 @@ conflictAction
197199
[ WHERE <condition> ]
198200
*/
199201
doReplace
200-
: EXCLUDED;
202+
: EXCLUDED ( WHERE condition=expr )?;
201203
// :TODO add the rest of the grammar
202204

203205
/*
@@ -207,7 +209,7 @@ doReplace
207209
[ WHERE <condition> ]
208210
*/
209211
doUpdate
210-
: EXCLUDED;
212+
: EXCLUDED ( WHERE condition=expr )?;
211213
// :TODO add the rest of the grammar
212214

213215
updateClause

partiql-lang/src/main/kotlin/org/partiql/lang/compiler/PartiQLCompilerDefault.kt

Lines changed: 18 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,8 @@ import org.partiql.lang.domains.PartiqlLogical
2020
import org.partiql.lang.domains.PartiqlLogicalResolved
2121
import org.partiql.lang.domains.PartiqlPhysical
2222
import org.partiql.lang.errors.PartiQLException
23-
import org.partiql.lang.eval.BindingCase
24-
import org.partiql.lang.eval.BindingName
25-
import org.partiql.lang.eval.Bindings
2623
import org.partiql.lang.eval.ExprFunction
2724
import org.partiql.lang.eval.ExprValue
28-
import org.partiql.lang.eval.Expression
2925
import org.partiql.lang.eval.PartiQLResult
3026
import org.partiql.lang.eval.PartiQLStatement
3127
import org.partiql.lang.eval.builtins.storedprocedure.StoredProcedure
@@ -35,10 +31,6 @@ import org.partiql.lang.eval.physical.PhysicalPlanCompilerImpl
3531
import org.partiql.lang.eval.physical.PhysicalPlanThunk
3632
import org.partiql.lang.eval.physical.operators.RelationalOperatorFactory
3733
import org.partiql.lang.eval.physical.operators.RelationalOperatorFactoryKey
38-
import org.partiql.lang.planner.DML_COMMAND_FIELD_ACTION
39-
import org.partiql.lang.planner.DML_COMMAND_FIELD_ROWS
40-
import org.partiql.lang.planner.DML_COMMAND_FIELD_TARGET_UNIQUE_ID
41-
import org.partiql.lang.planner.DmlAction
4234
import org.partiql.lang.planner.EvaluatorOptions
4335
import org.partiql.lang.planner.PartiQLPlanner
4436
import org.partiql.lang.types.TypedOpParameter
@@ -71,18 +63,20 @@ internal class PartiQLCompilerDefault(
7163
}
7264

7365
override fun compile(statement: PartiqlPhysical.Plan): PartiQLStatement {
74-
val expression = exprConverter.compile(statement)
7566
return when (statement.stmt) {
76-
is PartiqlPhysical.Statement.DmlQuery -> PartiQLStatement { expression.eval(it).toDML() }
67+
is PartiqlPhysical.Statement.Dml -> compileDml(statement.stmt, statement.locals.size)
7768
is PartiqlPhysical.Statement.Exec,
78-
is PartiqlPhysical.Statement.Query -> PartiQLStatement { expression.eval(it).toValue() }
69+
is PartiqlPhysical.Statement.Query -> {
70+
val expression = exprConverter.compile(statement)
71+
PartiQLStatement { expression.eval(it).toValue() }
72+
}
7973
is PartiqlPhysical.Statement.Explain -> throw PartiQLException("Unable to compile EXPLAIN without details.")
8074
}
8175
}
8276

8377
override fun compile(statement: PartiqlPhysical.Plan, details: PartiQLPlanner.PlanningDetails): PartiQLStatement {
8478
return when (statement.stmt) {
85-
is PartiqlPhysical.Statement.DmlQuery,
79+
is PartiqlPhysical.Statement.Dml -> compileDml(statement.stmt, statement.locals.size)
8680
is PartiqlPhysical.Statement.Exec,
8781
is PartiqlPhysical.Statement.Query -> compile(statement)
8882
is PartiqlPhysical.Statement.Explain -> PartiQLStatement { compileExplain(statement.stmt, details) }
@@ -100,6 +94,18 @@ internal class PartiQLCompilerDefault(
10094
PHYSICAL_TRANSFORMED
10195
}
10296

97+
private fun compileDml(dml: PartiqlPhysical.Statement.Dml, localsSize: Int): PartiQLStatement {
98+
val rows = exprConverter.compile(dml.rows, localsSize)
99+
return PartiQLStatement { session ->
100+
when (dml.operation) {
101+
is PartiqlPhysical.DmlOperation.DmlReplace -> PartiQLResult.Replace(dml.uniqueId.text, rows.eval(session))
102+
is PartiqlPhysical.DmlOperation.DmlInsert -> PartiQLResult.Insert(dml.uniqueId.text, rows.eval(session))
103+
is PartiqlPhysical.DmlOperation.DmlDelete -> PartiQLResult.Delete(dml.uniqueId.text, rows.eval(session))
104+
is PartiqlPhysical.DmlOperation.DmlUpdate -> TODO("DML Update compilation not supported yet.")
105+
}
106+
}
107+
}
108+
103109
private fun compileExplain(statement: PartiqlPhysical.Statement.Explain, details: PartiQLPlanner.PlanningDetails): PartiQLResult.Explain.Domain {
104110
return when (statement.target) {
105111
is PartiqlPhysical.ExplainTarget.Domain -> compileExplainDomain(statement.target, details)
@@ -152,44 +158,5 @@ internal class PartiQLCompilerDefault(
152158
}
153159
}
154160

155-
/**
156-
* The physical expr converter is EvaluatingCompiler with s/Ast/Physical and `bindingsToValues -> Thunk`
157-
* so it returns the [Expression] rather than a [PartiQLStatement]. This method parses a DML Command from the result.
158-
*
159-
* {
160-
* 'action': <action>,
161-
* 'target_unique_id': <unique_id>
162-
* 'rows': <rows>
163-
* }
164-
*
165-
* Later refactors will rework the Compiler to use [PartiQLStatement], but this is an acceptable workaround for now.
166-
*/
167-
private fun ExprValue.toDML(): PartiQLResult {
168-
val action = bindings string DML_COMMAND_FIELD_ACTION
169-
val target = bindings string DML_COMMAND_FIELD_TARGET_UNIQUE_ID
170-
val rows = bindings seq DML_COMMAND_FIELD_ROWS
171-
return when (DmlAction.safeValueOf(action)) {
172-
DmlAction.INSERT -> PartiQLResult.Insert(target, rows)
173-
DmlAction.DELETE -> PartiQLResult.Delete(target, rows)
174-
DmlAction.REPLACE -> PartiQLResult.Replace(target, rows)
175-
null -> error("Unknown DML Action `$action`")
176-
}
177-
}
178-
179161
private fun ExprValue.toValue(): PartiQLResult = PartiQLResult.Value(this)
180-
181-
private infix fun Bindings<ExprValue>.string(field: String): String {
182-
return this[BindingName(field, BindingCase.SENSITIVE)]?.scalar?.stringValue() ?: missing(field)
183-
}
184-
185-
private infix fun Bindings<ExprValue>.seq(field: String): Iterable<ExprValue> {
186-
val v = this[BindingName(field, BindingCase.SENSITIVE)] ?: missing(field)
187-
if (!v.type.isSequence) {
188-
error("DML command struct '$DML_COMMAND_FIELD_ROWS' field must be a bag or list")
189-
}
190-
return v.asIterable()
191-
}
192-
193-
private fun missing(field: String): Nothing =
194-
error("Field `$field` missing from DML command struct or has incorrect Ion type")
195162
}

partiql-lang/src/main/kotlin/org/partiql/lang/eval/physical/PhysicalPlanCompilerImpl.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,27 @@ internal class PhysicalPlanCompilerImpl(
173173
}
174174
}
175175

176+
/**
177+
* Compiles a [PartiqlPhysical.Expr] tree to an [Expression].
178+
*
179+
* Checks [Thread.interrupted] before every expression and sub-expression is compiled
180+
* and throws [InterruptedException] if [Thread.interrupted] it has been set in the
181+
* hope that long-running compilations may be aborted by the caller.
182+
*/
183+
internal fun compile(expr: PartiqlPhysical.Expr, localsSize: Int): Expression {
184+
val thunk = compileAstExpr(expr)
185+
186+
return object : Expression {
187+
override fun eval(session: EvaluationSession): ExprValue {
188+
val env = EvaluatorState(
189+
session = session,
190+
registers = Array(localsSize) { ExprValue.missingValue }
191+
)
192+
return thunk(env)
193+
}
194+
}
195+
}
196+
176197
override fun convert(expr: PartiqlPhysical.Expr): PhysicalPlanThunk = this.compileAstExpr(expr)
177198

178199
/**
@@ -183,8 +204,8 @@ internal class PhysicalPlanCompilerImpl(
183204
private fun compileAstStatement(ast: PartiqlPhysical.Statement): PhysicalPlanThunk {
184205
return when (ast) {
185206
is PartiqlPhysical.Statement.Query -> compileAstExpr(ast.expr)
186-
is PartiqlPhysical.Statement.DmlQuery -> compileAstExpr(ast.expr)
187207
is PartiqlPhysical.Statement.Exec -> compileExec(ast)
208+
is PartiqlPhysical.Statement.Dml,
188209
is PartiqlPhysical.Statement.Explain -> {
189210
val value = ExprValue.newBoolean(true)
190211
thunkFactory.thunkEnv(emptyMetaContainer()) { value }

partiql-lang/src/main/kotlin/org/partiql/lang/planner/QueryPlan.kt

Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import org.partiql.lang.eval.BindingName
55
import org.partiql.lang.eval.Bindings
66
import org.partiql.lang.eval.EvaluationSession
77
import org.partiql.lang.eval.ExprValue
8-
import org.partiql.lang.eval.ExprValueType
98

109
/** A query plan that has been compiled and is ready to be evaluated. */
1110
fun interface QueryPlan {
@@ -29,7 +28,7 @@ sealed class QueryResult {
2928
*
3029
* The primary benefit of this class is that it ensures that the [rows] property is evaluated lazily. It also
3130
* provides a cleaner API that is easier to work with for PartiQL embedders. Without this, the user would have to
32-
* consume the [ExprValue] directly and use code similar to that in [toDmlCommand] or convert it to Ion. Neither
31+
* consume the [ExprValue] directly and convert it to Ion. Neither
3332
* option is particularly developer friendly, efficient or maintainable.
3433
*
3534
* This is currently only factored to support `INSERT INTO` and `DELETE FROM` as `UPDATE` and `FROM ... UPDATE` is
@@ -67,54 +66,5 @@ enum class DmlAction {
6766
}
6867
}
6968

70-
internal const val DML_COMMAND_FIELD_ACTION = "action"
71-
internal const val DML_COMMAND_FIELD_TARGET_UNIQUE_ID = "target_unique_id"
72-
internal const val DML_COMMAND_FIELD_ROWS = "rows"
73-
7469
private operator fun Bindings<ExprValue>.get(fieldName: String): ExprValue? =
7570
this[BindingName(fieldName, BindingCase.SENSITIVE)]
76-
77-
private fun errMissing(fieldName: String): Nothing =
78-
error("'$fieldName' missing from DML command struct or has incorrect Ion type")
79-
80-
/**
81-
* Converts an [ExprValue] which is the result of a DML query to an instance of [DmlCommand].
82-
*
83-
* Format of a such an [ExprValue]:
84-
*
85-
* ```
86-
* {
87-
* 'action': <action>,
88-
* 'target_unique_id': <unique_id>
89-
* 'rows': <rows>
90-
* }
91-
* ```
92-
*
93-
* Where:
94-
* - `<action>` is either `insert` or `delete`
95-
* - `<target_unique_id>` is a string or symbol containing the unique identifier of the table to be effected
96-
* by the DML statement.
97-
* - `<rows>` is a bag or list containing the rows (structs) effected by the DML statement.
98-
* - When `<action>` is `insert` this is the rows to be inserted.
99-
* - When `<action>` is `delete` this is the rows to be deleted. Non-primary key fields may be elided, but the
100-
* default behavior is to include all fields because PartiQL does not yet know about primary keys.
101-
*/
102-
internal fun ExprValue.toDmlCommand(): QueryResult.DmlCommand {
103-
require(this.type == ExprValueType.STRUCT) { "'row' must be a struct" }
104-
105-
val actionString = this.bindings[DML_COMMAND_FIELD_ACTION]?.scalar?.stringValue()?.toUpperCase()
106-
?: errMissing(DML_COMMAND_FIELD_ACTION)
107-
108-
val dmlAction = DmlAction.safeValueOf(actionString)
109-
?: error("Unknown DmlAction in DML command struct: '$actionString'")
110-
111-
val targetUniqueId = this.bindings[DML_COMMAND_FIELD_TARGET_UNIQUE_ID]?.scalar?.stringValue()
112-
?: errMissing(DML_COMMAND_FIELD_TARGET_UNIQUE_ID)
113-
114-
val rows = this.bindings[DML_COMMAND_FIELD_ROWS] ?: errMissing(DML_COMMAND_FIELD_ROWS)
115-
if (!rows.type.isSequence) {
116-
error("DML command struct '$DML_COMMAND_FIELD_ROWS' field must be a bag or list")
117-
}
118-
119-
return QueryResult.DmlCommand(dmlAction, targetUniqueId, rows)
120-
}

partiql-lang/src/main/kotlin/org/partiql/lang/planner/transforms/AstToLogicalVisitorTransform.kt

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -363,46 +363,34 @@ internal class AstToLogicalVisitorTransform(
363363
}
364364
}
365365

366-
when (val conflictAction = dmlOp.conflictAction) {
367-
null -> {
368-
PartiqlLogical.build {
369-
dml(
370-
target = dmlOp.target.toDmlTargetId(),
371-
operation = dmlInsert(),
372-
rows = transformExpr(dmlOp.values),
373-
metas = node.metas
374-
)
375-
}
376-
}
377-
is PartiqlAst.ConflictAction.DoReplace -> {
378-
when (conflictAction.value) {
379-
PartiqlAst.OnConflictValue.Excluded() -> PartiqlLogical.build {
380-
dml(
381-
target = dmlOp.target.toDmlTargetId(),
382-
operation = dmlReplace(),
383-
rows = transformExpr(dmlOp.values),
384-
metas = node.metas
385-
)
386-
} else -> TODO("Only `DO REPLACE EXCLUDED` is supported in logical plan at the moment.")
387-
}
366+
val target = dmlOp.target.toDmlTargetId()
367+
val alias = dmlOp.asAlias?.let {
368+
PartiqlLogical.VarDecl(it)
369+
} ?: PartiqlLogical.VarDecl(target.name)
370+
371+
val operation = when (val conflictAction = dmlOp.conflictAction) {
372+
null -> PartiqlLogical.DmlOperation.DmlInsert(targetAlias = alias)
373+
is PartiqlAst.ConflictAction.DoReplace -> when (conflictAction.value) {
374+
is PartiqlAst.OnConflictValue.Excluded -> PartiqlLogical.DmlOperation.DmlReplace(
375+
targetAlias = alias,
376+
condition = conflictAction.condition?.let { transformExpr(conflictAction.condition) }
377+
)
388378
}
389-
is PartiqlAst.ConflictAction.DoUpdate -> {
390-
when (conflictAction.value) {
391-
PartiqlAst.OnConflictValue.Excluded() -> PartiqlLogical.build {
392-
dml(
393-
target = dmlOp.target.toDmlTargetId(),
394-
operation = dmlUpdate(),
395-
rows = transformExpr(dmlOp.values),
396-
metas = node.metas
397-
)
398-
}
399-
else -> TODO("Only `DO UPDATE EXCLUDED` is supported in logical plan at the moment.")
400-
}
379+
is PartiqlAst.ConflictAction.DoUpdate -> when (conflictAction.value) {
380+
is PartiqlAst.OnConflictValue.Excluded -> PartiqlLogical.DmlOperation.DmlUpdate(
381+
targetAlias = alias,
382+
condition = conflictAction.condition?.let { transformExpr(conflictAction.condition) }
383+
)
401384
}
402-
is PartiqlAst.ConflictAction.DoNothing -> TODO(
403-
"`ON CONFLICT DO NOTHING` is not supported in logical plan yet."
404-
)
385+
is PartiqlAst.ConflictAction.DoNothing -> TODO("`ON CONFLICT DO NOTHING` is not supported in logical plan yet.")
405386
}
387+
388+
PartiqlLogical.Statement.Dml(
389+
target = target,
390+
operation = operation,
391+
rows = transformExpr(dmlOp.values),
392+
metas = node.metas
393+
)
406394
}
407395
// INSERT single row with VALUE is disallowed. (This variation of INSERT might be removed in a future
408396
// release of PartiQL.)

0 commit comments

Comments
 (0)