Skip to content
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

Adds a built-in PartiQL System catalog, support for SQL-Path, and SPI cleanup #1670

Merged
merged 13 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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(project(":partiql-system"))
implementation(Deps.csv)
implementation(Deps.awsSdkBom)
implementation(Deps.awsSdkDynamodb)
Expand Down
4 changes: 2 additions & 2 deletions partiql-cli/src/main/kotlin/org/partiql/cli/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import org.partiql.cli.pipeline.Pipeline
import org.partiql.cli.shell.Shell
import org.partiql.spi.catalog.Catalog
import org.partiql.spi.catalog.Name
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.system.PartiQLSessionBuilder
import org.partiql.value.PartiQLValueExperimental
import org.partiql.value.io.PartiQLValueTextWriter
import picocli.CommandLine
Expand Down Expand Up @@ -210,7 +210,7 @@ internal class MainCommand : Runnable {
println()
}

private fun session() = Session.builder()
private fun session() = PartiQLSessionBuilder()
.identity(System.getProperty("user.name"))
.namespace(emptyList())
.catalog("default")
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 @@ -33,6 +33,7 @@ dependencies {
annotationProcessor(Deps.lombok)
// Test
testImplementation(project(":partiql-parser"))
testImplementation(project(":partiql-system"))
testImplementation(testFixtures(project(":partiql-types"))) // TODO: Remove use of StaticType
testImplementation(Deps.junit4)
testImplementation(Deps.junit4Params)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import org.partiql.eval.Statement
import org.partiql.parser.PartiQLParser
import org.partiql.plan.rel.RelLimit
import org.partiql.planner.PartiQLPlanner
import org.partiql.spi.catalog.Session
import org.partiql.system.PartiQLSession
import kotlin.test.Test
import kotlin.test.assertTrue

Expand All @@ -20,7 +20,7 @@ public class StrategyTest {

private val parser = PartiQLParser.standard()
private val planner = PartiQLPlanner.standard()
private val session = Session.empty()
private val session = PartiQLSession.empty()

private class MyLimit : ExprRelation {
override fun open(env: Environment) = error("open")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import org.partiql.plan.Plan
import org.partiql.planner.PartiQLPlanner
import org.partiql.spi.catalog.Catalog
import org.partiql.spi.catalog.Name
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.system.PartiQLSessionBuilder
import org.partiql.types.StaticType
import org.partiql.types.fromStaticType
import org.partiql.value.PartiQLValue
Expand Down Expand Up @@ -64,7 +64,7 @@ public class SuccessTestCase(
}
}
.build()
val session = Session.builder()
val session = PartiQLSessionBuilder()
.catalog("memory")
.catalogs(catalog)
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import org.partiql.parser.PartiQLParser
import org.partiql.plan.Plan
import org.partiql.planner.PartiQLPlanner
import org.partiql.spi.catalog.Catalog
import org.partiql.spi.catalog.Session
import org.partiql.spi.value.Datum
import org.partiql.system.PartiQLSessionBuilder
import org.partiql.types.PType
import org.partiql.value.PartiQLValue
import org.partiql.value.PartiQLValueExperimental
Expand Down Expand Up @@ -62,7 +62,7 @@ public class TypingTestCase @OptIn(PartiQLValueExperimental::class) constructor(
assertEquals(1, parseResult.statements.size)
val statement = parseResult.statements[0]
val catalog = Catalog.builder().name("memory").build()
val session = Session.builder()
val session = PartiQLSessionBuilder()
.catalog("memory")
.catalogs(catalog)
.build()
Expand Down
1 change: 1 addition & 0 deletions partiql-planner/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ dependencies {
annotationProcessor(Deps.lombok)
// Test
testImplementation(project(":partiql-parser"))
testImplementation(project(":partiql-system"))
testImplementation(testFixtures(project(":partiql-types"))) // TODO: Remove use of StaticType
testImplementation(Deps.kotlinReflect)
// Test Fixtures
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,25 @@ internal class Env(private val session: Session) {
error("Qualified functions are not supported.")
}

// 1. Search in the current catalog and namespace.
val catalog = default
// Search in SQL Path
session.getPath().forEach { catalogName ->
// TODO: Allow for referencing schemas within a catalog. For now, only allow top-level functions in a catalog.
if (catalogName.getLength() != 1) {
return@forEach
}
val catalog = catalogs.getCatalog(catalogName.first()) ?: return@forEach
val candidates = getCandidates(catalog, identifier, args)
if (candidates.isNotEmpty()) {
return candidates
}
}
return emptyList()
}

/**
* Given a [catalog], searches for candidate functions.
*/
private fun getCandidates(catalog: Catalog, identifier: Identifier, args: List<Rex>): List<Function> {
val name = identifier.getIdentifier().getText().lowercase() // CASE-NORMALIZED LOWER
val variants = catalog.getFunctions(session, name).toList()
val candidates = variants.filter { it.getParameters().size == args.size }
Expand All @@ -118,14 +135,35 @@ internal class Env(private val session: Session) {
* @return
*/
fun resolveFn(identifier: Identifier, args: List<Rex>): Rex? {

// Reject qualified routine names.
if (identifier.hasQualifier()) {
error("Qualified functions are not supported.")
}

// Search in SQL Path
session.getPath().forEach { catalogName ->
// TODO: Allow for referencing schemas within a catalog. For now, only allow top-level functions in a catalog.
if (catalogName.getLength() != 1) {
return@forEach
}
val catalog = catalogs.getCatalog(catalogName.first()) ?: return@forEach
val candidates = resolveFn(catalog, identifier, args)
if (candidates != null) {
return candidates
}
}
return null
}

/**
* TODO leverage session PATH.
*
* @param identifier
* @param args
* @return
*/
private fun resolveFn(catalog: Catalog, identifier: Identifier, args: List<Rex>): Rex? {
// 1. Search in the current catalog and namespace.
val catalog = default
val name = identifier.getIdentifier().getText().lowercase() // CASE-NORMALIZED LOWER
val variants = catalog.getFunctions(session, name).toList()
if (variants.isEmpty()) {
Expand Down Expand Up @@ -170,10 +208,25 @@ internal class Env(private val session: Session) {
}

fun resolveAgg(path: String, setQuantifier: SetQuantifier, args: List<Rex>): Rel.Op.Aggregate.Call.Resolved? {
// Search in SQL Path
session.getPath().forEach { catalogName ->
// TODO: Allow for referencing schemas within a catalog. For now, only allow top-level functions in a catalog.
if (catalogName.getLength() != 1) {
return@forEach
}
val catalog = catalogs.getCatalog(catalogName.first()) ?: return@forEach
val agg = resolveAgg(catalog, path, setQuantifier, args)
if (agg != null) {
return agg
}
}
return null
}

private fun resolveAgg(catalog: Catalog, path: String, setQuantifier: SetQuantifier, args: List<Rex>): Rel.Op.Aggregate.Call.Resolved? {
// TODO: Eventually, do we want to support sensitive lookup? With a path?

// 1. Search in the current catalog and namespace.
val catalog = default
val name = path.lowercase()
val candidates = catalog.getAggregations(session, name).toList()
if (candidates.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import org.partiql.planner.internal.TestCatalog
import org.partiql.planner.test.PartiQLTest
import org.partiql.planner.test.PartiQLTestProvider
import org.partiql.spi.catalog.Name
import org.partiql.spi.catalog.Session
import org.partiql.system.PartiQLSessionBuilder
import org.partiql.types.BagType
import org.partiql.types.StaticType
import org.partiql.types.StructType
Expand Down Expand Up @@ -66,7 +66,7 @@ class PlanTest {
)

private val pipeline: (PartiQLTest, Boolean) -> PartiQLPlanner.Result = { test, isSignalMode ->
val session = Session.builder()
val session = PartiQLSessionBuilder()
.catalog("default")
.catalogs(
TestCatalog.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import org.partiql.planner.util.PErrorCollector
import org.partiql.planner.util.PlanPrinter
import org.partiql.spi.Context
import org.partiql.spi.catalog.Catalog
import org.partiql.spi.catalog.Session
import org.partiql.spi.catalog.Table
import org.partiql.system.PartiQLSessionBuilder
import org.partiql.types.BagType
import org.partiql.types.Field
import org.partiql.types.PType
Expand All @@ -38,7 +38,7 @@ internal class PlannerPErrorReportingTests {
.define(Table.empty("struct_with_missing", PType.row(listOf(Field.of("f1", PType.smallint())))))
.build()

private val session = Session.builder()
private val session = PartiQLSessionBuilder()
.catalog(catalogName)
.catalogs(catalog)
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import org.partiql.plan.rex.RexSelect
import org.partiql.plan.rex.RexVar
import org.partiql.planner.PartiQLPlanner
import org.partiql.spi.catalog.Catalog
import org.partiql.spi.catalog.Session
import org.partiql.system.PartiQLSessionBuilder
import java.util.stream.Stream
import kotlin.test.assertEquals

Expand All @@ -41,7 +41,7 @@ class SubsumptionTest {
val parseResult = parser.parse(text)
assertEquals(1, parseResult.statements.size)
val statement = parseResult.statements[0]
val session = Session.builder().catalog("default").catalogs(catalog).build()
val session = PartiQLSessionBuilder().catalog("default").catalogs(catalog).build()
val plan = planner.plan(statement, session).plan
val excludeClause = getExcludeClause(plan.getOperation()).getExclusions()
assertEquals(tc.expectedExcludeExprs, excludeClause)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.partiql.spi.catalog.Session
import org.partiql.spi.catalog.Table
import org.partiql.spi.errors.PError
import org.partiql.spi.errors.PErrorListener
import org.partiql.system.PartiQLSessionBuilder
import org.partiql.types.PType
import org.partiql.types.StaticType
import org.partiql.types.fromStaticType
Expand All @@ -42,7 +43,7 @@ abstract class PartiQLTyperTestBase {
public val planner = PartiQLPlanner.standard()

internal val session: ((String, Catalog) -> Session) = { catalog, metadata ->
Session.builder()
PartiQLSessionBuilder()
.catalog(catalog)
.catalogs(metadata)
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import org.partiql.planner.util.PErrorCollector
import org.partiql.spi.Context
import org.partiql.spi.catalog.Identifier
import org.partiql.spi.catalog.Name
import org.partiql.spi.catalog.Session
import org.partiql.spi.catalog.Table
import org.partiql.system.PartiQLSessionBuilder
import org.partiql.types.PType
import org.partiql.value.PartiQLValueExperimental
import org.partiql.value.int32Value
Expand Down Expand Up @@ -120,7 +120,7 @@ class PlanTyperTest {
private fun getTyper(): PlanTyperWrapper {
val config = Context.of(PErrorCollector())
val env = Env(
Session.builder()
PartiQLSessionBuilder()
.catalog("pql")
.namespace("main")
.catalogs(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import org.partiql.spi.catalog.Name
import org.partiql.spi.catalog.Session
import org.partiql.spi.errors.PError
import org.partiql.spi.errors.PErrorListener
import org.partiql.system.PartiQLSessionBuilder
import org.partiql.types.BagType
import org.partiql.types.DecimalType
import org.partiql.types.Field
Expand Down Expand Up @@ -3824,7 +3825,7 @@ internal class PlanTyperTestsPorted {
}

private fun runTest(tc: SuccessTestCase) {
val session = Session.builder()
val session = PartiQLSessionBuilder()
.catalog(tc.catalog)
.catalogs(*catalogs.toTypedArray())
.namespace(tc.catalogPath)
Expand Down Expand Up @@ -3867,7 +3868,7 @@ internal class PlanTyperTestsPorted {
}

private fun runTest(tc: ErrorTestCase) {
val session = Session.builder()
val session = PartiQLSessionBuilder()
.catalog(tc.catalog)
.catalogs(*catalogs.toTypedArray())
.namespace(tc.catalogPath)
Expand Down Expand Up @@ -3911,7 +3912,7 @@ internal class PlanTyperTestsPorted {
}

private fun runTest(tc: ThrowingExceptionTestCase) {
val session = Session.builder()
val session = PartiQLSessionBuilder()
.catalog(tc.catalog)
.catalogs(*catalogs.toTypedArray())
.namespace(tc.catalogPath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import org.partiql.planner.internal.ir.relBinding
import org.partiql.planner.internal.typer.PlanTyper.Companion.toCType
import org.partiql.spi.catalog.Catalog
import org.partiql.spi.catalog.Identifier
import org.partiql.spi.catalog.Session
import org.partiql.system.PartiQLSessionBuilder
import org.partiql.types.PType
import kotlin.test.assertEquals
import kotlin.test.fail
Expand All @@ -36,7 +36,7 @@ internal class ScopeTest {
@JvmStatic
val locals = TypeEnv(
Env(
Session.builder()
PartiQLSessionBuilder()
.catalog("currentCatalog")
.catalogs(catalog)
.build()
Expand Down
1 change: 1 addition & 0 deletions partiql-spi/api/partiql-spi.api
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ public final class org/partiql/spi/catalog/Namespace : java/lang/Iterable, kotli
public synthetic fun <init> ([Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun append ([Ljava/lang/String;)Lorg/partiql/spi/catalog/Namespace;
public final fun asIdentifier ()Lorg/partiql/spi/catalog/Identifier;
public static final fun empty ()Lorg/partiql/spi/catalog/Namespace;
public fun equals (Ljava/lang/Object;)Z
public fun forEach (Ljava/util/function/Consumer;)V
public final fun get (I)Ljava/lang/String;
Expand Down
17 changes: 12 additions & 5 deletions partiql-spi/src/main/kotlin/org/partiql/spi/catalog/Catalog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package org.partiql.spi.catalog

import org.partiql.spi.catalog.impl.StandardCatalog
import org.partiql.spi.function.Aggregation
import org.partiql.spi.function.Builtins
import org.partiql.spi.function.Function

/**
Expand All @@ -22,7 +21,9 @@ public interface Catalog {
/**
* Get a table by name.
*/
public fun getTable(session: Session, name: Name): Table? = null
public fun getTable(session: Session, name: Name): Table? {
return null
}

/**
* Get a table by identifier.
Expand All @@ -41,17 +42,23 @@ public interface Catalog {
* 2. Invoke getTable("a"."b"."c"."Example"."x")
* 3. The implementation MUST match "a"."b"."c"."Example" to a.b.c.Example (note "x" does not match a table)
*/
public fun getTable(session: Session, identifier: Identifier): Table? = null
public fun getTable(session: Session, identifier: Identifier): Table? {
return null
}

/**
* Returns a collection of scalar functions in this catalog with the given name, or an empty list if none.
*/
public fun getFunctions(session: Session, name: String): Collection<Function> = Builtins.getFunctions(name)
public fun getFunctions(session: Session, name: String): Collection<Function> {
return emptyList()
}

/**
* Returns a collection of aggregation functions in this catalog with the given name, or an empty list if none.
*/
public fun getAggregations(session: Session, name: String): Collection<Aggregation> = Builtins.getAggregations(name)
public fun getAggregations(session: Session, name: String): Collection<Aggregation> {
return emptyList()
}

public companion object {

Expand Down
Loading
Loading