Skip to content

Partiql eval continue #1370

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,8 @@ internal class Compiler(
val args = node.args.map { visitRex(it, ctx).modeHandled() }.toTypedArray()
val candidates = node.candidates.map { candidate ->
val fn = symbols.getFn(candidate.fn)
val types = candidate.parameters.toTypedArray()
val coercions = candidate.coercions.toTypedArray()
ExprCallDynamic.Candidate(fn, types, coercions)
ExprCallDynamic.Candidate(fn, coercions)
}
return ExprCallDynamic(candidates, args)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ internal class ExprCallDynamic(
*/
internal class Candidate(
val fn: Fn,
val types: Array<PartiQLValueType>,
val coercions: Array<Ref.Cast?>
) {

private val signatureParameters = fn.signature.parameters.map { it.type }.toTypedArray()

fun eval(originalArgs: Array<PartiQLValue>, env: Environment): PartiQLValue {
val args = originalArgs.mapIndexed { i, arg ->
when (val c = coercions[i]) {
Expand All @@ -63,13 +64,28 @@ internal class ExprCallDynamic(
return fn.invoke(args)
}

internal fun matches(args: Array<PartiQLValue>): Boolean {
for (i in args.indices) {
if (types[i] == PartiQLValueType.ANY) {
return true
}
if (args[i].type != types[i]) {
return false
internal fun matches(inputs: Array<PartiQLValue>): Boolean {
for (i in inputs.indices) {
val inputType = inputs[i].type
val parameterType = signatureParameters[i]
val c = coercions[i]
when (c) {
// coercion might be null if one of the following is true
// Function parameter is ANY,
// Input type is null
// input type is the same as function parameter
null -> {
if (!(inputType == parameterType || inputType == PartiQLValueType.NULL || parameterType == PartiQLValueType.ANY)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this earlier today, but IMO the input, if null, should be coerced to a typed null ahead of time. We don't need the check for inputType == PartiQLValueType.NULL.

We could address the NULL aspect later, though.

return false
}
}
else -> {
// checking the input type is expected by the coercion
if (inputType != c.input) return false
// checking the result is expected by the function signature
// this should branch should never be reached, but leave it here for clarity
if (c.target != parameterType) error("Internal Error: Cast Target does not match Function Parameter")
}
}
}
return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import org.partiql.planner.internal.ir.Ref
import org.partiql.planner.internal.typer.toRuntimeTypeOrNull
import org.partiql.spi.fn.FnExperimental
import org.partiql.spi.fn.FnSignature
import org.partiql.types.AnyOfType
import org.partiql.types.NullType
import org.partiql.types.StaticType
import org.partiql.value.PartiQLValueExperimental
import org.partiql.value.PartiQLValueType
Expand Down Expand Up @@ -147,7 +149,13 @@ internal object FnResolver {
}

private fun buildArgumentPermutations(args: List<StaticType>): List<List<StaticType>> {
val flattenedArgs = args.map { it.flatten().allTypes }
val flattenedArgs = args.map {
if (it is AnyOfType) {
it.flatten().allTypes.filter { it !is NullType }
} else {
it.flatten().allTypes
}
}
Comment on lines +152 to +158
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when the StaticType is AnyType? Would the following suffice?

Suggested change
val flattenedArgs = args.map {
if (it is AnyOfType) {
it.flatten().allTypes.filter { it !is NullType }
} else {
it.flatten().allTypes
}
}
val flattenedArgs = args.map { it.flatten().allTypes }.filter { it !is NullType }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intuitively, if the input args is just a StaticType.NULL, then this will return an empty list, which might cause some trouble.

I will look into this.

return buildArgumentPermutations(flattenedArgs, accumulator = emptyList())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ internal object RexConverter {
// Call Variants
val call = when (arg2) {
null -> call("substring", arg0, arg1)
else -> call("substring_length", arg0, arg1, arg2)
else -> call("substring", arg0, arg1, arg2)
}
return rex(type, call)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,10 @@ private fun StaticType.asRuntimeType(): PartiQLValueType = when (this) {
is ListType -> PartiQLValueType.LIST
is SexpType -> PartiQLValueType.SEXP
is DateType -> PartiQLValueType.DATE
is DecimalType -> PartiQLValueType.DECIMAL_ARBITRARY
is DecimalType -> when (this.precisionScaleConstraint) {
is DecimalType.PrecisionScaleConstraint.Constrained -> PartiQLValueType.DECIMAL
DecimalType.PrecisionScaleConstraint.Unconstrained -> PartiQLValueType.DECIMAL_ARBITRARY
}
is FloatType -> PartiQLValueType.FLOAT64
is GraphType -> error("Graph type missing from runtime types")
is IntType -> when (this.rangeConstraint) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ internal class PartiQLValueComparatorInternal(private val nullsFirst: Boolean) :
}

when {
// TODO: This is EQG-specific behavior. Do we want to leave it as-is in the Value comparator?
l.isNullOrMissing() && r.isNullOrMissing() -> return EQUAL
l.isNullOrMissing() -> return when (nullsFirst) {
true -> LESS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ internal fun bigDecimalOf(num: Number, mc: MathContext = MATH_CONTEXT): BigDecim
is Double -> BigDecimal(num, mc)
is BigInteger -> BigDecimal(num, mc)
is BigDecimal -> num
is BigInteger -> num.toBigDecimal()
else -> throw IllegalArgumentException("Unsupported number type: $num, ${num.javaClass}")
}

Expand All @@ -58,20 +59,23 @@ private val CONVERSION_MAP = mapOf<Set<Class<*>>, Class<out Number>>(
setOf(Float::class.javaObjectType, Float::class.javaObjectType) to Float::class.javaObjectType,
// Float w/ Long -> Double
setOf(Float::class.javaObjectType, Long::class.javaObjectType) to Double::class.javaObjectType,
setOf(Float::class.javaObjectType, BigInteger::class.javaObjectType) to Double::class.javaObjectType,
setOf(Float::class.javaObjectType, Double::class.javaObjectType) to Double::class.javaObjectType,
setOf(Float::class.javaObjectType, BigDecimal::class.javaObjectType) to BigDecimal::class.javaObjectType,

setOf(Long::class.javaObjectType, Long::class.javaObjectType) to Long::class.javaObjectType,
setOf(Long::class.javaObjectType, BigInteger::class.javaObjectType) to BigInteger::class.javaObjectType,
setOf(Long::class.javaObjectType, Double::class.javaObjectType) to Double::class.javaObjectType,
setOf(Long::class.javaObjectType, BigDecimal::class.javaObjectType) to BigDecimal::class.javaObjectType,

setOf(BigInteger::class.javaObjectType, BigInteger::class.javaObjectType) to BigInteger::class.javaObjectType,
setOf(BigInteger::class.javaObjectType, Double::class.javaObjectType) to Double::class.javaObjectType,
setOf(BigInteger::class.javaObjectType, BigDecimal::class.javaObjectType) to BigDecimal::class.javaObjectType,

setOf(Double::class.javaObjectType, Double::class.javaObjectType) to Double::class.javaObjectType,
setOf(Double::class.javaObjectType, BigDecimal::class.javaObjectType) to BigDecimal::class.javaObjectType,

setOf(BigDecimal::class.javaObjectType, BigDecimal::class.javaObjectType) to BigDecimal::class.javaObjectType
setOf(BigDecimal::class.javaObjectType, BigDecimal::class.javaObjectType) to BigDecimal::class.javaObjectType,
)

private val CONVERTERS = mapOf<Class<*>, (Number) -> Number>(
Expand All @@ -81,8 +85,12 @@ private val CONVERTERS = mapOf<Class<*>, (Number) -> Number>(
Double::class.javaObjectType to Number::toDouble,
BigInteger::class.javaObjectType to { num ->
when (num) {
is Int -> num.toBigInteger()
is Long -> num.toBigInteger()
is BigInteger -> num
else -> BigInteger.valueOf(num.toLong())
else -> throw IllegalArgumentException(
"Unsupported number for BigInteger conversion: $num"
)
}
},
BigDecimal::class.java to { num ->
Expand All @@ -101,14 +109,13 @@ private val CONVERTERS = mapOf<Class<*>, (Number) -> Number>(
)

internal fun Number.isZero() = when (this) {
// using compareTo instead of equals for BigDecimal because equality also checks same scale
is Int -> this == 0
is Long -> this == 0L
is Float -> this == 0.0f || this == -0.0f
is Double -> this == 0.0 || this == -0.0
is BigDecimal -> BigDecimal.ZERO.compareTo(this) == 0
is BigInteger -> BigInteger.ZERO.compareTo(this) == 0
else -> throw IllegalStateException("$this (${this.javaClass.simpleName})")
is BigDecimal -> this.signum() == 0
is BigInteger -> this.signum() == 0
else -> throw IllegalStateException("$this")
}

@Suppress("UNCHECKED_CAST")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ abstract class ConformanceTestBase<T, V> {
}

// Tests the eval equivalence tests with the Kotlin implementation
@Timeout(value = 5, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
@Timeout(value = 500, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
@ParameterizedTest(name = "{arguments}")
@ArgumentsSource(TestProvider.Equiv::class)
fun validatePartiQLEvalEquivTestData(tc: TestCase) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,12 @@ class EvalExecutor(
if (v1.isNull && v2.isNull) {
return true
}
if (v1 == v2) {
// TODO: this comparator is designed for order by
// One of the false result it might produce is that
// it treats MISSING and NULL equally.
// we should move to hash or equals in value class once
// we finished implementing those.
if (comparator.compare(v1, v2) == 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NULL/MISSING according to your other comment would cause problems here, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

We should not use the comparator here, we should have the equals or hash code method implemented in the value class.

But the comparator is at the moment the only comparator, specifically when it comes to bag comparison.

I think we can leave this line here but add a TODO.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific scenario where this was failing? I can see that all of the value impls are data classes with the exception of collections and structs.

return true
}
if (v1.toIon().hashCode() == v2.toIon().hashCode()) {
Expand All @@ -101,6 +106,7 @@ class EvalExecutor(
val parser = PartiQLParser.default()
val planner = PartiQLPlanner.default()
val engine = PartiQLEngine.default()
val comparator = PartiQLValue.comparator()
}

object Factory : TestExecutor.Factory<PartiQLStatement<*>, PartiQLResult> {
Expand Down