Skip to content

Commit

Permalink
Adds optional casing to names/namespaces
Browse files Browse the repository at this point in the history
  • Loading branch information
RCHowell committed Jul 10, 2024
1 parent 7b13be1 commit d093760
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 25 deletions.
107 changes: 99 additions & 8 deletions partiql-planner/src/main/kotlin/org/partiql/planner/catalog/Name.kt
Original file line number Diff line number Diff line change
@@ -1,28 +1,119 @@
package org.partiql.planner.catalog

import org.partiql.ast.Identifier
import org.partiql.ast.sql.sql

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

public fun getNamespace(): Namespace = namespace

public fun hasNamespace(): Boolean = !namespace.isEmpty()

public fun getName(): String = name
public fun getName(): String = name.symbol

override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other == null || javaClass != other.javaClass) {
return false
}
return matches(other as Name, ignoreCase = false)
}

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

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

/**
* 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 && !matches(name, other.name)) {
return false
} else if (name.symbol != other.name.symbol) {
return false
}
return this.namespace.matches(other.namespace, ignoreCase)
}

// TODO de-duplicate or define on Identifier class
private fun matches(lhs: Identifier.Symbol, rhs: Identifier.Symbol): Boolean {
val ignoreCase = (
lhs.caseSensitivity == Identifier.CaseSensitivity.INSENSITIVE ||
rhs.caseSensitivity == Identifier.CaseSensitivity.INSENSITIVE
)
return lhs.symbol.equals(rhs.symbol, ignoreCase)
}

public companion object {

@JvmStatic
public fun of(vararg names: String): Name {
assert(names.size > 1) { "Cannot create an empty" }
public fun of(vararg names: String): Name = of(names.toList())

@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.Symbol(names.last(), Identifier.CaseSensitivity.SENSITIVE)
return Name(namespace, name)
}

@JvmStatic
public fun of(identifier: Identifier): Name = when (identifier) {
is Identifier.Qualified -> of(identifier)
is Identifier.Symbol -> of(identifier)
}

@JvmStatic
public fun of(identifier: Identifier.Symbol): Name {
return Name(
namespace = Namespace.of(*names.drop(1).toTypedArray()),
name = names.last(),
namespace = Namespace.root(),
name = identifier
)
}

@JvmStatic
public fun of(identifier: Identifier.Qualified): Name {
val identifiers = mutableListOf<Identifier.Symbol>()
identifiers.add(identifier.root)
identifiers.addAll(identifier.steps)
return of(identifiers)
}

@JvmStatic
public fun of(identifiers: Collection<Identifier.Symbol>): Name {
assert(identifiers.size > 1) { "Cannot create an empty name" }
val namespace = Namespace.of(identifiers.drop(1))
val name = identifiers.last()
return Name(namespace, name)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.partiql.planner.catalog

import org.partiql.ast.Identifier
import org.partiql.ast.sql.sql
import java.util.Spliterator
import java.util.function.Consumer

Expand All @@ -11,10 +13,10 @@ import java.util.function.Consumer
* - Calcite — https://github.com/apache/calcite/blob/main/core/src/main/java/org/apache/calcite/schema/Schema.java
*/
public class Namespace private constructor(
private val levels: Array<String>,
) : Iterable<String> {
private val levels: Array<Identifier.Symbol>,
) : Iterable<Identifier.Symbol> {

public fun getLevels(): Array<String> {
public fun getLevels(): Array<Identifier.Symbol> {
return levels
}

Expand All @@ -26,19 +28,19 @@ public class Namespace private constructor(
return levels.isEmpty()
}

public operator fun get(index: Int): String {
public operator fun get(index: Int): Identifier.Symbol {
return levels[index]
}

override fun forEach(action: Consumer<in String>?) {
override fun forEach(action: Consumer<in Identifier.Symbol>?) {
levels.toList().forEach(action)
}

override fun iterator(): Iterator<String> {
override fun iterator(): Iterator<Identifier.Symbol> {
return levels.iterator()
}

override fun spliterator(): Spliterator<String> {
override fun spliterator(): Spliterator<Identifier.Symbol> {
return levels.toList().spliterator()
}

Expand All @@ -49,16 +51,62 @@ public class Namespace private constructor(
if (other == null || javaClass != other.javaClass) {
return false
}
val namespace = other as Namespace
return levels.contentEquals(namespace.levels)
return matches(other as Namespace, ignoreCase = false)
}

/**
* The hashCode() is case-sensitive — java.util.Arrays.hashCode
*/
public override fun hashCode(): Int {
return levels.contentHashCode()
var result = 1
for (element in levels) result = 31 * result + element.symbol.hashCode()
return result
}

/**
* Return the SQL identifier representation of this namespace.
*/
public override fun toString(): String {
return levels.joinToString(".")
return levels.joinToString(".") { it.sql() }
}

/**
* Compares one namespace to another, possibly ignoring case.
*
* Note that ignoring case pushes the case-sensitivity check down to the identifier matching.
*
* For example,
* "x" and "X" are NOT equal regardless of ignoreCase because they are delimited.
* x and X are NOT equal with ignoreCase=false but are matching identifiers.
*
* @param other The other namespace to match against.
* @param ignoreCase If false, then compare all levels case-sensitively (exact-case match).
* @return
*/
public fun matches(other: Namespace, ignoreCase: Boolean = false): Boolean {
val n = getLength()
if (n != other.getLength()) {
return false
}
for (i in 0 until n) {
val lhs = levels[i]
val rhs = other[i]
if (ignoreCase && !matches(lhs, rhs)) {
return false
} else if (lhs.symbol != rhs.symbol) {
return false
}
}
return true
}

// TODO de-duplicate or define on Identifier class
private fun matches(lhs: Identifier.Symbol, rhs: Identifier.Symbol): Boolean {
val ignoreCase = (
lhs.caseSensitivity == Identifier.CaseSensitivity.INSENSITIVE ||
rhs.caseSensitivity == Identifier.CaseSensitivity.INSENSITIVE
)
return lhs.symbol.equals(rhs.symbol, ignoreCase)
}

public companion object {
Expand All @@ -68,19 +116,42 @@ public class Namespace private constructor(
public fun root(): Namespace = ROOT

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

@JvmStatic
public fun of(levels: Collection<String>): Namespace {
if (levels.isEmpty()) {
return root()
}
return Namespace(
levels
.map { Identifier.Symbol(it, Identifier.CaseSensitivity.SENSITIVE) }
.toTypedArray()
)
}

@JvmStatic
public fun of(identifier: Identifier): Namespace = when (identifier) {
is Identifier.Qualified -> of(identifier)
is Identifier.Symbol -> of(identifier)
}

@JvmStatic
public fun of(identifier: Identifier.Symbol): Namespace {
return Namespace(arrayOf(identifier))
}

@JvmStatic
public fun of(identifier: Identifier.Qualified): Namespace {
val levels = mutableListOf<Identifier.Symbol>()
levels.add(identifier.root)
levels.addAll(identifier.steps)
return Namespace(levels.toTypedArray())
}

@JvmStatic
public fun of(identifiers: Collection<Identifier.Symbol>): Namespace {
return Namespace(identifiers.toTypedArray())
}
}
}

0 comments on commit d093760

Please sign in to comment.