Skip to content

Commit

Permalink
Updates catalog names for case-insensitive lookup
Browse files Browse the repository at this point in the history
  • Loading branch information
RCHowell committed Jul 11, 2024
1 parent d8ef10b commit 9f558e4
Show file tree
Hide file tree
Showing 8 changed files with 393 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,43 @@ public interface Catalog {

/**
* Get a table by name.
*
* @param name The case-sensitive [Table] name.
* @return The [Table] or null if not found.
*/
public fun getTable(name: Name): Table? = null
public fun getTable(session: Session, name: Name): Table? = null

/**
* Get a table by identifier; note that identifiers may be case-insensitive.
*/
public fun getTable(session: Session, identifier: Identifier): Table? = null

/**
* List top-level tables.
*/
public fun listTables(): Collection<Name> = listTables(Namespace.empty())
public fun listTables(session: Session): Collection<Name> = listTables(session, Namespace.root())

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

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

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

/**
* 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 getRoutines(name: Name): Collection<Routine> = emptyList()
public fun getRoutines(session: Session, name: Name): Collection<Routine> = emptyList()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.partiql.planner.catalog

/**
* Catalogs is used to provide the default catalog and possibly others by name.
*/
public interface Catalogs {

/**
* Returns the default catalog. Required.
*/
public fun default(): Catalog

/**
* Returns a catalog by name (single identifier).
*/
public fun get(name: String, ignoreCase: Boolean = false): Catalog? {
val default = default()
return if (name.equals(default.getName(), ignoreCase)) {
default
} else {
null
}
}

/**
* Returns a list of all available catalogs.
*/
public fun list(): Collection<Catalog> = listOf(default())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package org.partiql.planner.catalog

/**
* Represents an SQL identifier (possibly qualified).
*
* @property qualifier If
* @property identifier
*/
public class Identifier private constructor(
private val qualifier: Array<Part>,
private val identifier: Part,
) {

/**
* Returns the unqualified name part.
*/
public fun getIdentifier(): Part = identifier

/**
* Returns the name's namespace.
*/
public fun getQualifier(): Array<Part> = qualifier

/**
* Returns true if the namespace is non-empty.
*/
public fun hasQualifier(): Boolean = qualifier.isNotEmpty()

/**
* Compares one identifier to another, possibly ignoring case.
*/
public fun matches(other: Identifier, ignoreCase: Boolean = false): Boolean {
//
if (this.qualifier.size != other.qualifier.size) {
return false
}
// Compare identifier
if (ignoreCase && !(this.identifier.matches(other.identifier))) {
return false
} else if (this.identifier != other.identifier) {
return false
}
for (i in this.qualifier.indices) {
val lhs = this.qualifier[i]
val rhs = other.qualifier[i]
if (ignoreCase && !lhs.matches(rhs)) {
return false
} else if (lhs != rhs) {
return false
}
}
return true
}

/**
* Compares the case-preserved text of two identifiers — that is case-sensitive equality.
*/
public override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other == null || javaClass != other.javaClass) {
return false
}
other as Identifier
return (this.identifier == other.identifier && this.qualifier.contentEquals(other.qualifier))
}

/**
* The hashCode() is case-sensitive — java.util.Arrays.hashCode
*/
public override fun hashCode(): Int {
var result = 1
result = 31 * result + qualifier.hashCode()
result = 31 * result + identifier.hashCode()
return result
}

/**
* Return the SQL representation of this identifier.
*/
public override fun toString(): String = buildString {
if (qualifier.isNotEmpty()) {
append(qualifier.joinToString("."))
append(".")
}
append(identifier)
}

/**
* Represents an SQL identifier part which is either regular (unquoted) or delimited (double-quoted).
*
* @property text The case-preserved identifier text.
* @property regular True if the identifier should be treated as an SQL regular identifier.
*/
public class Part private constructor(
private val text: String,
private val regular: Boolean,
) {

/**
* Compares two identifiers, ignoring case iff at least one identifier is non-delimited.
*/
public fun matches(other: Part): Boolean {
return this.text.equals(other.text, ignoreCase = (this.regular || other.regular))
}

/**
* Compares the case-preserved text of two identifiers — that is case-sensitive equality.
*/
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other == null || javaClass != other.javaClass) {
return false
}
return this.text == (other as Part).text
}

/**
* Returns the hashcode of the identifier's case-preserved text.
*/
override fun hashCode(): Int {
return this.text.hashCode()
}

/**
* Return the identifier as a SQL string.
*/
override fun toString(): String = when (regular) {
true -> "\"${text}\""
false -> text
}

public companion object {

@JvmStatic
public fun regular(text: String): Part = Part(text, true)

@JvmStatic
public fun delimited(text: String): Part = Part(text, false)
}
}

public companion object {

@JvmStatic
public fun regular(text: String): Identifier = Identifier(emptyArray(), Part.regular(text))

@JvmStatic
public fun delimited(text: String): Identifier = Identifier(emptyArray(), Part.delimited(text))

@JvmStatic
public fun of(part: Part): Identifier = Identifier(emptyArray(), part)

@JvmStatic
public fun of(vararg parts: Part): Identifier = TODO()

@JvmStatic
public fun of(parts: Collection<Part>): Identifier = TODO()

@JvmStatic
public fun of(vararg parts: String): Identifier {
TODO()
}

@JvmStatic
public fun of(parts: Collection<String>): Identifier {
TODO()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,79 @@
package org.partiql.planner.catalog

/**
* Thin wrapper over a list of strings.
* A reference to a named object in a catalog; case-preserved.
*/
public data class Name(
public class Name(
private val namespace: Namespace,
private val name: String,
) {

/**
* Returns the unqualified name part.
*/
public fun getName(): String = name

/**
* Returns the name's namespace.
*/
public fun getNamespace(): Namespace = namespace

/**
* Returns true if the namespace is non-empty.
*/
public fun hasNamespace(): Boolean = !namespace.isEmpty()

public fun getName(): String = name
/**
* Compares two names including their namespaces and symbols.
*/
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other == null || javaClass != other.javaClass) {
return false
}
other as Name
return (this.name == other.name) && (this.namespace == other.namespace)
}

/**
* The hashCode() is case-sensitive.
*/
override fun hashCode(): Int {
var result = 1
result = 31 * result + namespace.hashCode()
result = 31 * result + name.hashCode()
return result
}

/**
* 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.of(parts).toString()
}

public companion object {

/**
* Construct a name from a string.
*/
@JvmStatic
public fun of(vararg names: String): Name = of(names.toList())

/**
* Construct a name from a collection of strings.
*/
@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(),
)
public fun of(names: Collection<String>): Name {
assert(names.size > 1) { "Cannot create an empty name" }
val namespace = Namespace.of(names.drop(1))
val name = names.last()
return Name(namespace, name)
}
}
}
Loading

0 comments on commit 9f558e4

Please sign in to comment.