All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.partiql.lang.planner.QueryPlan.kt Maven / Gradle / Ivy

There is a newer version: 1.0.0-perf.1
Show newest version
package org.partiql.lang.planner

import org.partiql.lang.eval.BindingCase
import org.partiql.lang.eval.BindingName
import org.partiql.lang.eval.Bindings
import org.partiql.lang.eval.EvaluationSession
import org.partiql.lang.eval.ExprValue
import org.partiql.lang.eval.ExprValueType

/** A query plan that has been compiled and is ready to be evaluated. */
fun interface QueryPlan {
    /**
     * Evaluates the query plan with the given Session.
     */
    fun eval(session: EvaluationSession): QueryResult
}

sealed class QueryResult {
    /**
     * The result of an SFW query, arbitrary expression, or `EXEC` stored procedure call.
     */
    class Value(val value: ExprValue) : QueryResult()

    /**
     * The result of a INSERT or DELETE statement.  (UPDATE is out of scope for now.)
     *
     * Each instance of a DML command denotes an operation to be performed by the embedding PartiQL application
     * to effect the writes specified by a DML operation.
     *
     * The primary benefit of this class is that it ensures that the [rows] property is evaluated lazily.  It also
     * provides a cleaner API that is easier to work with for PartiQL embedders.  Without this, the user would have to
     * consume the [ExprValue] directly and use code similar to that in [toDmlCommand] or convert it to Ion.  Neither
     * option is particularly developer friendly, efficient or maintainable.
     *
     * This is currently only factored to support `INSERT INTO` and `DELETE FROM` as `UPDATE` and `FROM ... UPDATE` is
     * out of scope for the current effort.
     */
    data class DmlCommand(
        /** Identifies the action to take. */
        val action: DmlAction,
        /** The unique identifier of the table targed by the DML statement. */
        val targetUniqueId: String,
        /**
         * The rows to be inserted or deleted.
         *
         * In the case of delete, the rows must contain at least the fields which comprise the table's primary key.
         */
        val rows: Iterable
    ) : QueryResult()
}

/**
 * Identifies the action to take.
 * TODO This should be represented in the IR grammar - https://github.com/partiql/partiql-lang-kotlin/issues/756
 */
enum class DmlAction {
    INSERT,
    DELETE,
    REPLACE;

    companion object {
        fun safeValueOf(v: String): DmlAction? = try {
            valueOf(v.toUpperCase())
        } catch (ex: IllegalArgumentException) {
            null
        }
    }
}

internal const val DML_COMMAND_FIELD_ACTION = "action"
internal const val DML_COMMAND_FIELD_TARGET_UNIQUE_ID = "target_unique_id"
internal const val DML_COMMAND_FIELD_ROWS = "rows"

private operator fun Bindings.get(fieldName: String): ExprValue? =
    this[BindingName(fieldName, BindingCase.SENSITIVE)]

private fun errMissing(fieldName: String): Nothing =
    error("'$fieldName' missing from DML command struct or has incorrect Ion type")

/**
 * Converts an [ExprValue] which is the result of a DML query to an instance of [DmlCommand].
 *
 * Format of a such an [ExprValue]:
 *
 * ```
 * {
 *     'action': ,
 *     'target_unique_id': 
 *     'rows': 
 * }
 * ```
 *
 * Where:
 *  - `` is either `insert` or `delete`
 *  - `` is a string or symbol containing the unique identifier of the table to be effected
 *  by the DML statement.
 *  - `` is a bag or list containing the rows (structs) effected by the DML statement.
 *      - When `` is `insert` this is the rows to be inserted.
 *      - When `` is `delete` this is the rows to be deleted.  Non-primary key fields may be elided, but the
 *      default behavior is to include all fields because PartiQL does not yet know about primary keys.
 */
internal fun ExprValue.toDmlCommand(): QueryResult.DmlCommand {
    require(this.type == ExprValueType.STRUCT) { "'row' must be a struct" }

    val actionString = this.bindings[DML_COMMAND_FIELD_ACTION]?.scalar?.stringValue()?.toUpperCase()
        ?: errMissing(DML_COMMAND_FIELD_ACTION)

    val dmlAction = DmlAction.safeValueOf(actionString)
        ?: error("Unknown DmlAction in DML command struct: '$actionString'")

    val targetUniqueId = this.bindings[DML_COMMAND_FIELD_TARGET_UNIQUE_ID]?.scalar?.stringValue()
        ?: errMissing(DML_COMMAND_FIELD_TARGET_UNIQUE_ID)

    val rows = this.bindings[DML_COMMAND_FIELD_ROWS] ?: errMissing(DML_COMMAND_FIELD_ROWS)
    if (!rows.type.isSequence) {
        error("DML command struct '$DML_COMMAND_FIELD_ROWS' field must be a bag or list")
    }

    return QueryResult.DmlCommand(dmlAction, targetUniqueId, rows)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy