Skip to content

Commit

Permalink
Updates plugins to use the Catalog and Bindings interfaces (#1502)
Browse files Browse the repository at this point in the history
  • Loading branch information
RCHowell authored Jul 10, 2024
1 parent bb5cbbb commit d8ef10b
Show file tree
Hide file tree
Showing 26 changed files with 349 additions and 1,208 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.partiql.plugins.local

import org.junit.jupiter.api.Test

class LocalConnectorTest {

@Test
fun sanity() {
TODO("Not yet implemented")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,32 @@ public interface Catalog {
/**
* List top-level tables.
*/
public fun listTables(): Name? = null
public fun listTables(): Collection<Name> = listTables(Namespace.empty())

/**
* List all tables under this namespace.
*
* @param namespace
*/
public fun listTables(namespace: Namespace): List<Name> = emptyList()
public fun listTables(namespace: Namespace): Collection<Name> = emptyList()

/**
* List top-level namespaces from the catalog.
*/
public fun listNamespaces(): Collection<Namespace> = emptyList()
public fun listNamespaces(): Collection<Namespace> = listNamespaces(Namespace.empty())

/**
* List all child namespaces from the namespace.
*
* @param namespace
*/
public fun listNamespaces(namespace: Namespace)
public fun listNamespaces(namespace: Namespace): Collection<Namespace> = emptyList()

/**
* Get a function's variants by name.
* Get a routine's variants by name.
*
* @param name The case-sensitive [Routine] name.
* @return A collection of all [Routine]s in the current namespace with this name.
*/
public fun getFunctions(name: Name): Collection<Routine> = emptyList()
public fun getRoutines(name: Name): Collection<Routine> = emptyList()
}
30 changes: 17 additions & 13 deletions partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Name.kt
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
package org.partiql.planner.catalog

import java.util.Spliterator
import java.util.function.Consumer

/**
* Thin wrapper over a list of strings.
*/
public data class Name(public val steps: List<String>) : Iterable<String> {

public companion object {
public data class Name(
private val namespace: Namespace,
private val name: String,
) {

@JvmStatic
public fun of(vararg steps: String): Name = Name(steps.toList())
}
public fun getNamespace(): Namespace = namespace

public operator fun get(index: Int): String = steps[index]
public fun hasNamespace(): Boolean = !namespace.isEmpty()

override fun forEach(action: Consumer<in String>?): Unit = steps.forEach(action)
public fun getName(): String = name

override fun iterator(): Iterator<String> = steps.iterator()
public companion object {

override fun spliterator(): Spliterator<String> = steps.spliterator()
@JvmStatic
public fun of(vararg names: String): Name {
assert(names.size > 1) { "Cannot create an empty" }
return Name(
namespace = Namespace.of(*names.drop(1).toTypedArray()),
name = names.last(),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,20 @@ public class Namespace private constructor(

public fun empty(): Namespace = EMPTY

@JvmStatic
public fun of(vararg levels: String): Namespace {
if (levels.isEmpty()) {
return empty()
}
return Namespace(arrayOf(*levels))
}

@JvmStatic
public fun of(levels: Collection<String>): Namespace {
if (levels.isEmpty()) {
return empty()
}
return Namespace(levels.toTypedArray())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
package org.partiql.spi.connector

import com.amazon.ionelement.api.StructElement
import com.amazon.ionelement.api.emptyIonStruct
import org.partiql.eval.bindings.Bindings
import org.partiql.planner.catalog.Catalog

/**
* A mechanism by which PartiQL can access bindings and metadata for a namespace (catalog).
* A mechanism by which PartiQL can access bindings and catalog metadata.
*/
public interface Connector {

Expand All @@ -28,9 +30,9 @@ public interface Connector {
public fun getBindings(): Bindings

/**
* Returns the root namespace of this catalog.
* Returns a [Catalog] which the planner uses to load catalog metadata.
*/
public fun getMetadata(): org.partiql.planner.catalog.Catalog
public fun getCatalog(): Catalog

/**
* A Plugin leverages a [Factory] to produce a [Connector] which is used for binding and metadata access.
Expand All @@ -48,6 +50,6 @@ public interface Connector {
* @param config
* @return
*/
public fun create(config: StructElement? = null): Connector
public fun create(config: StructElement = emptyIonStruct()): Connector
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.partiql.plugins.local

import org.partiql.eval.bindings.Binding
import org.partiql.eval.bindings.Bindings

internal object LocalBindings : Bindings {

override fun getBindings(name: String): Bindings? = null

override fun getBinding(name: String): Binding? = null
}
Original file line number Diff line number Diff line change
@@ -1,109 +1,75 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at:
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*/

package org.partiql.plugins.local

import com.amazon.ionelement.api.loadSingleElement
import org.partiql.spi.BindingCase
import org.partiql.spi.BindingName
import org.partiql.spi.BindingPath
import org.partiql.types.StaticType
import java.io.File
import org.partiql.planner.catalog.Catalog
import org.partiql.planner.catalog.Name
import org.partiql.planner.catalog.Namespace
import org.partiql.planner.catalog.Routine
import org.partiql.planner.catalog.Table
import java.nio.file.Path

private sealed class FsTree(val name: String) {

// "Directory" node
class D(name: String, val children: List<FsTree>) : FsTree(name)

// Type node
class T(name: String, val type: StaticType) : FsTree(name)
}
import kotlin.io.path.isDirectory
import kotlin.io.path.notExists

/**
* Build a memoized catalog tree from local schema definitions.
* Implementation of [Catalog] where dirs are namespaces and files are table metadata.
*/
internal class LocalCatalog private constructor(private val root: FsTree.D) {
internal class LocalCatalog(
private val name: String,
private val root: Path,
) : Catalog {

/**
* Search the tree for the type.
*/
public fun lookup(path: BindingPath): LocalObject? {
val match = mutableListOf<String>()
var curr: FsTree? = root
for (step in path.steps) {
if (curr == null) return null
when (curr) {
is FsTree.T -> break
is FsTree.D -> {
curr = curr.children.firstOrNull { step.matches(it.name) }
if (curr != null) match.add(curr.name)
}
}
}
// All steps matched and we're at a leaf
if (curr is FsTree.T) {
return LocalObject(match, curr.type)
}
return null
private companion object {
private const val EXT = ".ion"
}

init {
assert(root.isDirectory()) { "LocalNamespace must be a directory" }
}

/**
* Provide a list of all objects in this catalog.
*/
public fun listObjects(): List<BindingPath> = sequence { search(emptyList(), root) }.toList()
override fun getName(): String {
return name
}

private suspend fun SequenceScope<BindingPath>.search(acc: List<BindingName>, node: FsTree) =
when (node) {
is FsTree.D -> search(acc, node)
is FsTree.T -> search(acc, node)
override fun getTable(name: Name): Table? {
val path = toPath(name.getNamespace()).resolve(name.getName() + EXT)
if (path.notExists() || !path.isDirectory()) {
return null
}
return LocalTable(name.getName(), path)
}

private suspend fun SequenceScope<BindingPath>.search(acc: List<BindingName>, node: FsTree.D) {
val steps = acc + BindingName(node.name, BindingCase.INSENSITIVE)
for (child in node.children) {
search(steps, child)
override fun listTables(namespace: Namespace): Collection<Name> {
val path = toPath(namespace)
if (path.notExists()) {
// throw exception?
return emptyList()
}
return super.listTables(namespace)
}

private suspend fun SequenceScope<BindingPath>.search(acc: List<BindingName>, node: FsTree.T) {
val steps = acc + BindingName(node.name, BindingCase.INSENSITIVE)
this.yield(BindingPath(steps))
override fun listNamespaces(namespace: Namespace): Collection<Namespace> {
val path = toPath(namespace)
if (path.notExists() || path.isDirectory()) {
// throw exception?
return emptyList()
}
// List all child directories
return path.toFile()
.listFiles()!!
.filter { it.isDirectory }
.map { toNamespace(it.toPath()) }
}

companion object {

/**
* Builds a FsTree from the given root.
*/
public fun load(root: Path): LocalCatalog = LocalCatalog(root.toFile().tree() as FsTree.D)
override fun getRoutines(name: Name): Collection<Routine> = emptyList()

private fun File.tree(): FsTree = when (this.isDirectory) {
true -> d()
else -> t()
}

private fun File.d(): FsTree.D {
val children = listFiles()!!.map { it.tree() }
return FsTree.D(name, children)
private fun toPath(namespace: Namespace): Path {
var curr = root
for (level in namespace) {
curr = curr.resolve(level)
}
return curr
}

private fun File.t(): FsTree.T {
val text = readText()
val ion = loadSingleElement(text)
val type = ion.toStaticType()
return FsTree.T(nameWithoutExtension, type)
}
private fun toNamespace(path: Path): Namespace {
return Namespace.of(path.relativize(root).map { it.toString() })
}
}
Loading

0 comments on commit d8ef10b

Please sign in to comment.