Skip to content

Commit

Permalink
Merges v1-metadata-catalog identifier factoring
Browse files Browse the repository at this point in the history
  • Loading branch information
RCHowell committed Jul 15, 2024
2 parents 207b508 + 9f558e4 commit ab3413b
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 136 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ 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(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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ public interface Catalogs {
/**
* Returns a catalog by name (single identifier).
*/
public fun get(identifier: Identifier): Catalog? {
public fun get(name: String, ignoreCase: Boolean = false): Catalog? {
val default = default()
return if (identifier.matches(default.getName())) {
return if (name.equals(default.getName(), ignoreCase)) {
default
} else {
null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,69 +1,183 @@
package org.partiql.planner.catalog

/**
* Represents an SQL identifier (<regular identifier>) which is regular (unquoted) or delimited (double-quoted).
* Represents an SQL identifier (possibly qualified).
*
* @property text The identifier body.
* @property regular True if the identifier should be treated as an SQL regular identifier.
* @property qualifier If
* @property identifier
*/
public class Identifier private constructor(
private val text: String,
private val regular: Boolean,
private val qualifier: Array<Part>,
private val identifier: Part,
) {

/**
* Returns the identifier's case-preserved text.
* Returns the unqualified name part.
*/
public fun getText(): String = text
public fun getIdentifier(): Part = identifier

/**
* Compares this identifier to a string.
* Returns the name's namespace.
*/
public fun matches(other: String): Boolean {
return this.text.equals(other, ignoreCase = this.regular)
}
public fun getQualifier(): Array<Part> = qualifier

/**
* Compares two identifiers, ignoring case iff at least one identifier is non-delimited.
* Returns true if the namespace is non-empty.
*/
public fun matches(other: Identifier): Boolean {
return this.text.equals(other.text, ignoreCase = (this.regular || other.regular))
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.
*/
override fun equals(other: Any?): Boolean {
public override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other == null || javaClass != other.javaClass) {
return false
}
return this.text == (other as Identifier).text
other as Identifier
return (this.identifier == other.identifier && this.qualifier.contentEquals(other.qualifier))
}

/**
* Returns the hashcode of the identifier's case-preserved text.
* The hashCode() is case-sensitive — java.util.Arrays.hashCode
*/
override fun hashCode(): Int {
return this.text.hashCode()
public override fun hashCode(): Int {
var result = 1
result = 31 * result + qualifier.hashCode()
result = 31 * result + identifier.hashCode()
return result
}

/**
* Return the identifier as a SQL string.
* Return the SQL representation of this identifier.
*/
override fun toString(): String = when (regular) {
true -> "\"${text}\""
false -> text
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(text, true)
public fun regular(text: String): Identifier = Identifier(emptyArray(), Part.regular(text))

@JvmStatic
public fun delimited(text: String): Identifier = Identifier(text, false)
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 = of(parts.toList())

@JvmStatic
public fun of(parts: Collection<Part>): Identifier {
if (parts.isEmpty()) {
error("Cannot create an identifier with no parts")
}
val qualifier = parts.drop(1).toTypedArray()
val identifier = parts.last()
return Identifier(qualifier, identifier)
}

@JvmStatic
public fun of(vararg parts: String): Identifier = of(parts.toList())

@JvmStatic
public fun of(parts: Collection<String>): Identifier {
if (parts.isEmpty()) {
error("Cannot create an identifier with no parts")
}
val qualifier = parts.drop(1).map { Part.delimited(it) }.toTypedArray()
val identifier = Part.delimited(parts.last())
return Identifier(qualifier, identifier)
}
}
}
62 changes: 19 additions & 43 deletions partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Name.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@ package org.partiql.planner.catalog
*/
public class Name(
private val namespace: Namespace,
private val name: Identifier,
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(): Identifier = name

/**
* Compares two names including their namespaces and symbols.
*/
Expand All @@ -24,7 +32,8 @@ public class Name(
if (other == null || javaClass != other.javaClass) {
return false
}
return matches(other as Name, ignoreCase = false)
other as Name
return (this.name == other.name) && (this.namespace == other.namespace)
}

/**
Expand All @@ -38,30 +47,13 @@ public class Name(
}

/**
* Return the SQL name representation of this name.
* Return the SQL name representation of this name — all parts delimited.
*/
override fun toString(): String {
return if (namespace.isEmpty()) {
name.toString()
} else {
"$namespace.$name"
}
}

/**
* Compares one name to another, possibly ignoring case.
*
* @param other The other name to match against.
* @param ignoreCase If false, the compare all levels case-sensitively (exact-case match).
* @return
*/
public fun matches(other: Name, ignoreCase: Boolean = false): Boolean {
if (ignoreCase && !(this.name.matches(other.name))) {
return false
} else if (name != other.name) {
return false
}
return this.namespace.matches(other.namespace, ignoreCase)
val parts = mutableListOf<String>()
parts.addAll(namespace.getLevels())
parts.add(name)
return Identifier.of(parts).toString()
}

public companion object {
Expand All @@ -77,22 +69,6 @@ public class Name(
*/
@JvmStatic
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 = Identifier.delimited(names.last())
return Name(namespace, name)
}

@JvmStatic
public fun of(name: Identifier): Name {
return Name(
namespace = Namespace.root(),
name = name
)
}

@JvmStatic
public fun of(names: Collection<Identifier>): Name {
assert(names.size > 1) { "Cannot create an empty name" }
val namespace = Namespace.of(names.drop(1))
val name = names.last()
Expand Down
Loading

0 comments on commit ab3413b

Please sign in to comment.