Skip to content

Commit

Permalink
Removes PartiQLValue and adds support for VARIANT (partiql#1678)
Browse files Browse the repository at this point in the history
Removes PartiQLValue from public API
Removes PartiQLValueType from public API
Removes PartiQLValueExperimental from public API
Removes PartiQLValue reader/writer from public API
Adds full support for the VARIANT type
Adds PType static factory methods with default parameters
Adds AST-to-Plan typing logic of integers to the Ion Variant
Adds a lowerSafe() internal utility method for reducing code size
  • Loading branch information
johnedquinn authored Dec 27, 2024
1 parent ea779ee commit 1abe677
Show file tree
Hide file tree
Showing 104 changed files with 723 additions and 1,429 deletions.
3 changes: 1 addition & 2 deletions docs/wiki/tutorials/Pluggable Functions Tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import org.partiql.types.PartiQLValueType
import org.partiql.types.function.FunctionParameter
import org.partiql.types.function.FunctionSignature
import org.partiql.value.PartiQLValue
import org.partiql.value.PartiQLValueExperimental

import org.partiql.value.StringValue
import org.partiql.value.stringValue

Expand All @@ -43,7 +43,6 @@ object TrimLead : PartiQLFunction {
description = "Trims leading whitespace of a [str]." // A brief description of your function
)

@OptIn(PartiQLValueExperimental::class)
override operator fun invoke(session: ConnectorSession, arguments: List<PartiQLValue>): PartiQLValue {
// Implement the function logic here
val str = (arguments[0] as? StringValue)?.string ?: ""
Expand Down
1 change: 1 addition & 0 deletions partiql-cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ dependencies {
implementation(project(":partiql-planner"))
implementation(project(":partiql-types"))
implementation(project(":partiql-spi"))
implementation(testFixtures(project(":partiql-spi")))
implementation(Deps.csv)
implementation(Deps.awsSdkBom)
implementation(Deps.awsSdkDynamodb)
Expand Down
8 changes: 4 additions & 4 deletions partiql-cli/src/main/kotlin/org/partiql/cli/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import org.partiql.spi.catalog.Session
import org.partiql.spi.catalog.Table
import org.partiql.spi.value.Datum
import org.partiql.spi.value.DatumReader
import org.partiql.value.PartiQLValueExperimental
import org.partiql.value.io.PartiQLValueTextWriter
import org.partiql.spi.value.ValueUtils
import org.partiql.spi.value.io.PartiQLValueTextWriter
import picocli.CommandLine
import java.io.File
import java.io.InputStream
Expand Down Expand Up @@ -187,7 +187,6 @@ internal class MainCommand : Runnable {
Shell(pipeline, session(), debug).start()
}

@OptIn(PartiQLValueExperimental::class)
private fun run(statement: String) {
val config = getPipelineConfig()
val pipeline = when (strict) {
Expand All @@ -206,7 +205,8 @@ internal class MainCommand : Runnable {
// TODO add format support
checkFormat(format)
val writer = PartiQLValueTextWriter(System.out)
writer.append(result.toPartiQLValue()) // TODO: Create a Datum writer
val p = ValueUtils.newPartiQLValue(result)
writer.append(p) // TODO: Create a Datum writer
println()
}

Expand Down
8 changes: 4 additions & 4 deletions partiql-cli/src/main/kotlin/org/partiql/cli/shell/Shell.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ import org.jline.utils.InfoCmp
import org.joda.time.Duration
import org.partiql.cli.pipeline.Pipeline
import org.partiql.spi.catalog.Session
import org.partiql.value.PartiQLValueExperimental
import org.partiql.value.io.PartiQLValueTextWriter
import org.partiql.spi.value.ValueUtils
import org.partiql.spi.value.io.PartiQLValueTextWriter
import java.io.Closeable
import java.io.PrintStream
import java.nio.file.Path
Expand Down Expand Up @@ -161,7 +161,6 @@ internal class Shell(
}
}

@OptIn(PartiQLValueExperimental::class)
private fun run(exiting: AtomicBoolean) = TerminalBuilder.builder()
.name("PartiQL")
.nativeSignals(true)
Expand Down Expand Up @@ -276,7 +275,8 @@ internal class Shell(
out.appendLine()
out.info("=== RESULT ===")
val writer = PartiQLValueTextWriter(out)
writer.append(result.toPartiQLValue()) // TODO: Create a Datum writer
val p = ValueUtils.newPartiQLValue(result)
writer.append(p) // TODO: Create a Datum writer
out.appendLine()
out.appendLine()
out.success("OK!")
Expand Down
1 change: 1 addition & 0 deletions partiql-eval/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dependencies {
// Test
testImplementation(project(":partiql-parser"))
testImplementation(testFixtures(project(":partiql-types"))) // TODO: Remove use of StaticType
testImplementation(testFixtures(project(":partiql-spi")))
testImplementation(Deps.junit4)
testImplementation(Deps.junit4Params)
testImplementation(Deps.junitVintage) // Enables JUnit4
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.partiql.eval.internal.helpers

import org.partiql.spi.value.Datum
import org.partiql.types.PType

internal object DatumUtils {

/**
* Calls [Datum.lower] if the datum is a variant, otherwise returns the datum. If you don't know whether the value
* is of type [PType.VARIANT], you should use [Datum.lowerSafe] before invoking whatever methods you intend to use.
* This is essentially a workaround for the fact that we currently don't know whether a particular expression will be
* [PType.VARIANT] or not. The planner/plan can eventually be optimized to accommodate this.
*/
internal fun Datum.lowerSafe(): Datum {
return when (this.type.code()) {
PType.VARIANT -> this.lower()
else -> this
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@ package org.partiql.eval.internal.helpers
import org.partiql.errors.TypeCheckException
import org.partiql.spi.value.Datum
import org.partiql.types.PType
import org.partiql.value.PartiQLValue
import org.partiql.value.PartiQLValueExperimental
import org.partiql.value.PartiQLValueType
import java.math.BigInteger

/**
* Holds helper functions for [PartiQLValue].
* Holds helper functions for [Datum].
*/
internal object ValueUtility {

Expand All @@ -18,12 +15,14 @@ internal object ValueUtility {
*/
@JvmStatic
fun Datum.isTrue(): Boolean {
return this.type.code() == PType.BOOL && !this.isNull && !this.isMissing && this.boolean
}

@OptIn(PartiQLValueExperimental::class)
fun Datum.check(type: PartiQLValueType): Datum {
return this.check(type.toPType())
if (this.isNull || this.isMissing) {
return false
}
return when (this.type.code()) {
PType.VARIANT -> this.lower().isTrue()
PType.BOOL -> this.boolean
else -> false
}
}

/**
Expand All @@ -37,6 +36,9 @@ internal object ValueUtility {
if (this.type == type) {
return this
}
if (this.type.code() == PType.VARIANT) {
return this.lower().check(type)
}
if (!this.isNull) {
throw TypeCheckException("Expected type $type but received ${this.type}.")
}
Expand All @@ -51,29 +53,42 @@ internal object ValueUtility {
*/
fun Datum.getText(): String {
return when (this.type.code()) {
PType.VARIANT -> this.lower().getText()
PType.STRING, PType.CHAR -> this.string
else -> throw TypeCheckException("Expected text, but received ${this.type}.")
}
}

/**
* Takes in a [Datum] that is any integer type ([PartiQLValueType.INT8], [PartiQLValueType.INT8],
* [PartiQLValueType.INT8], [PartiQLValueType.INT8], [PartiQLValueType.INT8]) and returns the [BigInteger] (potentially
* coerced) that represents the integer.
* Converts all number values to [BigInteger]. If the number is [PType.DECIMAL] or [PType.NUMERIC], this asserts that
* the scale is zero.
*
* INTERNAL NOTE: The PLANNER should be handling the coercion. This function should not be necessary.
*
* TODO: This is used specifically for LIMIT and OFFSET. This makes the conformance tests pass by coercing values
* of type [PType.NUMERIC] and [PType.DECIMAL], but this is unspecified. Do we allow for LIMIT 2.0? Or of
* a value that is greater than [PType.BIGINT]'s MAX value by using a [PType.DECIMAL] with a high precision and scale
* of zero? This hasn't been decided, however, as the conformance tests allow for this, this function coerces
* the value to a [BigInteger] regardless of the number's type.
*
* @throws NullPointerException if the value is null
* @throws TypeCheckException if type is not an integer type
*/
fun Datum.getBigIntCoerced(): BigInteger {
return when (this.type.code()) {
PType.VARIANT -> this.lower().getBigIntCoerced()
PType.TINYINT -> this.byte.toInt().toBigInteger()
PType.SMALLINT -> this.short.toInt().toBigInteger()
PType.INTEGER -> this.int.toBigInteger()
PType.BIGINT -> this.long.toBigInteger()
PType.NUMERIC -> this.bigInteger
else -> throw TypeCheckException()
PType.NUMERIC, PType.DECIMAL -> {
val decimal = this.bigDecimal
if (decimal.scale() != 0) {
throw TypeCheckException("Expected integer, but received decimal.")
}
return decimal.toBigInteger()
}
else -> throw TypeCheckException("Type: ${this.type}")
}
}

Expand All @@ -90,6 +105,7 @@ internal object ValueUtility {
*/
fun Datum.getInt32Coerced(): Int {
return when (this.type.code()) {
PType.VARIANT -> this.lower().getInt32Coerced()
PType.TINYINT -> this.byte.toInt()
PType.SMALLINT -> this.short.toInt()
PType.INTEGER -> this.int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import org.partiql.plan.Exclusion
import org.partiql.spi.value.Datum
import org.partiql.spi.value.Field
import org.partiql.types.PType
import org.partiql.value.PartiQLValue
import org.partiql.value.PartiQLValueType

/**
* Implementation of the EXCLUDE clause; there are good opportunities to tune/optimize this.
Expand Down Expand Up @@ -133,8 +131,8 @@ internal class RelOpExclude(
}

/**
* Returns a [PartiQLValue] created from an iterable of [coll]. Requires [type] to be a collection type
* (i.e. [PartiQLValueType.LIST] or [PartiQLValueType.BAG].).
* Returns a [Datum] created from an iterable of [coll]. Requires [type] to be a collection type
* (i.e. [PType.ARRAY] or [PType.BAG].).
*/
private fun newCollValue(type: PType, coll: Iterable<Datum>): Datum {
return when (type.code()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.partiql.eval.Environment
import org.partiql.eval.ExprRelation
import org.partiql.eval.ExprValue
import org.partiql.eval.Row
import org.partiql.eval.internal.helpers.DatumUtils.lowerSafe
import org.partiql.spi.value.Datum
import org.partiql.types.PType

Expand All @@ -16,7 +17,7 @@ internal class RelOpIterate(
private var index: Long = 0

override fun open(env: Environment) {
val r = expr.eval(env.push(Row()))
val r = expr.eval(env.push(Row())).lowerSafe()
index = 0
iterator = when (r.type.code()) {
PType.BAG -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.partiql.eval.Environment
import org.partiql.eval.ExprRelation
import org.partiql.eval.ExprValue
import org.partiql.eval.Row
import org.partiql.eval.internal.helpers.DatumUtils.lowerSafe
import org.partiql.spi.value.Datum
import org.partiql.types.PType

Expand All @@ -16,7 +17,7 @@ internal class RelOpIteratePermissive(
private var isIndexable: Boolean = true

override fun open(env: Environment) {
val r = expr.eval(env.push(Row()))
val r = expr.eval(env.push(Row())).lowerSafe()
index = 0
iterator = when (r.type.code()) {
PType.BAG -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.partiql.eval.Environment
import org.partiql.eval.ExprRelation
import org.partiql.eval.ExprValue
import org.partiql.eval.Row
import org.partiql.eval.internal.helpers.DatumUtils.lowerSafe
import org.partiql.eval.internal.helpers.RecordValueIterator
import org.partiql.types.PType

Expand All @@ -15,7 +16,7 @@ internal class RelOpScan(
private lateinit var records: Iterator<Row>

override fun open(env: Environment) {
val r = expr.eval(env.push(Row()))
val r = expr.eval(env.push(Row())).lowerSafe()
records = when (r.type.code()) {
PType.ARRAY, PType.BAG -> RecordValueIterator(r.iterator())
else -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.partiql.eval.Environment
import org.partiql.eval.ExprRelation
import org.partiql.eval.ExprValue
import org.partiql.eval.Row
import org.partiql.eval.internal.helpers.DatumUtils.lowerSafe
import org.partiql.eval.internal.helpers.RecordValueIterator
import org.partiql.types.PType

Expand All @@ -14,7 +15,7 @@ internal class RelOpScanPermissive(
private lateinit var records: Iterator<Row>

override fun open(env: Environment) {
val r = expr.eval(env.push(Row()))
val r = expr.eval(env.push(Row())).lowerSafe()
records = when (r.type.code()) {
PType.BAG, PType.ARRAY -> RecordValueIterator(r.iterator())
else -> iterator { yield(Row(arrayOf(r))) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.partiql.eval.Environment
import org.partiql.eval.ExprRelation
import org.partiql.eval.ExprValue
import org.partiql.eval.Row
import org.partiql.eval.internal.helpers.DatumUtils.lowerSafe
import org.partiql.spi.value.Datum
import org.partiql.spi.value.Field
import org.partiql.types.PType
Expand Down Expand Up @@ -58,7 +59,7 @@ internal sealed class RelOpUnpivot : ExprRelation {
class Strict(private val expr: ExprValue) : RelOpUnpivot() {

override fun struct(): Datum {
val v = expr.eval(env.push(Row()))
val v = expr.eval(env.push(Row())).lowerSafe()
if (v.type.code() != PType.STRUCT && v.type.code() != PType.ROW) {
throw TypeCheckException()
}
Expand All @@ -78,7 +79,7 @@ internal sealed class RelOpUnpivot : ExprRelation {
class Permissive(private val expr: ExprValue) : RelOpUnpivot() {

override fun struct(): Datum {
val v = expr.eval(env.push(Row()))
val v = expr.eval(env.push(Row())).lowerSafe()
if (v.isMissing) {
return Datum.struct(emptyList())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import org.partiql.types.PType.TIMESTAMPZ
import org.partiql.types.PType.TIMEZ
import org.partiql.types.PType.TINYINT
import org.partiql.types.PType.VARCHAR
import org.partiql.types.PType.VARIANT
import org.partiql.value.datetime.DateTimeValue
import java.math.BigDecimal
import java.math.BigInteger
Expand Down Expand Up @@ -105,6 +106,7 @@ internal object CastTable {
registerTimestamp()
registerDate()
registerTime()
registerVariant()
}

private fun String.pad(): String {
Expand Down Expand Up @@ -493,6 +495,13 @@ internal object CastTable {
register(TIMEZ, TIMESTAMPZ) { x, _ -> Datum.timestamp(DateTimeValue.timestamp(DateTimeValue.date(1970, 1, 1), x.time)) }
}

private fun registerVariant() {
PType.codes().forEach { pType ->
register(VARIANT, pType) { x, t -> cast(x.lower(), t) }
}
register(VARIANT, VARIANT) { x, _ -> x }
}

private fun register(source: Int, target: Int, cast: (Datum, PType) -> Datum) {
_table[source][target] = cast
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import org.partiql.errors.TypeCheckException
import org.partiql.eval.Environment
import org.partiql.eval.ExprValue
import org.partiql.eval.Row
import org.partiql.eval.internal.helpers.DatumUtils.lowerSafe
import org.partiql.eval.internal.operator.rex.ExprCallDynamic.Candidate
import org.partiql.eval.internal.operator.rex.ExprCallDynamic.CoercionFamily.DYNAMIC
import org.partiql.eval.internal.operator.rex.ExprCallDynamic.CoercionFamily.UNKNOWN
import org.partiql.spi.function.Function
import org.partiql.spi.value.Datum
import org.partiql.types.PType
import org.partiql.value.PartiQLValue

/**
* Implementation of Dynamic Dispatch.
Expand All @@ -23,7 +23,7 @@ import org.partiql.value.PartiQLValue
* 3. Lookup the candidate to dispatch to and invoke.
*
* This implementation can evaluate ([eval]) the input [Row], execute and gather the
* arguments, and pass the [PartiQLValue]s directly to the [Candidate.eval].
* arguments, and pass the values directly to the [Candidate.eval].
*
* This implementation also caches previously resolved candidates.
*
Expand All @@ -46,7 +46,7 @@ internal class ExprCallDynamic(
private val candidates: MutableMap<List<PType>, Candidate> = mutableMapOf()

override fun eval(env: Environment): Datum {
val actualArgs = args.map { it.eval(env) }.toTypedArray()
val actualArgs = args.map { it.eval(env).lowerSafe() }.toTypedArray()
val actualTypes = actualArgs.map { it.type }
var candidate = candidates[actualTypes]
if (candidate == null) {
Expand Down
Loading

0 comments on commit 1abe677

Please sign in to comment.