Skip to content

Commit abfc58d

Browse files
yliuuuualancai98
andauthored
Support parsing for attribute and tuple level constraint (#1442)
* Parsing for attribute and tuple constraint Co-authored-by: Alan Cai <[email protected]>
1 parent 1bb5577 commit abfc58d

File tree

7 files changed

+485
-123
lines changed

7 files changed

+485
-123
lines changed

partiql-ast/src/main/kotlin/org/partiql/ast/helpers/ToLegacyAst.kt

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ import com.amazon.ionelement.api.ionString
1616
import com.amazon.ionelement.api.ionSymbol
1717
import com.amazon.ionelement.api.metaContainerOf
1818
import org.partiql.ast.AstNode
19+
import org.partiql.ast.Constraint
1920
import org.partiql.ast.DatetimeField
21+
import org.partiql.ast.DdlOp
2022
import org.partiql.ast.Exclude
2123
import org.partiql.ast.Expr
2224
import org.partiql.ast.From
@@ -121,24 +123,23 @@ private class AstTranslator(val metas: Map<String, MetaContainer>) : AstBaseVisi
121123
domain(statement, type, format, metas)
122124
}
123125

124-
override fun visitStatementDDL(node: Statement.DDL, ctx: Ctx) = super.visit(node, ctx) as PartiqlAst.Statement.Ddl
126+
override fun visitStatementDDL(node: Statement.DDL, ctx: Ctx) = when (val op = node.op) {
127+
is DdlOp.CreateIndex -> visitDdlOpCreateIndex(op, ctx)
128+
is DdlOp.CreateTable -> visitDdlOpCreateTable(op, ctx)
129+
is DdlOp.DropIndex -> visitDdlOpDropIndex(op, ctx)
130+
is DdlOp.DropTable -> visitDdlOpDropTable(op, ctx)
131+
}
125132

126-
override fun visitStatementDDLCreateTable(
127-
node: Statement.DDL.CreateTable,
128-
ctx: Ctx,
129-
) = translate(node) { metas ->
133+
override fun visitDdlOpCreateTable(node: DdlOp.CreateTable, ctx: Ctx) = translate(node) { metas ->
130134
if (node.name !is Identifier.Symbol) {
131135
error("The legacy AST does not support qualified identifiers as table names")
132136
}
133-
val tableName = (node.name as Identifier.Symbol).symbol
137+
val tableName = node.name.symbol
134138
val def = node.definition?.let { visitTableDefinition(it, ctx) }
135139
ddl(createTable(tableName, def), metas)
136140
}
137141

138-
override fun visitStatementDDLCreateIndex(
139-
node: Statement.DDL.CreateIndex,
140-
ctx: Ctx,
141-
) = translate(node) { metas ->
142+
override fun visitDdlOpCreateIndex(node: DdlOp.CreateIndex, ctx: Ctx) = translate(node) { metas ->
142143
if (node.index != null) {
143144
error("The legacy AST does not support index names")
144145
}
@@ -150,7 +151,7 @@ private class AstTranslator(val metas: Map<String, MetaContainer>) : AstBaseVisi
150151
ddl(createIndex(tableName, fields), metas)
151152
}
152153

153-
override fun visitStatementDDLDropTable(node: Statement.DDL.DropTable, ctx: Ctx) = translate(node) { metas ->
154+
override fun visitDdlOpDropTable(node: DdlOp.DropTable, ctx: Ctx) = translate(node) { metas ->
154155
if (node.table !is Identifier.Symbol) {
155156
error("The legacy AST does not support qualified identifiers as table names")
156157
}
@@ -159,7 +160,7 @@ private class AstTranslator(val metas: Map<String, MetaContainer>) : AstBaseVisi
159160
ddl(dropTable(tableName), metas)
160161
}
161162

162-
override fun visitStatementDDLDropIndex(node: Statement.DDL.DropIndex, ctx: Ctx) = translate(node) { metas ->
163+
override fun visitDdlOpDropIndex(node: DdlOp.DropIndex, ctx: Ctx) = translate(node) { metas ->
163164
if (node.index !is Identifier.Symbol) {
164165
error("The legacy AST does not support qualified identifiers as index names")
165166
}
@@ -174,28 +175,28 @@ private class AstTranslator(val metas: Map<String, MetaContainer>) : AstBaseVisi
174175
}
175176

176177
override fun visitTableDefinition(node: TableDefinition, ctx: Ctx) = translate(node) { metas ->
177-
val parts = node.columns.translate<PartiqlAst.TableDefPart>(ctx)
178+
val parts = node.attributes.translate<PartiqlAst.TableDefPart>(ctx)
179+
if (node.constraints.isNotEmpty()) {
180+
error("The legacy AST does not support table level constraint declaration")
181+
}
178182
tableDef(parts, metas)
179183
}
180184

181-
override fun visitTableDefinitionColumn(node: TableDefinition.Column, ctx: Ctx) = translate(node) { metas ->
182-
val name = node.name
185+
override fun visitTableDefinitionAttribute(node: TableDefinition.Attribute, ctx: Ctx) = translate(node) { metas ->
186+
// Legacy AST treat table name as a case-sensitive string
187+
val name = node.name.symbol
183188
val type = visitType(node.type, ctx)
184189
val constraints = node.constraints.translate<PartiqlAst.ColumnConstraint>(ctx)
185190
columnDeclaration(name, type, constraints, metas)
186191
}
187192

188-
override fun visitTableDefinitionColumnConstraint(
189-
node: TableDefinition.Column.Constraint,
190-
ctx: Ctx,
191-
) = translate(node) { metas ->
193+
override fun visitConstraint(node: Constraint, ctx: Ctx) = translate(node) {
192194
val name = node.name
193-
val def = when (node.body) {
194-
is TableDefinition.Column.Constraint.Body.Check -> {
195-
throw IllegalArgumentException("PIG AST does not support CHECK (<expr>) constraint")
196-
}
197-
is TableDefinition.Column.Constraint.Body.NotNull -> columnNotnull()
198-
is TableDefinition.Column.Constraint.Body.Nullable -> columnNull()
195+
val def = when (node.definition) {
196+
is Constraint.Definition.Check -> throw IllegalArgumentException("PIG AST does not support CHECK (<expr>) constraint")
197+
is Constraint.Definition.NotNull -> columnNotnull()
198+
is Constraint.Definition.Nullable -> columnNull()
199+
is Constraint.Definition.Unique -> throw IllegalArgumentException("PIG AST does not support Unique/Primary Key constraint")
199200
}
200201
columnConstraint(name, def, metas)
201202
}

partiql-ast/src/main/resources/partiql_ast.ion

Lines changed: 48 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -106,32 +106,9 @@ statement::[
106106
],
107107

108108
// Data Definition Language
109-
d_d_l::[
110-
111-
// CREATE TABLE <identifier> [<table_def>]
112-
create_table::{
113-
name: identifier,
114-
definition: optional::table_definition,
115-
},
116-
117-
// CREATE INDEX [<identifier>] ON <identifier> (<path> [, <path>]...)
118-
create_index::{
119-
index: optional::identifier,
120-
table: identifier,
121-
fields: list::[path],
122-
},
123-
124-
// DROP TABLE <identifier>
125-
drop_table::{
126-
table: identifier,
127-
},
128-
129-
// DROP INDEX <identifier> ON <identifier>
130-
drop_index::{
131-
index: identifier, // <identifier>[0]
132-
table: identifier, // <identifier>[1]
133-
},
134-
],
109+
d_d_l::{
110+
op: ddl_op
111+
},
135112

136113
// EXEC <symbol> [<expr>.*]
137114
exec::{
@@ -151,6 +128,32 @@ statement::[
151128
},
152129
]
153130

131+
ddl_op::[
132+
// CREATE TABLE <identifier> [<table_def>]
133+
create_table::{
134+
name: identifier,
135+
definition: optional::table_definition,
136+
},
137+
138+
// CREATE INDEX [<identifier>] ON <identifier> (<path> [, <path>]...)
139+
create_index::{
140+
index: optional::identifier,
141+
table: identifier,
142+
fields: list::[path],
143+
},
144+
145+
// DROP TABLE <identifier>
146+
drop_table::{
147+
table: identifier,
148+
},
149+
150+
// DROP INDEX <identifier> ON <identifier>
151+
drop_index::{
152+
index: identifier, // <identifier>[0]
153+
table: identifier, // <identifier>[1]
154+
},
155+
]
156+
154157
// PartiQL Type AST nodes
155158
//
156159
// Several of these are the same "type", but have various syntax rules we wish to capture.
@@ -781,27 +784,29 @@ returning::{
781784
],
782785
}
783786

784-
// `<column_name> <type> <column_constraint>*`
785-
// `( CONSTRAINT <column_constraint_name> )? <column_constraint_def>`
786787
table_definition::{
787-
columns: list::[column],
788+
attributes: list::[attribute],
789+
// table level constraints
790+
constraints: list::[constraint],
788791
_: [
789-
column::{
790-
name: string,
792+
attribute::{
793+
name: '.identifier.symbol',
791794
type: '.type',
792795
constraints: list::[constraint],
793-
_: [
794-
// TODO improve modeling language to avoid these wrapped unions
795-
// Also, prefer not to nest more than twice
796-
constraint::{
797-
name: optional::string,
798-
body: [
799-
nullable::{},
800-
not_null::{},
801-
check::{ expr: expr },
802-
],
803-
},
804-
],
796+
}
797+
],
798+
}
799+
800+
constraint::{
801+
name: optional::string,
802+
definition: [
803+
nullable::{},
804+
not_null::{},
805+
check::{ expr: expr },
806+
unique::{
807+
// for attribute level constraint, we can set this attribute to null
808+
attributes: optional::list::['.identifier.symbol'],
809+
is_primary_key: bool,
805810
},
806811
],
807812
}

partiql-lang/src/main/kotlin/org/partiql/lang/syntax/impl/PartiQLPigVisitor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ internal class PartiQLPigVisitor(
268268
}
269269

270270
override fun visitColumnConstraint(ctx: PartiQLParser.ColumnConstraintContext) = PartiqlAst.build {
271-
val name = ctx.columnConstraintName()?.let { visitSymbolPrimitive(it.symbolPrimitive()).name.text }
271+
val name = ctx.constraintName()?.let { visitSymbolPrimitive(it.symbolPrimitive()).name.text }
272272
val def = visit(ctx.columnConstraintDef()) as PartiqlAst.ColumnConstraintDef
273273
columnConstraint(name, def)
274274
}

partiql-lang/src/test/kotlin/org/partiql/lang/syntax/PartiQLParserDDLTest.kt

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,47 @@ internal class PartiQLParserDDLTest : PartiQLParserTestBase() {
3535
query = "DROP Table foo.bar",
3636
code = ErrorCode.PARSE_UNEXPECTED_TOKEN,
3737
context = mapOf(),
38-
)
38+
),
39+
ParserErrorTestCase(
40+
description = "PIG Parser does not support Unique Constraints in CREATE TABLE",
41+
query = """
42+
CREATE TABLE tbl (
43+
a INT2 UNIQUE
44+
)
45+
""".trimIndent(),
46+
code = ErrorCode.PARSE_UNEXPECTED_TOKEN,
47+
context = mapOf(),
48+
),
49+
ParserErrorTestCase(
50+
description = "PIG Parser does not support Primary Key Constraint in CREATE TABLE",
51+
query = """
52+
CREATE TABLE tbl (
53+
a INT2 PRIMARY KEY
54+
)
55+
""".trimIndent(),
56+
code = ErrorCode.PARSE_UNEXPECTED_TOKEN,
57+
context = mapOf(),
58+
),
59+
ParserErrorTestCase(
60+
description = "PIG Parser does not support CHECK Constraint in CREATE TABLE",
61+
query = """
62+
CREATE TABLE tbl (
63+
a INT2 CHECK(a > 0)
64+
)
65+
""".trimIndent(),
66+
code = ErrorCode.PARSE_UNEXPECTED_TOKEN,
67+
context = mapOf(),
68+
),
69+
ParserErrorTestCase(
70+
description = "PIG Parser does not support table constraint in CREATE TABLE",
71+
query = """
72+
CREATE TABLE tbl (
73+
check (a > 0)
74+
)
75+
""".trimIndent(),
76+
code = ErrorCode.PARSE_UNEXPECTED_TOKEN,
77+
context = mapOf(),
78+
),
3979
)
4080
}
4181
}

partiql-parser/src/main/antlr/PartiQL.g4

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,8 @@ execCommand
7575
qualifiedName : (qualifier+=symbolPrimitive PERIOD)* name=symbolPrimitive;
7676

7777
tableName : symbolPrimitive;
78-
tableConstraintName : symbolPrimitive;
7978
columnName : symbolPrimitive;
80-
columnConstraintName : symbolPrimitive;
79+
constraintName : symbolPrimitive;
8180

8281
ddl
8382
: createCommand
@@ -100,17 +99,43 @@ tableDef
10099

101100
tableDefPart
102101
: columnName type columnConstraint* # ColumnDeclaration
102+
| ( CONSTRAINT constraintName )? tableConstraintDef # TableConstrDeclaration
103+
;
104+
105+
tableConstraintDef
106+
: checkConstraintDef # TableConstrCheck
107+
| uniqueConstraintDef # TableConstrUnique
103108
;
104109

105110
columnConstraint
106-
: ( CONSTRAINT columnConstraintName )? columnConstraintDef
111+
: ( CONSTRAINT constraintName )? columnConstraintDef
107112
;
108113

109114
columnConstraintDef
110-
: NOT NULL # ColConstrNotNull
111-
| NULL # ColConstrNull
115+
: NOT NULL # ColConstrNotNull
116+
| NULL # ColConstrNull
117+
| uniqueSpec # ColConstrUnique
118+
| checkConstraintDef # ColConstrCheck
119+
;
120+
121+
checkConstraintDef
122+
: CHECK PAREN_LEFT searchCondition PAREN_RIGHT
123+
;
124+
125+
uniqueSpec
126+
: PRIMARY KEY # PrimaryKey
127+
| UNIQUE # Unique
128+
;
129+
130+
uniqueConstraintDef
131+
: uniqueSpec PAREN_LEFT columnName (COMMA columnName)* PAREN_RIGHT
112132
;
113133

134+
// <search condition> ::= <boolean term> | <search condition> OR <boolean term>
135+
// we cannot do exactly that for the way expression precedence is structured in the grammar file.
136+
// but we at least can eliminate SFW query here.
137+
searchCondition : exprOr;
138+
114139
/**
115140
*
116141
* DATA MANIPULATION LANGUAGE (DML)
@@ -192,9 +217,6 @@ conflictTarget
192217
: PAREN_LEFT symbolPrimitive (COMMA symbolPrimitive)* PAREN_RIGHT
193218
| ON CONSTRAINT constraintName;
194219

195-
constraintName
196-
: symbolPrimitive;
197-
198220
conflictAction
199221
: DO NOTHING
200222
| DO REPLACE doReplace

0 commit comments

Comments
 (0)