Skip to content

qualified identifier #1411

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- Support parsing of qualified name as table name for DDL command CREATE TABLE and DROP TABLE.

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,11 @@ internal class PartiQLPigVisitor(
}

override fun visitDropTable(ctx: PartiQLParser.DropTableContext) = PartiqlAst.build {
val id = visitSymbolPrimitive(ctx.tableName().symbolPrimitive())
val id = if (ctx.qualifiedName().qualifier.isEmpty()) {
visitSymbolPrimitive(ctx.qualifiedName().name)
} else {
throw ParserException("PIG Parser does not support qualified name as table name", ErrorCode.PARSE_UNEXPECTED_TOKEN)
}
dropTable(id.toIdentifier(), ctx.DROP().getSourceMetaContainer())
}

Expand All @@ -236,7 +240,11 @@ internal class PartiQLPigVisitor(
}

override fun visitCreateTable(ctx: PartiQLParser.CreateTableContext) = PartiqlAst.build {
val name = visitSymbolPrimitive(ctx.tableName().symbolPrimitive()).name
val name = if (ctx.qualifiedName().qualifier.isEmpty()) {
visitSymbolPrimitive(ctx.qualifiedName().name).name
} else {
throw ParserException("PIG Parser does not support qualified name as table name", ErrorCode.PARSE_UNEXPECTED_TOKEN)
}
val def = ctx.tableDef()?.let { visitTableDef(it) }
createTable_(name, def, ctx.CREATE().getSourceMetaContainer())
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.partiql.lang.syntax

import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ArgumentsSource
import org.partiql.errors.ErrorCode
import org.partiql.errors.Property
import org.partiql.lang.util.ArgumentsProviderBase

internal class PartiQLParserDDLTest : PartiQLParserTestBase() {
// As we expended the functionality of DDL, making sure that the PIG Parser is not impacted.

override val targets: Array<ParserTarget> = arrayOf(ParserTarget.DEFAULT)

internal data class ParserErrorTestCase(
val description: String? = null,
val query: String,
val code: ErrorCode,
val context: Map<Property, Any> = emptyMap()
)

@ArgumentsSource(ErrorTestProvider::class)
@ParameterizedTest
fun errorTests(tc: ParserErrorTestCase) = checkInputThrowingParserException(tc.query, tc.code, tc.context, assertContext = false)

class ErrorTestProvider : ArgumentsProviderBase() {
override fun getParameters() = listOf(
ParserErrorTestCase(
description = "PIG Parser does not support qualified Identifier as input for Create",
query = "CREATE TABLE foo.bar",
code = ErrorCode.PARSE_UNEXPECTED_TOKEN,
context = mapOf()
),
ParserErrorTestCase(
description = "PIG Parser does not support qualified Identifier as input for DROP",
query = "DROP Table foo.bar",
code = ErrorCode.PARSE_UNEXPECTED_TOKEN,
context = mapOf(),
)
)
}
}
7 changes: 5 additions & 2 deletions partiql-parser/src/main/antlr/PartiQL.g4
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ execCommand
* Currently, this is a small subset of SQL DDL that is likely to make sense for PartiQL as well.
*/

// <qualified name> ::= [ <schema name> <period> ] <qualified identifier>
qualifiedName : (qualifier+=symbolPrimitive PERIOD)* name=symbolPrimitive;

tableName : symbolPrimitive;
tableConstraintName : symbolPrimitive;
columnName : symbolPrimitive;
Expand All @@ -82,12 +85,12 @@ ddl
;

createCommand
: CREATE TABLE tableName ( PAREN_LEFT tableDef PAREN_RIGHT )? # CreateTable
: CREATE TABLE qualifiedName ( PAREN_LEFT tableDef PAREN_RIGHT )? # CreateTable
| CREATE INDEX ON symbolPrimitive PAREN_LEFT pathSimple ( COMMA pathSimple )* PAREN_RIGHT # CreateIndex
;

dropCommand
: DROP TABLE target=tableName # DropTable
: DROP TABLE qualifiedName # DropTable
| DROP INDEX target=symbolPrimitive ON on=symbolPrimitive # DropIndex
;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,18 @@ internal class PartiQLParserDefault : PartiQLParser {
}
}

override fun visitQualifiedName(ctx: org.partiql.parser.antlr.PartiQLParser.QualifiedNameContext) = translate(ctx) {
val qualifier = ctx.qualifier.map { visitSymbolPrimitive(it) }
val name = visitSymbolPrimitive(ctx.name)
if (qualifier.isEmpty()) {
name
} else {
val root = qualifier.first()
val steps = qualifier.drop(1) + listOf(name)
identifierQualified(root, steps)
}
}

/**
*
* DATA DEFINITION LANGUAGE (DDL)
Expand All @@ -579,7 +591,7 @@ internal class PartiQLParserDefault : PartiQLParser {
override fun visitQueryDdl(ctx: GeneratedParser.QueryDdlContext): AstNode = visitDdl(ctx.ddl())

override fun visitDropTable(ctx: GeneratedParser.DropTableContext) = translate(ctx) {
val table = visitSymbolPrimitive(ctx.tableName().symbolPrimitive())
val table = visitQualifiedName(ctx.qualifiedName())
statementDDLDropTable(table)
}

Expand All @@ -590,7 +602,7 @@ internal class PartiQLParserDefault : PartiQLParser {
}

override fun visitCreateTable(ctx: GeneratedParser.CreateTableContext) = translate(ctx) {
val table = visitSymbolPrimitive(ctx.tableName().symbolPrimitive())
val table = visitQualifiedName(ctx.qualifiedName())
val definition = ctx.tableDef()?.let { visitTableDef(it) }
statementDDLCreateTable(table, definition)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package org.partiql.parser.internal

import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.ArgumentsProvider
import org.junit.jupiter.params.provider.ArgumentsSource
import org.partiql.ast.AstNode
import org.partiql.ast.Identifier
import org.partiql.ast.identifierQualified
import org.partiql.ast.identifierSymbol
import org.partiql.ast.statementDDLCreateTable
import org.partiql.ast.statementDDLDropTable
import java.util.stream.Stream
import kotlin.test.assertEquals

class PartiQLParserDDLTests {

private val parser = PartiQLParserDefault()

data class SuccessTestCase(
val description: String? = null,
val query: String,
val node: AstNode
)

@ArgumentsSource(TestProvider::class)
@ParameterizedTest
fun errorTests(tc: SuccessTestCase) = assertExpression(tc.query, tc.node)

class TestProvider : ArgumentsProvider {
val createTableTests = listOf(
SuccessTestCase(
"CREATE TABLE with unqualified case insensitive name",
"CREATE TABLE foo",
statementDDLCreateTable(
identifierSymbol("foo", Identifier.CaseSensitivity.INSENSITIVE),
null
)
),
// Support Case Sensitive identifier as table name
// Subsequent process may need to change
// See: https://www.db-fiddle.com/f/9A8mknSNYuRGLfkqkLeiHD/0 for reference.
SuccessTestCase(
"CREATE TABLE with unqualified case sensitive name",
"CREATE TABLE \"foo\"",
statementDDLCreateTable(
identifierSymbol("foo", Identifier.CaseSensitivity.SENSITIVE),
null
)
),
SuccessTestCase(
"CREATE TABLE with qualified case insensitive name",
"CREATE TABLE myCatalog.mySchema.foo",
statementDDLCreateTable(
identifierQualified(
identifierSymbol("myCatalog", Identifier.CaseSensitivity.INSENSITIVE),
listOf(
identifierSymbol("mySchema", Identifier.CaseSensitivity.INSENSITIVE),
identifierSymbol("foo", Identifier.CaseSensitivity.INSENSITIVE),
)
),
null
)
),
SuccessTestCase(
"CREATE TABLE with qualified name with mixed case sensitivity",
"CREATE TABLE myCatalog.\"mySchema\".foo",
statementDDLCreateTable(
identifierQualified(
identifierSymbol("myCatalog", Identifier.CaseSensitivity.INSENSITIVE),
listOf(
identifierSymbol("mySchema", Identifier.CaseSensitivity.SENSITIVE),
identifierSymbol("foo", Identifier.CaseSensitivity.INSENSITIVE),
)
),
null
)
),
)

val dropTableTests = listOf(
SuccessTestCase(
"DROP TABLE with unqualified case insensitive name",
"DROP TABLE foo",
statementDDLDropTable(
identifierSymbol("foo", Identifier.CaseSensitivity.INSENSITIVE),
)
),
SuccessTestCase(
"DROP TABLE with unqualified case sensitive name",
"DROP TABLE \"foo\"",
statementDDLDropTable(
identifierSymbol("foo", Identifier.CaseSensitivity.SENSITIVE),
)
),
SuccessTestCase(
"DROP TABLE with qualified case insensitive name",
"DROP TABLE myCatalog.mySchema.foo",
statementDDLDropTable(
identifierQualified(
identifierSymbol("myCatalog", Identifier.CaseSensitivity.INSENSITIVE),
listOf(
identifierSymbol("mySchema", Identifier.CaseSensitivity.INSENSITIVE),
identifierSymbol("foo", Identifier.CaseSensitivity.INSENSITIVE),
)
),
)
),
SuccessTestCase(
"DROP TABLE with qualified name with mixed case sensitivity",
"DROP TABLE myCatalog.\"mySchema\".foo",
statementDDLDropTable(
identifierQualified(
identifierSymbol("myCatalog", Identifier.CaseSensitivity.INSENSITIVE),
listOf(
identifierSymbol("mySchema", Identifier.CaseSensitivity.SENSITIVE),
identifierSymbol("foo", Identifier.CaseSensitivity.INSENSITIVE),
)
),
)
),
)

override fun provideArguments(context: ExtensionContext?): Stream<out Arguments> =
(createTableTests + dropTableTests).map { Arguments.of(it) }.stream()
}

private fun assertExpression(input: String, expected: AstNode) {
val result = parser.parse(input)
val actual = result.root
assertEquals(expected, actual)
}
}