Skip to content

Commit

Permalink
Backup tree catalog things
Browse files Browse the repository at this point in the history
  • Loading branch information
RCHowell committed Jul 17, 2024
1 parent f83266b commit ca51d6d
Show file tree
Hide file tree
Showing 13 changed files with 262 additions and 126 deletions.
1 change: 1 addition & 0 deletions partiql-planner/api/partiql-planner.api
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ public final class org/partiql/planner/catalog/Name {
public fun equals (Ljava/lang/Object;)Z
public final fun getName ()Ljava/lang/String;
public final fun getNamespace ()Lorg/partiql/planner/catalog/Namespace;
public final fun getPath ()Ljava/util/List;
public final fun hasNamespace ()Z
public fun hashCode ()I
public static final fun of (Ljava/util/Collection;)Lorg/partiql/planner/catalog/Name;
Expand Down
139 changes: 69 additions & 70 deletions partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Catalog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,27 @@ public interface Catalog {
public fun getTable(session: Session, name: Name): Table? = null

/**
* Get a table by identifier.
* Given an [Identifier], returns a [Table.Handle] that corresponds to the longest-available requested path.
*
* For example, given a table named "Table" located within Catalog "AWS" and Namespace "a".b"."c", a user could
* call [getTableHandle] with the identifier "a"."b"."c"."Table". The returned [Table.Handle] will contain the table
* representation and the matching path: "a"."b"."c"."Table"
*
* As another example, consider a table within a [Namespace] that may be a struct with nested attributes.
* A user could call [getTableHandle] with the identifier "a"."b"."c"."Table"."x". In the Namespace, only table
* "Table" exists. Therefore, this method will return a [Table.Handle] with the "Table" representation and the
* matching path: "a"."b"."c"."Table".
*
* IMPORTANT: The returned [Table.Handle.namespace] must be correct for correct evaluation.
*
* If the [Identifier] does not correspond to an existing [Table], implementers should return null.
*/
public fun getTableHandle(session: Session, identifier: Identifier): Table.Handle? = null

/**
* Creates a table with the given name (possibly in a namespace).
*/
public fun getTable(session: Session, identifier: Identifier): Table? = null
public fun createTable(session: Session, name: Name, schema: PType)

/**
* List top-level tables.
Expand Down Expand Up @@ -57,95 +75,76 @@ public interface Catalog {
public fun getRoutines(session: Session, name: Name): Collection<Routine> = emptyList()

/**
* Factory methods and builder.
* Factory methods.
*/
public companion object {

/**
* Returns a default [Catalog] implementation based upon an in-memory tree.
*
* @param name The name of the catalog.
*/
@JvmStatic
public fun builder(): Builder = Builder()
public fun standard(name: String): Catalog = Standard(name)
}

/**
* Java-style builder for a default [Catalog] implementation.
*
* A default [Catalog] implementation based upon an in-memory tree.
*/
public class Builder {
private class Standard(val name: String) : Catalog {

private var name: String? = null
private var tables = mutableMapOf<String, Table>()
private val root: Tree = Tree(null, mutableMapOf())

public fun name(name: String): Builder {
this.name = name
return this
private class Tree(
private val table: Table?,
private val children: MutableMap<String, Tree>,
) {
fun contains(name: String) = children.contains(name)
fun get(name: String): Tree? = children[name]
fun getOrPut(name: String): Tree = children.getOrPut(name) { Tree(null, mutableMapOf()) }
}

public fun createTable(name: String, schema: PType): Builder {
this.tables[name] = Table.of(name, schema)
return this
}
override fun getName(): String = name

public fun createTable(name: Name, schema: PType): Builder {
if (name.hasNamespace()) {
error("Table name must not have a namespace: $name")
}
this.tables[name.getName()] = Table.of(name.getName(), schema)
return this
override fun getTable(session: Session, name: Name): Table? {
return null
}

public fun build(): Catalog {

val name = this.name ?: throw IllegalArgumentException("Catalog name must be provided")

return object : Catalog {

override fun getName(): String = name

override fun getTable(session: Session, name: Name): Table? {
if (name.hasNamespace()) {
return null
}
return tables[name.getName()]
}

override fun getTable(session: Session, identifier: Identifier): Table? {
if (identifier.hasQualifier()) {
error("Catalog does not support qualified table names")
}
var match: Table? = null
val id = identifier.getIdentifier()
for (table in tables.values) {
if (id.matches(table.getName())) {
if (match == null) {
match = table
} else {
error("Ambiguous table name: $name")
}
}
override fun getTableHandle(session: Session, identifier: Identifier): Table.Handle? {
if (identifier.hasQualifier()) {
error("Catalog does not support qualified table names")
}
var match: Table? = null
val id = identifier.getIdentifier()
for (table in tree.values) {
if (id.matches(table.getName())) {
if (match == null) {
match = table
} else {
error("Ambiguous table name: $name")
}
return match
}

override fun listTables(session: Session): Collection<Name> {
return tables.values.map { Name.of(it.getName()) }
}
}
return match
}

override fun listTables(session: Session, namespace: Namespace): Collection<Name> {
if (!namespace.isEmpty()) {
return emptyList()
}
return tables.values.map { Name.of(it.getName()) }
}
override fun createTable(session: Session, name: Name, schema: PType) {
TODO("Not yet implemented")
}

override fun listNamespaces(session: Session): Collection<Namespace> {
return emptyList()
}
// TODO
override fun listTables(session: Session, namespace: Namespace): Collection<Name> {
return emptyList()
}

override fun listNamespaces(session: Session, namespace: Namespace): Collection<Namespace> {
return emptyList()
}
// TODO
override fun listNamespaces(session: Session, namespace: Namespace): Collection<Namespace> {
return emptyList()
}

override fun getRoutines(session: Session, name: Name): Collection<Routine> = emptyList()
}
// TODO
override fun getRoutines(session: Session, name: Name): Collection<Routine> {
return emptyList()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ public class Name(
* Returns true if the namespace is non-empty.
*/
public fun hasNamespace(): Boolean = !namespace.isEmpty()

/**
* Returns a list of strings representing the path of this name.
*/
public fun getPath(): List<String> {
val parts = mutableListOf<String>()
parts.addAll(namespace.getLevels())
parts.add(name)
return parts
}

/**
* Compares two names including their namespaces and symbols.
*/
Expand Down Expand Up @@ -50,10 +61,7 @@ public class Name(
* Return the SQL name representation of this name — all parts delimited.
*/
override fun toString(): String {
val parts = mutableListOf<String>()
parts.addAll(namespace.getLevels())
parts.add(name)
return Identifier.delimited(parts).toString()
return Identifier.delimited(getPath()).toString()
}

public companion object {
Expand All @@ -69,7 +77,7 @@ public class Name(
*/
@JvmStatic
public fun of(names: Collection<String>): Name {
assert(names.size > 1) { "Cannot create an empty name" }
assert(names.size > 0) { "Cannot create an empty name" }
val namespace = Namespace.of(names.drop(1))
val name = names.last()
return Name(namespace, name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ public class Namespace private constructor(
return levels.contentEquals((other as Namespace).levels)
}

public fun concat(vararg levels: String): Namespace {
return Namespace(this.levels + levels)
}

/**
* The hashCode() is case-sensitive — java.util.Arrays.hashCode
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ import org.partiql.types.PType
*/
public interface Table {

/**
* Handle holds both a table and its namespace within its respective catalog.
*
* Note: This replaces ConnectorObjectHandle from versions < 1.0
*/
public class Handle(
public val namespace: Namespace,
public val table: Table,
)

/**
* The table's name.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.partiql.planner.catalog.internal

import org.partiql.planner.catalog.Name
import org.partiql.planner.catalog.Namespace
import org.partiql.planner.catalog.Table

/**
* Helper for lookups.
*/
internal class Tree {

private val root = Node(null, mutableMapOf())

private class Node(
@JvmField var table: Table?,
@JvmField val children: MutableMap<String, Node>,
) {

fun get(name: String): Node? = children[name]

fun insert(name: String): Node {
var child = children[name]
if (child == null) {
child = Node(null, mutableMapOf())
children[name] = child
}
return child
}
}

private fun get(namespace: Namespace): Node? {
var curr: Node = root
for (name in namespace) {
curr = curr.get(name) ?: return null
}
return curr
}

/**
* Insert the table at the given namespace.
*/
fun insert(namespace: Namespace, table: Table) {
var curr = root
for (name in namespace) {
curr = curr.insert(name)
}
val name = table.getName()
curr = curr.insert(name)
curr.table = table
}

/**
* List tables at the current namespace.
*/
fun listTables(namespace: Namespace): List<Name> {
val ns = get(namespace) ?: return emptyList()
return ns.children.values.mapNotNull {
if (it.table == null) {
null
} else {
Name(namespace, it.table!!.getName())
}
}
}

/**
* List namespaces at the current namespace.
*/
fun listNamespaces(namespace: Namespace): List<Namespace> {
val ns = get(namespace) ?: return emptyList()
return ns.children.mapNotNull { (key, value) ->
if (value.table != null) {
null
} else {
namespace.concat(key)
}
}
}

/**
* Lookup the namespace, then get the table.
*/
fun getTable(name: Name): Table? {
return get(name.getNamespace())?.get(name.getName())?.table
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package org.partiql.planner.internal

import org.partiql.planner.catalog.Catalogs
import org.partiql.planner.catalog.Identifier
import org.partiql.planner.catalog.Name
import org.partiql.planner.catalog.Session
import org.partiql.planner.internal.casts.CastTable
import org.partiql.planner.internal.ir.Ref
import org.partiql.planner.internal.ir.Rex
import org.partiql.planner.internal.ir.rexOpCastResolved
import org.partiql.planner.internal.ir.rexOpVarGlobal
import org.partiql.planner.internal.typer.CompilerType

/**
Expand All @@ -23,18 +26,35 @@ internal class Env(
private val session: Session,
) {

private val default = catalogs.default()

/**
*
* TODO handle missing table error.
* TODO fallback to matching root for a catalog name if it exists.
*
* Convert any remaining binding names (tail) to a path expression.
*/
fun getTable(identifier: Identifier): Rex? {
TODO("Env.getTable not implemented")
val table = default.getTableHandle(session, identifier)
if (table == null) {
// error table not found?
return null
}

// search for longest number of path steps.



val ref = Ref.Table(
catalog = default.getName(),
name = Name.of(table.getName()),
type = CompilerType(table.getSchema())
)
return Rex(ref.type, rexOpVarGlobal(ref))
}

fun getRoutine(identifier: Identifier, args: List<Rex>): Rex? {
TODO("Env.getRoutine not implemented")
// TODO
return null
}

fun resolveCast(input: Rex, target: CompilerType): Rex.Op.Cast.Resolved? {
Expand Down
Loading

0 comments on commit ca51d6d

Please sign in to comment.