diff --git a/CHANGELOG.md b/CHANGELOG.md index 30667aac57..d7a6959e4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Thank you to all who have contributed! - **Breaking** The default integer literal type is now 32-bit; if the literal can not fit in a 32-bit integer, it overflows to 64-bit. - **BREAKING** `PartiQLValueType` now distinguishes between Arbitrary Precision Decimal and Fixed Precision Decimal. - **BREAKING** Function Signature Changes. Now Function signature has two subclasses, `Scalar` and `Aggregation`. +- **BREAKING** Plugin Changes. Only return one Connector.Factory, use Kotlin fields. JVM signature remains the same. - **BREAKING** In the produced plan: - The new plan is fully resolved and typed. - Operators will be converted to function call. diff --git a/partiql-cli/src/main/kotlin/org/partiql/cli/utils/ServiceLoaderUtil.kt b/partiql-cli/src/main/kotlin/org/partiql/cli/utils/ServiceLoaderUtil.kt index cfa7120fd5..0ed38ae35b 100644 --- a/partiql-cli/src/main/kotlin/org/partiql/cli/utils/ServiceLoaderUtil.kt +++ b/partiql-cli/src/main/kotlin/org/partiql/cli/utils/ServiceLoaderUtil.kt @@ -113,12 +113,13 @@ class ServiceLoaderUtil { } else { listOf() } - return plugins.flatMap { plugin -> plugin.getFunctions() } + return plugins.flatMap { plugin -> plugin.functions } + .filterIsInstance() .map { partiqlFunc -> PartiQLtoExprFunction(partiqlFunc) } } @OptIn(PartiQLValueExperimental::class, PartiQLFunctionExperimental::class) - private fun PartiQLtoExprFunction(customFunction: PartiQLFunction): ExprFunction { + private fun PartiQLtoExprFunction(customFunction: PartiQLFunction.Scalar): ExprFunction { val name = customFunction.signature.name val parameters = customFunction.signature.parameters.map { it.type } val returnType = customFunction.signature.returns @@ -130,8 +131,8 @@ class ServiceLoaderUtil { ) override fun callWithRequired(session: EvaluationSession, required: List): ExprValue { - val partiQLArguments = required.mapIndexed { i, expr -> ExprToPartiQLValue(expr, parameters[i]) } - val partiQLResult = customFunction.invoke(session.toConnectorSession(), partiQLArguments) + val partiQLArguments = required.mapIndexed { i, expr -> ExprToPartiQLValue(expr, parameters[i]) }.toTypedArray() + val partiQLResult = customFunction.invoke(partiQLArguments) return PartiQLtoExprValue(partiQLResult) } } diff --git a/partiql-cli/src/test/kotlin/org/partiql/cli/utils/PowTest.kt b/partiql-cli/src/test/kotlin/org/partiql/cli/utils/PowTest.kt deleted file mode 100644 index 0a725a6bc9..0000000000 --- a/partiql-cli/src/test/kotlin/org/partiql/cli/utils/PowTest.kt +++ /dev/null @@ -1,34 +0,0 @@ - -package org.partiql.cli.functions - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.partiql.cli.makeCliAndGetResult -import org.partiql.cli.pipeline.AbstractPipeline -import org.partiql.cli.utils.ServiceLoaderUtil -import java.nio.file.Paths - -/** - * Class `PowTest` is used to test the 'test_power' function, which calculates the base to the power of exponent. - * It is a plugin mockdb functions loaded by Java Service Loader. - * - * @property pipeline Creates a pipeline using service loaded functions. It allows to process a stream of records. - * - * @constructor Creates an instance of `PowTest`. - */ -class PowTest { - - val pluginPath = Paths.get(System.getProperty("testingPluginDirectory")) - - private val pipeline = AbstractPipeline.create( - AbstractPipeline.PipelineOptions( - functions = ServiceLoaderUtil.loadFunctions(pluginPath) - ) - ) - - @Test - fun PowTest() { - val result = makeCliAndGetResult(query = "test_power(2,3)", pipeline = pipeline) - assertEquals(8.0, result.toDouble()) - } -} diff --git a/partiql-cli/src/test/kotlin/org/partiql/cli/utils/ServiceLoaderUtilTest.kt b/partiql-cli/src/test/kotlin/org/partiql/cli/utils/ServiceLoaderUtilTest.kt deleted file mode 100644 index b0248c86a0..0000000000 --- a/partiql-cli/src/test/kotlin/org/partiql/cli/utils/ServiceLoaderUtilTest.kt +++ /dev/null @@ -1,18 +0,0 @@ - -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test -import org.partiql.cli.utils.ServiceLoaderUtil -import org.partiql.lang.eval.ExprFunction -import java.nio.file.Paths - -class ServiceLoaderUtilTest { - @Test - fun `loadPlugins loads the correct plugins`() { - - val pluginPath = Paths.get(System.getProperty("testingPluginDirectory")) - val functions: List = ServiceLoaderUtil.loadFunctions(pluginPath) - - assertTrue(functions.map { it.signature.name }.contains("trim_lead")) - assertTrue(functions.map { it.signature.name }.contains("test_power")) - } -} diff --git a/partiql-cli/src/test/kotlin/org/partiql/cli/utils/TrimLeadTest.kt b/partiql-cli/src/test/kotlin/org/partiql/cli/utils/TrimLeadTest.kt deleted file mode 100644 index 4f62e8cbbd..0000000000 --- a/partiql-cli/src/test/kotlin/org/partiql/cli/utils/TrimLeadTest.kt +++ /dev/null @@ -1,38 +0,0 @@ - -package org.partiql.cli.functions - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.partiql.cli.makeCliAndGetResult -import org.partiql.cli.pipeline.AbstractPipeline -import org.partiql.cli.utils.ServiceLoaderUtil -import java.nio.file.Paths - -/** - * Class `TrimLeadTest` is used to test the 'trim_lead' function, which is used to trim the leading whitespace characters - * from the string it processes. It is a plugin mockdb functions loaded by Java Service Loader. - * - * @property pipeline Creates a pipeline using service loaded functions. It allows to process a stream of records. - * - * @constructor Creates an instance of `TrimLeadTest`. - */ -class TrimLeadTest { - - val pluginPath = Paths.get(System.getProperty("testingPluginDirectory")) - - private val pipeline = AbstractPipeline.create( - AbstractPipeline.PipelineOptions( - functions = ServiceLoaderUtil.loadFunctions(pluginPath) - ) - ) - - @Test - fun TrimTest() { - val input = "' hello'" - val expected = "\"hello\"" - - val result = makeCliAndGetResult(query = "trim_lead($input)", pipeline = pipeline) - - assertEquals(expected, result.trim()) - } -} diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt index c6280900f0..10b56080be 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt @@ -112,7 +112,7 @@ internal class PlanTyper( /** * Rewrite the statement with inferred types and resolved variables */ - public fun resolve(statement: Statement): Statement { + fun resolve(statement: Statement): Statement { if (statement !is Statement.Query) { throw IllegalArgumentException("PartiQLPlanner only supports Query statements") } @@ -182,15 +182,15 @@ internal class PlanTyper( } // compute element type - val t = rex.type as StructType + val t = rex.type val e = if (t.contentClosed) { StaticType.unionOf(t.fields.map { it.value }.toSet()).flatten() } else { - StaticType.ANY + ANY } // compute rel type - val kType = StaticType.STRING + val kType = STRING val vType = e val type = ctx!!.copyWithSchema(listOf(kType, vType)) @@ -419,7 +419,7 @@ internal class PlanTyper( if (resolvedVar == null) { handleUndefinedVariable(path.steps.last()) - return rex(StaticType.ANY, rexOpErr("Undefined variable ${node.identifier}")) + return rex(ANY, rexOpErr("Undefined variable ${node.identifier}")) } val type = resolvedVar.type val op = when (resolvedVar) { @@ -439,7 +439,7 @@ internal class PlanTyper( * Match path as far as possible (rewriting the steps), then infer based on resolved root and rewritten steps. */ override fun visitRexOpPath(node: Rex.Op.Path, ctx: StaticType?): Rex { - val visitedSteps = node.steps.map { visitRexOpPathStep(it, null) as Rex.Op.Path.Step } + val visitedSteps = node.steps.map { visitRexOpPathStep(it, null) } // 1. Resolve path prefix val (root, steps) = when (val rootOp = node.root.op) { is Rex.Op.Var.Unresolved -> { @@ -448,7 +448,7 @@ internal class PlanTyper( val resolvedVar = env.resolve(path, locals, rootOp.scope) if (resolvedVar == null) { handleUndefinedVariable(path.steps.last()) - return rex(StaticType.ANY, node) + return rex(ANY, node) } val type = resolvedVar.type val (op, steps) = when (resolvedVar) { @@ -498,7 +498,7 @@ internal class PlanTyper( } // 4. Invalid path reference; always MISSING - if (type == StaticType.MISSING) { + if (type == MISSING) { handleAlwaysMissing() return rexErr("Unknown identifier $node") } @@ -540,7 +540,7 @@ internal class PlanTyper( is FnMatch.Dynamic -> { val types = mutableSetOf() if (match.isMissable && !isNotMissable) { - types.add(StaticType.MISSING) + types.add(MISSING) } val candidates = match.candidates.map { candidate -> val rex = toRexCall(candidate, args, isNotMissable) @@ -574,7 +574,7 @@ internal class PlanTyper( newArgs.forEach { if (it.type == MissingType && !isNotMissable) { handleAlwaysMissing() - return rex(StaticType.MISSING, rexOpCallStatic(newFn, newArgs)) + return rex(MISSING, rexOpCallStatic(newFn, newArgs)) } } @@ -615,14 +615,14 @@ internal class PlanTyper( // Return type with calculated nullability var type = when { - isNull -> StaticType.NULL + isNull -> NULL isNullable -> returns.toStaticType() else -> returns.toNonNullStaticType() } // Some operators can return MISSING during runtime if (match.isMissable && !isNotMissable) { - type = StaticType.unionOf(type, StaticType.MISSING) + type = StaticType.unionOf(type, MISSING) } // Finally, rewrite this node @@ -740,8 +740,8 @@ internal class PlanTyper( } val ref = call.args.getOrNull(0) ?: error("IS STRUCT requires an argument.") val simplifiedCondition = when { - ref.type.allTypes.all { it is StructType } -> rex(StaticType.BOOL, rexOpLit(boolValue(true))) - ref.type.allTypes.none { it is StructType } -> rex(StaticType.BOOL, rexOpLit(boolValue(false))) + ref.type.allTypes.all { it is StructType } -> rex(BOOL, rexOpLit(boolValue(true))) + ref.type.allTypes.none { it is StructType } -> rex(BOOL, rexOpLit(boolValue(false))) else -> condition } @@ -789,9 +789,9 @@ internal class PlanTyper( when (field.k.op) { is Rex.Op.Lit -> { // A field is only included in the StructType if its key is a text literal - val key = field.k.op as Rex.Op.Lit + val key = field.k.op if (key.value is TextValue<*>) { - val name = (key.value as TextValue<*>).string!! + val name = key.value.string!! val type = field.v.type structKeysSeent.add(name) structTypeFields.add(StructType.Field(name, type)) @@ -919,7 +919,7 @@ internal class PlanTyper( } override fun visitRexOpErr(node: Rex.Op.Err, ctx: StaticType?): PlanNode { - val type = ctx ?: StaticType.ANY + val type = ctx ?: ANY return rex(type, node) } @@ -968,13 +968,13 @@ internal class PlanTyper( PlanningProblemDetails.CompileError("TupleUnion wasn't normalized to exclude union types.") ) ) - possibleOutputTypes.add(StaticType.MISSING) + possibleOutputTypes.add(MISSING) } is NullType -> { - return StaticType.NULL + return NULL } else -> { - return StaticType.MISSING + return MISSING } } } @@ -1057,7 +1057,7 @@ internal class PlanTyper( */ private fun inferPathStep(type: StaticType, step: Rex.Op.Path.Step): Pair = when (type) { - is AnyType -> StaticType.ANY to step + is AnyType -> ANY to step is StructType -> inferPathStep(type, step) is ListType, is SexpType -> inferPathStep(type as CollectionType, step) to step is AnyOfType -> { @@ -1066,7 +1066,7 @@ internal class PlanTyper( else -> { val prevTypes = type.allTypes if (prevTypes.any { it is AnyType }) { - StaticType.ANY to step + ANY to step } else { val results = prevTypes.map { inferPathStep(it, step) } val types = results.map { it.first } @@ -1081,7 +1081,7 @@ internal class PlanTyper( } } } - else -> StaticType.MISSING to step + else -> MISSING to step } /** @@ -1156,13 +1156,13 @@ internal class PlanTyper( isClosed && isOrdered -> { struct.fields.firstOrNull { entry -> binding.isEquivalentTo(entry.key) }?.let { (sensitive(it.key) to it.value) - } ?: (key to StaticType.MISSING) + } ?: (key to MISSING) } // 2. Struct is closed isClosed -> { val matches = struct.fields.filter { entry -> binding.isEquivalentTo(entry.key) } when (matches.size) { - 0 -> (key to StaticType.MISSING) + 0 -> (key to MISSING) 1 -> matches.first().let { (sensitive(it.key) to it.value) } else -> { val firstKey = matches.first().key @@ -1175,7 +1175,7 @@ internal class PlanTyper( } } // 3. Struct is open - else -> (key to StaticType.ANY) + else -> (key to ANY) } return type to name } @@ -1197,7 +1197,7 @@ internal class PlanTyper( * Let TX be the single-column table that is the result of applying the * to each row of T and eliminating null values <--- all NULL values are eliminated as inputs */ - public fun resolveAgg(agg: Agg.Unresolved, arguments: List): Pair { + fun resolveAgg(agg: Agg.Unresolved, arguments: List): Pair { var missingArg = false val args = arguments.map { val arg = visitRex(it, null) @@ -1227,7 +1227,7 @@ internal class PlanTyper( // Some operators can return MISSING during runtime if (match.isMissable) { - type = StaticType.unionOf(type, StaticType.MISSING).flatten() + type = StaticType.unionOf(type, MISSING).flatten() } // Finally, rewrite this node @@ -1248,7 +1248,7 @@ internal class PlanTyper( private fun Rex.type(typeEnv: TypeEnv) = RexTyper(typeEnv).visitRex(this, this.type) - private fun rexErr(message: String) = rex(StaticType.MISSING, rexOpErr(message)) + private fun rexErr(message: String) = rex(MISSING, rexOpErr(message)) /** * I found decorating the tree with the binding names (for resolution) was easier than associating introduced @@ -1315,7 +1315,7 @@ internal class PlanTyper( private fun getElementTypeForFromSource(fromSourceType: StaticType): StaticType = when (fromSourceType) { is BagType -> fromSourceType.elementType is ListType -> fromSourceType.elementType - is AnyType -> StaticType.ANY + is AnyType -> ANY is AnyOfType -> AnyOfType(fromSourceType.types.map { getElementTypeForFromSource(it) }.toSet()) // All the other types coerce into a bag of themselves (including null/missing/sexp). else -> fromSourceType @@ -1437,7 +1437,7 @@ internal class PlanTyper( private fun Fn.Unresolved.isNotMissable(): Boolean { return when (identifier) { is Identifier.Qualified -> false - is Identifier.Symbol -> when ((identifier as Identifier.Symbol).symbol) { + is Identifier.Symbol -> when (identifier.symbol) { "and" -> true "or" -> true "not" -> true @@ -1450,7 +1450,7 @@ internal class PlanTyper( } private fun Fn.Unresolved.isTypeAssertion(): Boolean { - return (identifier is Identifier.Symbol && (identifier as Identifier.Symbol).symbol.startsWith("is")) + return (identifier is Identifier.Symbol && identifier.symbol.startsWith("is")) } /** diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/Plugin.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/Plugin.kt index 007bb8a66a..4df4c5dda9 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/Plugin.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/Plugin.kt @@ -22,11 +22,15 @@ import org.partiql.spi.function.PartiQLFunctionExperimental * A singular unit of external logic. */ public interface Plugin { - public fun getConnectorFactories(): List /** - * Represents custom built-in functions to be accessed during execution. - **/ - @OptIn(PartiQLFunctionExperimental::class) - public fun getFunctions(): List + * A [Connector.Factory] is used to instantiate a connector. + */ + public val factory: Connector.Factory + + /** + * Functions defined by this plugin. + */ + @PartiQLFunctionExperimental + public val functions: List } diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/Connector.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/Connector.kt index 5e8b975978..01ac354974 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/connector/Connector.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/connector/Connector.kt @@ -20,10 +20,33 @@ import com.amazon.ionelement.api.StructElement * A mechanism by which PartiQL can access a Catalog. */ public interface Connector { + + /** + * Returns a [ConnectorMetadata] for the given [ConnectorSession]. The [ConnectorMetadata] is responsible + * for accessing catalog metadata. + * + * @param session + * @return + */ public fun getMetadata(session: ConnectorSession): ConnectorMetadata + /** + * A Plugin leverages a [Factory] to produce a [Connector] which is used for catalog metadata and data access. + */ public interface Factory { - public fun getName(): String + + /** + * The connector name used to register the factory. + */ + public val name: String + + /** + * The connector factory method. + * + * @param catalogName + * @param config + * @return + */ public fun create(catalogName: String, config: StructElement? = null): Connector } } diff --git a/partiql-spi/src/main/kotlin/org/partiql/spi/function/PartiQLFunction.kt b/partiql-spi/src/main/kotlin/org/partiql/spi/function/PartiQLFunction.kt index db51b58576..b95f460613 100644 --- a/partiql-spi/src/main/kotlin/org/partiql/spi/function/PartiQLFunction.kt +++ b/partiql-spi/src/main/kotlin/org/partiql/spi/function/PartiQLFunction.kt @@ -1,20 +1,76 @@ package org.partiql.spi.function -import org.partiql.spi.connector.ConnectorSession import org.partiql.types.function.FunctionSignature import org.partiql.value.PartiQLValue import org.partiql.value.PartiQLValueExperimental /** - * Represents a function interface that can be overridden by external teams. - * - * An implementation of this interface defines the behavior of the function - * and its signature, which includes the function's names, return type, parameters, - * determinism, and an optional description. + * The [PartiQLFunction] interface is used to implement user-defined-functions (UDFs). + * UDFs can be registered to a plugin for use in the query planner and evaluator. */ @PartiQLFunctionExperimental -public interface PartiQLFunction { +public sealed interface PartiQLFunction { + + /** + * Defines the function's parameters and argument handling. + */ public val signature: FunctionSignature - @OptIn(PartiQLValueExperimental::class) - public fun invoke(session: ConnectorSession, arguments: List): PartiQLValue + + /** + * Represents an SQL row-value expression call. + */ + public interface Scalar : PartiQLFunction { + + /** + * Scalar function signature. + */ + override val signature: FunctionSignature.Scalar + + /** + * Invoke the routine with the given arguments. + * + * @param args + * @return + */ + @OptIn(PartiQLValueExperimental::class) + public fun invoke(args: Array): PartiQLValue + } + + /** + * Represents an SQL table-value expression call. + */ + public interface Aggregation : PartiQLFunction { + + /** + * Aggregation function signature. + */ + override val signature: FunctionSignature.Aggregation + + /** + * Instantiates an accumulator for this aggregation function. + * + * @return + */ + public fun accumulator(): Accumulator + } + + public interface Accumulator { + + /** + * Apply args to the accumulator. + * + * @param args + * @return + */ + @OptIn(PartiQLValueExperimental::class) + public fun next(args: Array): PartiQLValue + + /** + * Return the accumulator value. + * + * @return + */ + @OptIn(PartiQLValueExperimental::class) + public fun value(): PartiQLValue + } } diff --git a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalConnector.kt b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalConnector.kt index 0a7c8e1b17..5ec5808ec1 100644 --- a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalConnector.kt +++ b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalConnector.kt @@ -64,7 +64,7 @@ class LocalConnector( private val default: Path = Paths.get(System.getProperty("user.home")).resolve(".partiql/local") - override fun getName(): String = CONNECTOR_NAME + override val name: String = CONNECTOR_NAME override fun create(catalogName: String, config: StructElement?): Connector { assert(config != null) { "Local plugin requires non-null config" } diff --git a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalPlugin.kt b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalPlugin.kt index f180c90f56..94ad833a39 100644 --- a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalPlugin.kt +++ b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/LocalPlugin.kt @@ -14,24 +14,20 @@ package org.partiql.plugins.local -import org.partiql.plugins.local.functions.Pow -import org.partiql.plugins.local.functions.TrimLead import org.partiql.spi.Plugin import org.partiql.spi.connector.Connector import org.partiql.spi.function.PartiQLFunction import org.partiql.spi.function.PartiQLFunctionExperimental /** - * FsPlugin is a PartiQL plugin that provides schemas written in PartiQL Value Schema. + * LocalPlugin is a PartiQL plugin that provides schemas written in PartiQL Value Schema. * * Backed by a memoized catalog tree from the given root dir; global bindings are files. */ class LocalPlugin : Plugin { - override fun getConnectorFactories(): List = listOf(LocalConnector.Factory()) + override val factory: Connector.Factory = LocalConnector.Factory() - @PartiQLFunctionExperimental - override fun getFunctions(): List = listOf( - TrimLead, Pow - ) + @OptIn(PartiQLFunctionExperimental::class) + override val functions: List = listOf() } diff --git a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/functions/Pow.kt b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/functions/Pow.kt deleted file mode 100644 index d27b60c27d..0000000000 --- a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/functions/Pow.kt +++ /dev/null @@ -1,36 +0,0 @@ -package org.partiql.plugins.local.functions - -import org.partiql.spi.connector.ConnectorSession -import org.partiql.spi.function.PartiQLFunction -import org.partiql.spi.function.PartiQLFunctionExperimental -import org.partiql.types.function.FunctionParameter -import org.partiql.types.function.FunctionSignature -import org.partiql.value.Int8Value -import org.partiql.value.PartiQLValue -import org.partiql.value.PartiQLValueExperimental -import org.partiql.value.PartiQLValueType -import org.partiql.value.float64Value - -@OptIn(PartiQLFunctionExperimental::class) -object Pow : PartiQLFunction { - - @OptIn(PartiQLValueExperimental::class) - override val signature = FunctionSignature.Scalar( - name = "test_power", - returns = PartiQLValueType.FLOAT64, - parameters = listOf( - FunctionParameter(name = "base", type = PartiQLValueType.INT8), - FunctionParameter(name = "exponent", type = PartiQLValueType.INT8) - ), - description = "Power [base] with [exponent]", - isDeterministic = true - ) - - @OptIn(PartiQLValueExperimental::class) - override operator fun invoke(session: ConnectorSession, arguments: List): PartiQLValue { - val base = (arguments[0] as? Int8Value)?.int ?: 0 - val exponent = (arguments[1] as? Int8Value)?.int ?: 0 - val processed = Math.pow(base.toDouble(), exponent.toDouble()) - return float64Value(processed) - } -} diff --git a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/functions/TrimLead.kt b/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/functions/TrimLead.kt deleted file mode 100644 index 9037ca6c02..0000000000 --- a/plugins/partiql-local/src/main/kotlin/org/partiql/plugins/local/functions/TrimLead.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.partiql.plugins.local.functions - -import org.partiql.spi.connector.ConnectorSession -import org.partiql.spi.function.PartiQLFunction -import org.partiql.spi.function.PartiQLFunctionExperimental -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.PartiQLValueType -import org.partiql.value.StringValue -import org.partiql.value.stringValue - -@OptIn(PartiQLFunctionExperimental::class) -object TrimLead : PartiQLFunction { - - @OptIn(PartiQLValueExperimental::class) - override val signature = FunctionSignature.Scalar( - name = "trim_lead", - returns = PartiQLValueType.STRING, - parameters = listOf( - FunctionParameter(name = "str", type = PartiQLValueType.STRING) - ), - description = "Trims leading whitespace of a [str].", - isDeterministic = true - ) - - @OptIn(PartiQLValueExperimental::class) - override operator fun invoke(session: ConnectorSession, arguments: List): PartiQLValue { - val str = (arguments[0] as? StringValue)?.string ?: "" - val processed = str.trimStart() - return stringValue(processed) - } -} diff --git a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryConnector.kt b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryConnector.kt index 105fb4c7ba..830e1b0d02 100644 --- a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryConnector.kt +++ b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryConnector.kt @@ -23,7 +23,7 @@ public class MemoryConnector(private val metadata: ConnectorMetadata) : Connecto class Factory(private val catalogs: Map) : Connector.Factory { - override fun getName(): String = CONNECTOR_NAME + override val name: String = CONNECTOR_NAME override fun create(catalogName: String, config: StructElement?): Connector { return catalogs[catalogName] ?: error("Catalog $catalogName is not registered in the MemoryPlugin") diff --git a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryPlugin.kt b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryPlugin.kt index 322dadd142..3b269ff21b 100644 --- a/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryPlugin.kt +++ b/plugins/partiql-memory/src/main/kotlin/org/partiql/plugins/memory/MemoryPlugin.kt @@ -7,8 +7,8 @@ import org.partiql.spi.function.PartiQLFunctionExperimental class MemoryPlugin(private val catalogs: Map) : Plugin { - override fun getConnectorFactories(): List = listOf(MemoryConnector.Factory(catalogs)) + override val factory: Connector.Factory = MemoryConnector.Factory(catalogs) - @PartiQLFunctionExperimental - override fun getFunctions(): List = emptyList() + @OptIn(PartiQLFunctionExperimental::class) + override val functions: List = emptyList() }