diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt index 74e5402e0..cf5c03e82 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/ir/Nodes.kt @@ -347,7 +347,7 @@ internal sealed class Statement : PlanNode() { @JvmField public val name: String, @JvmField - public val `value`: PartiQLValue, + public val `value`: String, ) : PlanNode() { public override val children: List = emptyList() diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/normalize/NormalizeFromSource.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/normalize/NormalizeFromSource.kt index cabb298fb..225ba6864 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/normalize/NormalizeFromSource.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/normalize/NormalizeFromSource.kt @@ -26,7 +26,6 @@ import org.partiql.ast.FromType import org.partiql.ast.Query import org.partiql.ast.QueryBody import org.partiql.ast.Statement -import org.partiql.ast.ddl.Ddl import org.partiql.ast.expr.Expr import org.partiql.planner.internal.helpers.toBinder @@ -35,12 +34,11 @@ import org.partiql.planner.internal.helpers.toBinder */ internal object NormalizeFromSource : AstPass { - override fun apply(statement: Statement): Statement = when(statement) { + override fun apply(statement: Statement): Statement = when (statement) { is Query -> statement.accept(Visitor, 0) as Statement else -> statement } - private object Visitor : AstRewriter() { // Each SFW starts the ctx count again. diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/normalize/NormalizeGroupBy.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/normalize/NormalizeGroupBy.kt index 63c8a1d0f..4228eb604 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/normalize/NormalizeGroupBy.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/normalize/NormalizeGroupBy.kt @@ -29,7 +29,7 @@ import org.partiql.planner.internal.helpers.toBinder */ internal object NormalizeGroupBy : AstPass { - override fun apply(statement: Statement) = when(statement) { + override fun apply(statement: Statement) = when (statement) { is Query -> Visitor.visitStatement(statement, 0) as Statement else -> statement } diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/AstToPlan.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/AstToPlan.kt index a9f06dfc0..8797a56c5 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/AstToPlan.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/AstToPlan.kt @@ -21,7 +21,6 @@ import org.partiql.ast.AstVisitor import org.partiql.ast.DataType import org.partiql.ast.Query import org.partiql.ast.ddl.CreateTable -import org.partiql.ast.ddl.Ddl import org.partiql.ast.expr.ExprQuerySet import org.partiql.errors.TypeCheckException import org.partiql.planner.internal.Env diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/DdlConverter.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/DdlConverter.kt index 85e8144eb..4e5dcf225 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/DdlConverter.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/DdlConverter.kt @@ -10,6 +10,8 @@ import org.partiql.ast.ddl.AttributeConstraint.Unique import org.partiql.ast.ddl.ColumnDefinition import org.partiql.ast.ddl.CreateTable import org.partiql.ast.ddl.Ddl +import org.partiql.ast.ddl.KeyValue +import org.partiql.ast.ddl.PartitionBy import org.partiql.ast.ddl.TableConstraint import org.partiql.ast.sql.sql import org.partiql.planner.internal.DdlField @@ -20,6 +22,8 @@ import org.partiql.planner.internal.ir.statementDDL import org.partiql.planner.internal.ir.statementDDLAttribute import org.partiql.planner.internal.ir.statementDDLCommandCreateTable import org.partiql.planner.internal.ir.statementDDLConstraintCheck +import org.partiql.planner.internal.ir.statementDDLPartitionByAttrList +import org.partiql.planner.internal.ir.statementDDLTableProperty import org.partiql.planner.internal.transforms.AstToPlan.convert import org.partiql.planner.internal.transforms.AstToPlan.visitType import org.partiql.planner.internal.typer.CompilerType @@ -75,10 +79,10 @@ internal object DdlConverter { val pk = node.constraints.filterIsInstance() .filter { it.isPrimaryKey } .let { - when(it.size) { + when (it.size) { 0 -> emptyList() 1 -> it.first().columns.map { convert(it) } - else -> throw IllegalArgumentException("multiple PK") + else -> throw IllegalArgumentException("multiple PK") } } @@ -88,18 +92,21 @@ internal object DdlConverter { acc + constr.columns.map { convert(it) } } + val partitionBy = node.partitionBy?.let { visitPartitionBy(it, ctx) } + val tableProperty = node.tableProperties.map { visitKeyValue(it, ctx) } + return statementDDLCommandCreateTable( tableName, attributes, emptyList(), - null, - emptyList(), + partitionBy, + tableProperty, pk, unique ) } - // !!! The planning stage ignores constraint name for now + // !!! The planning stage ignores the constraint name for now override fun visitColumnDefinition(node: ColumnDefinition, ctx: Env): Statement.DDL.Attribute { val name = convert(node.name) val type = visitType(node.dataType, ctx) @@ -156,6 +163,14 @@ internal object DdlConverter { node.searchCondition.sql() ) + override fun visitPartitionBy(node: PartitionBy, ctx: Env): Statement.DDL.PartitionBy { + return statementDDLPartitionByAttrList(node.columns.map { convert(it) }) + } + + override fun visitKeyValue(node: KeyValue, ctx: Env): Statement.DDL.TableProperty { + return statementDDLTableProperty(node.key, node.value) + } + private fun visitType(node: DataType, ctx: Env): CompilerType { // Struct requires special process in DDL return if (node.code() == DataType.STRUCT) { diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/InlineCheckConstraintExtractor.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/InlineCheckConstraintExtractor.kt index a7517d27d..b845641cf 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/InlineCheckConstraintExtractor.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/InlineCheckConstraintExtractor.kt @@ -28,7 +28,9 @@ internal object InlineCheckConstraintExtractor : PlanBaseVisitor override fun visitStatementDDLConstraintCheck(node: Statement.DDL.Constraint.Check, ctx: PShape): PShape { val lowered = visitRex(node.expression, ctx) // No lowering happened, then wrap the PShape with a generic Constraint trait - return if (lowered.equals(ctx)) { ConstraintTrait(ctx, node.sql) } else lowered + return if (lowered == ctx) { + ConstraintTrait(ctx, node.sql) + } else lowered } override fun visitRex(node: Rex, ctx: PShape): PShape { @@ -36,7 +38,7 @@ internal object InlineCheckConstraintExtractor : PlanBaseVisitor } override fun visitRexOpCallStatic(node: Rex.Op.Call.Static, ctx: PShape): PShape { - return when(node.fn.name) { + return when (node.fn.name) { "gte" -> getRhsAsNumericOrNull(node.args[1])?.let { RangeTrait(ctx, it, null) } ?: ctx diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt index afe86caad..539e38efc 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/transforms/PlanTransform.kt @@ -54,7 +54,7 @@ internal class PlanTransform(private val flags: Set) { */ fun transform(internal: IPlan, listener: PErrorListener): Plan { val signal = flags.contains(PlannerFlag.SIGNAL_MODE) - when(internal.statement) { + when (internal.statement) { is Statement.DDL -> { val query = internal.statement val visitor = DDLVisitor(listener, signal) @@ -94,19 +94,42 @@ internal class PlanTransform(private val flags: Set) { override fun visitStatementDDLCommandCreateTable(node: Statement.DDL.Command.CreateTable, ctx: Unit): Action.CreateTable { val fields = node.attributes.map { val shape = visitStatementDDLAttribute(it, ctx) - Field.of(it.name.getIdentifier().getText(), shape) + if (node.primaryKey.contains(it.name)) { + Field.of(it.name.getIdentifier().getText(), NotNullTrait(shape)) + } else { + Field.of(it.name.getIdentifier().getText(), shape) + } } val row = PShape(PType.row(fields)) - .let { - if (node.primaryKey.isNotEmpty()) - PrimaryKeyTrait(it, node.primaryKey.map { it.getIdentifier().getText() }) - else it - }.let { - if (node.unique.isNotEmpty()) - UniqueTrait(it, node.unique.map { it.getIdentifier().getText() }) - else it + val schema = PShape(PType.bag(row)).let { + if (node.primaryKey.isNotEmpty()) + PrimaryKeyTrait(it, node.primaryKey.map { it.getIdentifier().getText() }) + else it + }.let { + if (node.unique.isNotEmpty()) + UniqueTrait(it, node.unique.map { it.getIdentifier().getText() }) + else it + }.let { it -> + var shape = it + if (node.tableProperties.isNotEmpty()) { + node.tableProperties.forEach { prop -> + shape = MetadataTrait(shape, prop.name, prop.value) + } + } + shape + }.let { it -> + when (val partition = node.partitionBy) { + is Statement.DDL.PartitionBy.AttrList -> { + val names = buildString { + append("[") + append(partition.attrs.joinToString(",") { it.getIdentifier().getText() }) + append("]") + } + MetadataTrait(it, "partition", names) + } + null -> it } - val schema = PShape(PType.bag(row)) + } return Action.CreateTable { Table.builder() .name(node.name.getIdentifier().getText()) @@ -115,12 +138,12 @@ internal class PlanTransform(private val flags: Set) { } } - override fun visitStatementDDLAttribute(node: Statement.DDL.Attribute, ctx: Unit): PType { + override fun visitStatementDDLAttribute(node: Statement.DDL.Attribute, ctx: Unit): PShape { val ddlField = DdlField.fromAttr(node) return visitDdlField(ddlField, ctx) } - private fun visitDdlField(ddlField: DdlField, ctx: Unit): PType { + private fun visitDdlField(ddlField: DdlField, ctx: Unit): PShape { val baseShape = ddlField.type.getDelegate() val fieldReduced = when (baseShape.code()) { PType.ROW -> { @@ -143,10 +166,8 @@ internal class PlanTransform(private val flags: Set) { } ?: constraintReduced return metadataReduced } - } - private class Visitor( private val listener: PErrorListener, private val signal: Boolean, diff --git a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt index d5707a970..48c03c164 100644 --- a/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt +++ b/partiql-planner/src/main/kotlin/org/partiql/planner/internal/typer/PlanTyper.kt @@ -52,6 +52,7 @@ import org.partiql.planner.internal.ir.rexOpStructField import org.partiql.planner.internal.ir.rexOpSubquery import org.partiql.planner.internal.ir.statementDDL import org.partiql.planner.internal.ir.statementDDLCommandCreateTable +import org.partiql.planner.internal.ir.statementDDLPartitionByAttrList import org.partiql.planner.internal.ir.statementQuery import org.partiql.planner.internal.ir.util.PlanRewriter import org.partiql.spi.Context @@ -1285,22 +1286,32 @@ internal class PlanTyper(private val env: Env, config: Context) { isDeclaredAttribute(uniqueAttr, attrs.map { DdlField.fromAttr(it) }) ?: throw IllegalArgumentException("Unresolved ref") } - val tablePrimaryContr = node.primaryKey.map { uniqueAttr -> - isDeclaredAttribute(uniqueAttr, attrs.map { DdlField.fromAttr(it) }) ?: throw IllegalArgumentException("Unresolved ref") + val tablePrimaryContr = node.primaryKey.map { pkAtrr -> + isDeclaredAttribute(pkAtrr, attrs.map { DdlField.fromAttr(it) }) ?: throw IllegalArgumentException("Unresolved ref") } // ALSO: For PK // Thing like PRIMARY KEY (FOO, FOO) will be rejected - if (tablePrimaryContr.toSet().size != tableUniqueContr.size) { + if (tablePrimaryContr.toSet().size != tablePrimaryContr.size) { throw IllegalArgumentException("Attribute appears multiple times in primary key constraint") } val finalPK = tablePrimaryContr + attributePK - val finalUnique = (tableUniqueContr + attributeUnique).toSet().toList() + val finalUnique = (tableUniqueContr + attributeUnique + finalPK).toSet().toList() + + val partitionBy = when (val partitionAttr = node.partitionBy) { + is Statement.DDL.PartitionBy.AttrList -> { + val attrListResolved = partitionAttr.attrs.map { attr -> + isDeclaredAttribute(attr, attrs.map { DdlField.fromAttr(it) }) ?: throw IllegalArgumentException("Unresolved ref") + } + statementDDLPartitionByAttrList(attrListResolved.map { Identifier.of(Identifier.Part.delimited(it)) }) + } + null -> null + } return statementDDLCommandCreateTable( node.name, attrs, node.tblConstraints, - node.partitionBy, + partitionBy, node.tableProperties, finalPK.map { Identifier.of(Identifier.Part.delimited(it)) }, finalUnique.map { Identifier.of(Identifier.Part.delimited(it)) } @@ -1333,6 +1344,7 @@ internal class PlanTyper(private val env: Env, config: Context) { private fun isDeclaredAttribute(identifier: Identifier, declareAttrs: List): String? { declareAttrs.forEach { declared -> if (identifier.matches(declared.name, identifier.getIdentifier().isRegular())) + // Storing as Declared to work around identifier case sensitivity at the moment return declared.name } return null diff --git a/partiql-planner/src/main/resources/partiql_plan_internal.ion b/partiql-planner/src/main/resources/partiql_plan_internal.ion index 44e20027a..d6dc83a2d 100644 --- a/partiql-planner/src/main/resources/partiql_plan_internal.ion +++ b/partiql-planner/src/main/resources/partiql_plan_internal.ion @@ -99,7 +99,7 @@ statement::[ ], table_property::{ name: string, - value: partiql_value, + value: string, }, ] } diff --git a/partiql-planner/src/test/kotlin/org/partiql/planner/ddl/DDLTest.kt b/partiql-planner/src/test/kotlin/org/partiql/planner/ddl/DDLTest.kt index 136e5ae2d..1654ade48 100644 --- a/partiql-planner/src/test/kotlin/org/partiql/planner/ddl/DDLTest.kt +++ b/partiql-planner/src/test/kotlin/org/partiql/planner/ddl/DDLTest.kt @@ -1,22 +1,20 @@ package org.partiql.planner.ddl +import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows import org.partiql.parser.PartiQLParser import org.partiql.plan.Action import org.partiql.plan.Plan import org.partiql.planner.PartiQLPlanner -import org.partiql.planner.internal.DdlField import org.partiql.planner.internal.TestCatalog -import org.partiql.planner.internal.typer.CompilerType -import org.partiql.spi.catalog.Identifier import org.partiql.spi.catalog.Session import org.partiql.spi.catalog.Table +import org.partiql.spi.errors.PErrorException import org.partiql.types.Field import org.partiql.types.PType import org.partiql.types.shape.PShape -import org.partiql.types.shape.trait.PTrait -import kotlin.math.exp -import kotlin.test.assertEquals +import java.math.BigDecimal class DDLTest { @@ -36,32 +34,89 @@ class DDLTest { return planner.plan(statement, session).plan } - internal fun constructBasicField(name: String, type: PShape): DdlField { - return DdlField( - name = Identifier.of(Identifier.Part.regular(name)), - type = CompilerType(type), - isNullable = true, - isOptional = false, - constraints = listOf(), - isPrimaryKey = false, - isUnique = false, - comment = null - ) + private fun tableAssertionAndReturnFields( + table: Table, + tableName: String, + primaryKey: List = emptyList(), + unique: List = emptyList(), + metadata: Map = emptyMap(), + ): List { + Assertions.assertEquals(tableName, table.getName().getName()) + val schema = table.getSchema() as? PShape + ?: throw AssertionError("Expect Schema to be a PShape") + schema as? PShape ?: throw AssertionError("Expect Schema to be a PShape") + Assertions.assertEquals(PType.BAG, schema.code()) { + "Expect Schema to be a Bag Type" + } + if (primaryKey.isNotEmpty()) { + Assertions.assertTrue(primaryKey.containsAll(schema.primaryKey())) + Assertions.assertTrue(schema.primaryKey().containsAll(primaryKey)) + } + if (unique.isNotEmpty()) { + Assertions.assertTrue(unique.containsAll(schema.unique())) + Assertions.assertTrue(schema.unique().containsAll(unique)) + } + val struct = schema.typeParameter + Assertions.assertEquals(PType.ROW, struct.code()) + return struct.fields.toList() + } + + private fun assertField( + field: Field, + fieldName: String, + code: Int, + isNullable: Boolean, + isOptional: Boolean, + maxValue: Number? = null, + minValue: Number? = null, + precision: Int? = null, + scale: Int? = null, + length: Int? = null, + meta: Map = emptyMap(), + ) { + Assertions.assertEquals(fieldName, field.name) + val shape = field.type as PShape + Assertions.assertEquals(code, shape.code()) + Assertions.assertEquals(isOptional, shape.isOptional) + Assertions.assertEquals(isNullable, shape.isNullable) + if (maxValue != null) { + Assertions.assertTrue(BigDecimal(maxValue.toString()).compareTo(BigDecimal(shape.maxValue().toString())) == 0) { + """ + Expected maxValue to be $maxValue but was: ${shape.maxValue()} + """.trimIndent() + } + } + if (minValue != null) { + Assertions.assertTrue(BigDecimal(minValue.toString()).compareTo(BigDecimal(shape.minValue().toString())) == 0) { + """ + Expected minValue to be $minValue but was: ${shape.minValue()} + """.trimIndent() + } + } + if (precision != null) { + Assertions.assertEquals(precision, shape.precision) + } + if (scale != null) { + Assertions.assertEquals(scale, shape.scale) + } + if (length != null) { + Assertions.assertEquals(length, shape.length) + } + Assertions.assertEquals(meta, shape.meta()) } - internal val int2_field_basic = constructBasicField("int2_attr", PShape(PType.smallint())) - internal val int4_field_basic = constructBasicField("int4_attr", PShape(PType.integer())) - internal val int8_field_baisc = constructBasicField("int8_attr", PShape(PType.bigint())) - internal val decimal_field_basic = constructBasicField("decimal_attr", PShape(PType.decimal(10, 5))) - internal val float_field_basic = constructBasicField("float_attr", PShape(PType.real())) - internal val double_field_basic = constructBasicField("double_attr", PShape(PType.doublePrecision())) - internal val char_field_basic = constructBasicField("char_attr", PShape(PType.character(1))) - internal val varchar_field_basic = constructBasicField("varchar_attr", PShape(PType.varchar(255))) - internal val timestamp_field_basic = constructBasicField("timestamp_attr", PShape(PType.timestamp(2))) - internal val date_field_basic = constructBasicField("date_attr", PShape(PType.date())) + private fun assertInt2NullableOptional(field: Field, name: String, nullable: Boolean, optional: Boolean = true) { + assertField( + field, + name, + PType.SMALLINT, + nullable, optional, + Short.MAX_VALUE, Short.MIN_VALUE + ) + } @Test - fun ddlTest() { + fun createTableBasicTest() { val ddl = """ CREATE TABLE foo ( int2_attr INT2, @@ -72,36 +127,364 @@ class DDLTest { char_attr CHAR(1), varchar_attr VARCHAR(255), timestamp_attr TIMESTAMP(2), - data_attr DATE + date_attr DATE + ); + """.trimIndent() + val plan = plan(ddl) + val createTable = plan.action as Action.CreateTable + val fields = tableAssertionAndReturnFields( + createTable.table, + "foo" + ) + val int2_attr = fields[0] + assertField( + int2_attr, + "int2_attr", + PType.SMALLINT, + true, false, + Short.MAX_VALUE, Short.MIN_VALUE + ) + val int4_attr = fields[1] + assertField( + int4_attr, + "int4_attr", + PType.INTEGER, + true, false, + Int.MAX_VALUE, Int.MIN_VALUE + ) + val int8_attr = fields[2] + assertField( + int8_attr, + "int8_attr", + PType.BIGINT, + true, false, + Long.MAX_VALUE, Long.MIN_VALUE + ) + val decimal_attr = fields[3] + assertField( + decimal_attr, + "decimal_attr", + PType.DECIMAL, + true, false, + precision = 10, scale = 5, + ) + val float_attr = fields[4] + assertField( + float_attr, + "float_attr", + PType.REAL, + true, false, + ) + val char_attr = fields[5] + assertField( + char_attr, + "char_attr", + PType.CHAR, + true, false, + length = 1 + ) + val varchar_attr = fields[6] + assertField( + varchar_attr, + "varchar_attr", + PType.VARCHAR, + true, false, + length = 255 + ) + val timestamp_attr = fields[7] + assertField( + timestamp_attr, + "timestamp_attr", + PType.TIMESTAMP, + true, false, + precision = 2 + ) + val date_attr = fields[8] + assertField( + date_attr, + "date_attr", + PType.DATE, + true, false, + ) + } + @Test + fun createTableNotNullTest() { + val ddl = """ + CREATE TABLE foo ( + attr1 INT2 NOT NULL, + attr2 INT2 NOT NULL NULL, + attr3 INT2 NOT NULL NULL NOT NULL, + attr4 INT2 NULL NOT NULL + ); + """.trimIndent() + + val plan = plan(ddl) + val createTable = plan.action as Action.CreateTable + val fields = tableAssertionAndReturnFields( + createTable.table, + "foo" + ) + + val attr1 = fields[0] + assertInt2NullableOptional(attr1, "attr1", false) + val attr2 = fields[1] + assertInt2NullableOptional(attr2, "attr2", true) + val attr3 = fields[2] + assertInt2NullableOptional(attr3, "attr3", false) + val attr4 = fields[3] + assertInt2NullableOptional(attr4, "attr4", false) + } + + @Test + fun createTableOptionalTest() { + val ddl = """ + CREATE TABLE foo ( + attr1 OPTIONAL INT2, + attr2 OPTIONAL INT2 NOT NULL ); """.trimIndent() val plan = plan(ddl) - val action = plan.action as Action.CreateTable - val table = action.table - val expectedSchema = PShape(PShape.bag( - PShape.row( - int2_field_basic, - int4_field_basic, - int8_field_baisc, - decimal_field_basic, - float_field_basic, - double_field_basic, - char_field_basic, - varchar_field_basic, - timestamp_field_basic, - date_field_basic, - ) - )) - val expected = Table - .builder() - .name("foo") - .schema(expectedSchema) - .build() - - println(expected.getSchema()) - println(table.getSchema()) - val tableSchema = table.getSchema() - val assertion = tableSchema == expectedSchema - println(assertion) - } -} \ No newline at end of file + val createTable = plan.action as Action.CreateTable + val fields = tableAssertionAndReturnFields( + createTable.table, + "foo" + ) + + val attr1 = fields[0] + assertInt2NullableOptional(attr1, "attr1", true, true) + val attr2 = fields[1] + assertInt2NullableOptional(attr2, "attr2", false, true) + } + + @Test + fun createTableCommentTest() { + val ddl = """ + CREATE TABLE foo ( + attr1 INT2 COMMENT 'attr1' + ); + """.trimIndent() + val plan = plan(ddl) + val createTable = plan.action as Action.CreateTable + val fields = tableAssertionAndReturnFields( + createTable.table, + "foo" + ) + + val attr1 = fields[0] + assertField( + attr1, + "attr1", + PType.SMALLINT, + true, false, + Short.MAX_VALUE, Short.MIN_VALUE, + meta = mapOf("comment" to "attr1") + ) + } + + @Test + fun createTableCheckConstraintLoweredTest() { + val ddl = """ + CREATE TABLE foo ( + attr1 INT2 CHECK(attr1 >= 0), + attr2 INT2 CHECK(attr2 >=0 and attr2 <= 10), + attr3 INT2 CHECK(attr3 >= 0) CHECK (attr3 <= 10), + -- Leading to a empty value set + attr4 INT2 CHECK(attr4 >= 1000000) + ); + """.trimIndent() + val plan = plan(ddl) + val createTable = plan.action as Action.CreateTable + val fields = tableAssertionAndReturnFields( + createTable.table, + "foo" + ) + + val attr1 = fields[0] + assertField( + attr1, + "attr1", + PType.SMALLINT, + true, false, + Short.MAX_VALUE, 0, + ) + + val attr2 = fields[1] + assertField( + attr2, + "attr2", + PType.SMALLINT, + true, false, + 10, 0, + ) + + val attr3 = fields[2] + assertField( + attr3, + "attr3", + PType.SMALLINT, + true, false, + 10, 0, + ) + + val attr4 = fields[3] + assertField( + attr4, + "attr4", + PType.SMALLINT, + true, false, + Short.MAX_VALUE, 1000000, + ) + } + + @Test + fun createTablePrimaryKeyInlineTest() { + val ddl = """ + CREATE TABLE foo ( + attr1 INT2 PRIMARY KEY + ); + """.trimIndent() + val plan = plan(ddl) + val createTable = plan.action as Action.CreateTable + val fields = tableAssertionAndReturnFields( + createTable.table, + "foo", + listOf("attr1") + ) + + // Side effect: Nullable is false + val attr1 = fields[0] + assertField( + attr1, + "attr1", + PType.SMALLINT, + false, false, + Short.MAX_VALUE, Short.MIN_VALUE, + ) + } + + @Test + fun createTablePrimaryKeyTableTest() { + val ddl = """ + CREATE TABLE foo ( + attr1 INT2, + -- case insensitive + PRIMARY KEY (ATTR1) + ); + """.trimIndent() + val plan = plan(ddl) + val createTable = plan.action as Action.CreateTable + val fields = tableAssertionAndReturnFields( + createTable.table, + "foo", + listOf("attr1") + ) + + // Side effect: Nullable is false + val attr1 = fields[0] + assertField( + attr1, + "attr1", + PType.SMALLINT, + false, false, + Short.MAX_VALUE, Short.MIN_VALUE, + ) + } + + @Test + fun createTableUniqueKey() { + val ddl = """ + CREATE TABLE foo ( + attr1 INT2 NOT NULL, + attr2 INT2 UNIQUE, + attr3 INT2 PRIMARY KEY, + attr4 INT2, + -- Duplicated declaration + UNIQUE (ATTR2), + UNIQUE (attr1), + UNIQUE (attr1, attr4) + ); + """.trimIndent() + val plan = plan(ddl) + val createTable = plan.action as Action.CreateTable + tableAssertionAndReturnFields( + createTable.table, + "foo", + listOf("attr3"), + // Side effect: attr3 is primary key, therefore it is unique + listOf("attr1", "attr2", "attr3", "attr4") + ) + } + + @Test + fun createTableMetadata() { + val ddl = """ + CREATE TABLE foo ( + attr1 INT2 + ) + TBLPROPERTIES('key' = 'value') + PARTITION BY (attr1); + """.trimIndent() + val plan = plan(ddl) + val createTable = plan.action as Action.CreateTable + tableAssertionAndReturnFields( + createTable.table, + "foo", + metadata = mapOf("key" to "value", "partition" to "[attr1]") + ) + } + + @Test + fun negative_createTable_primaryKey_1() { + val ddl = """ + CREATE TABLE foo ( + attr1 INT2 PRIMARY KEY, + attr2 INT2 PRIMARY KEY, + ); + """.trimIndent() + assertThrows { + plan(ddl) + } + } + + @Test + fun negative_createTable_primaryKey_2() { + val ddl = """ + CREATE TABLE foo ( + attr1 INT2 PRIMARY KEY, + attr2 INT2, + PRIMARY KEY(attr2) + ); + """.trimIndent() + assertThrows { + plan(ddl) + } + } + + @Test + fun negative_createTable_primaryKey_3() { + val ddl = """ + CREATE TABLE foo ( + attr1 INT2, + attr2 INT2, + PRIMARY KEY(attr3) + ); + """.trimIndent() + assertThrows { + plan(ddl) + } + } + + @Test + fun negative_createTable_primaryKey_4() { + val ddl = """ + CREATE TABLE foo ( + "attr1" INT2, + attr2 INT2, + PRIMARY KEY("ATTR1") + ); + """.trimIndent() + assertThrows { + plan(ddl) + } + } +} diff --git a/partiql-types/src/main/java/org/partiql/types/shape/PShape.java b/partiql-types/src/main/java/org/partiql/types/shape/PShape.java index 5553222e9..55c38945c 100644 --- a/partiql-types/src/main/java/org/partiql/types/shape/PShape.java +++ b/partiql-types/src/main/java/org/partiql/types/shape/PShape.java @@ -4,10 +4,17 @@ import org.jetbrains.annotations.NotNull; import org.partiql.types.Field; import org.partiql.types.PType; +import org.partiql.types.shape.trait.UniqueTrait; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.Map; +import java.util.Objects; +/** + * TODO: Improve API Economic. + */ public class PShape extends PType { private final PType type; @@ -67,9 +74,16 @@ public boolean isOptional() { } public Map meta() { - return null; + return new HashMap<>(); } + public Collection primaryKey() { + return new ArrayList<>(); + } + + public Collection unique() { + return new ArrayList<>(); + } @Override public @NotNull Collection getFields() throws UnsupportedOperationException { @@ -107,11 +121,14 @@ public int getScale() throws UnsupportedOperationException { } @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) return false; - return type.equals(o); + // TODO: Revisit Equals and hasCode function + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) return false; + PShape that = (PShape) obj; + return Objects.equals(type, that.type); } + // TODO: Revisit Equals and hasCode function @Override public int hashCode() { return type.hashCode(); diff --git a/partiql-types/src/main/java/org/partiql/types/shape/trait/ConstraintTrait.java b/partiql-types/src/main/java/org/partiql/types/shape/trait/ConstraintTrait.java index baba294ff..559675569 100644 --- a/partiql-types/src/main/java/org/partiql/types/shape/trait/ConstraintTrait.java +++ b/partiql-types/src/main/java/org/partiql/types/shape/trait/ConstraintTrait.java @@ -4,6 +4,11 @@ import java.util.Objects; +/** + * TODO: Improve API Economic. + *

+ * TODO: Equals and HashCode. + */ public class ConstraintTrait extends PTrait { private final String expression; @@ -13,15 +18,17 @@ public ConstraintTrait(PShape shape, String expression) { } @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) return false; - ConstraintTrait that = (ConstraintTrait) o; + // TODO: Revisit Equals and hasCode function + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) return false; + ConstraintTrait that = (ConstraintTrait) obj; if (!Objects.equals(expression, that.expression)) return false; - return shape.equals(that.shape); + return Objects.equals(shape, that.shape); } + // TODO: Revisit Equals and hasCode function @Override public int hashCode() { - return Objects.hash(super.hashCode()); + return shape.hashCode(); } } diff --git a/partiql-types/src/main/java/org/partiql/types/shape/trait/MetadataTrait.java b/partiql-types/src/main/java/org/partiql/types/shape/trait/MetadataTrait.java index 017d0d7fe..c44a01a1e 100644 --- a/partiql-types/src/main/java/org/partiql/types/shape/trait/MetadataTrait.java +++ b/partiql-types/src/main/java/org/partiql/types/shape/trait/MetadataTrait.java @@ -6,6 +6,11 @@ import java.util.Map; import java.util.Objects; +/** + * TODO: Improve API Economic. + *

+ * TODO: Equals and HashCode. + */ public class MetadataTrait extends PTrait { @NotNull private final String name; @@ -21,21 +26,24 @@ public MetadataTrait(PShape shape, @NotNull String name, @NotNull String value) @Override public Map meta() { Map map = super.meta(); - map.put("name", name); + map.put(name, value); return map; } + @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) return false; - MetadataTrait that = (MetadataTrait) o; + // TODO: Revisit Equals and hasCode function + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) return false; + MetadataTrait that = (MetadataTrait) obj; if (!Objects.equals(name, that.name)) return false; if (!Objects.equals(value, that.value)) return false; - return shape.equals(that.shape); + return Objects.equals(shape, that.shape); } + // TODO: Revisit Equals and hasCode function @Override public int hashCode() { - return Objects.hash(super.hashCode()); + return shape.hashCode(); } } diff --git a/partiql-types/src/main/java/org/partiql/types/shape/trait/NotNullTrait.java b/partiql-types/src/main/java/org/partiql/types/shape/trait/NotNullTrait.java index edd8563f5..95553a23d 100644 --- a/partiql-types/src/main/java/org/partiql/types/shape/trait/NotNullTrait.java +++ b/partiql-types/src/main/java/org/partiql/types/shape/trait/NotNullTrait.java @@ -4,6 +4,11 @@ import java.util.Objects; +/** + * TODO: Improve API Economic. + *

+ * TODO: Equals and HashCode. + */ public class NotNullTrait extends PTrait { public NotNullTrait(PShape shape) { super(shape); @@ -15,14 +20,16 @@ public boolean isNullable() { } @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) return false; - NotNullTrait that = (NotNullTrait) o; - return shape.equals(that.shape); + // TODO: Revisit Equals and hasCode function + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) return false; + NotNullTrait that = (NotNullTrait) obj; + return Objects.equals(shape, that.shape); } + // TODO: Revisit Equals and hasCode function @Override public int hashCode() { - return Objects.hash(super.hashCode()); + return shape.hashCode(); } } diff --git a/partiql-types/src/main/java/org/partiql/types/shape/trait/PTrait.java b/partiql-types/src/main/java/org/partiql/types/shape/trait/PTrait.java index 20588dc1e..298a3bca5 100644 --- a/partiql-types/src/main/java/org/partiql/types/shape/trait/PTrait.java +++ b/partiql-types/src/main/java/org/partiql/types/shape/trait/PTrait.java @@ -5,10 +5,16 @@ import org.partiql.types.PType; import org.partiql.types.shape.PShape; +import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.Objects; +/** + * TODO: Improve API Economic. + *

+ * TODO: Equals and HashCode. + */ public abstract class PTrait extends PShape { PShape shape; @@ -42,6 +48,15 @@ public Map meta() { return shape.meta(); } + @Override + public Collection primaryKey() { + return shape.primaryKey(); + } + + public Collection unique() { + return shape.unique(); + } + @Override public @NotNull Collection getFields() throws UnsupportedOperationException { return shape.getFields(); @@ -76,4 +91,18 @@ public int getScale() throws UnsupportedOperationException { public @NotNull String toString() { return shape.toString(); } + + @Override + // TODO: Revisit Equals and hasCode function + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) return false; + PTrait that = (PTrait) obj; + return Objects.equals(shape, that.shape); + } + + // TODO: Revisit Equals and hasCode function + @Override + public int hashCode() { + return shape.hashCode(); + } } diff --git a/partiql-types/src/main/java/org/partiql/types/shape/trait/PrimaryKeyTrait.java b/partiql-types/src/main/java/org/partiql/types/shape/trait/PrimaryKeyTrait.java index 35ef5a0cd..6f3cd1eb9 100644 --- a/partiql-types/src/main/java/org/partiql/types/shape/trait/PrimaryKeyTrait.java +++ b/partiql-types/src/main/java/org/partiql/types/shape/trait/PrimaryKeyTrait.java @@ -2,10 +2,15 @@ import org.partiql.types.shape.PShape; -import java.util.HashSet; +import java.util.Collection; import java.util.List; import java.util.Objects; +/** + * TODO: Improve API Economic. + *

+ * TODO: Equals and HashCode. + */ public class PrimaryKeyTrait extends PTrait { private final List identifier; @@ -15,20 +20,23 @@ public PrimaryKeyTrait(PShape shape, List identifier) { } @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) return false; - PrimaryKeyTrait that = (PrimaryKeyTrait) o; - if (!new HashSet<>(this.identifier).containsAll(that.identifier)) { - return false; - } - if (!new HashSet<>(that.identifier).containsAll(this.identifier)) { - return false; - } - return this.shape.equals(that.shape); + public Collection primaryKey() { + return identifier; } + @Override + // TODO: Revisit Equals and hasCode function + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) return false; + PrimaryKeyTrait that = (PrimaryKeyTrait) obj; + if (!identifier.containsAll(that.identifier)) return false; + if (!that.identifier.containsAll(identifier)) return false; + return Objects.equals(shape, that.shape); + } + + // TODO: Revisit Equals and hasCode function @Override public int hashCode() { - return Objects.hash(super.hashCode()); + return shape.hashCode(); } } diff --git a/partiql-types/src/main/java/org/partiql/types/shape/trait/RangeTrait.java b/partiql-types/src/main/java/org/partiql/types/shape/trait/RangeTrait.java index 6e37d7079..b3aa5bf41 100644 --- a/partiql-types/src/main/java/org/partiql/types/shape/trait/RangeTrait.java +++ b/partiql-types/src/main/java/org/partiql/types/shape/trait/RangeTrait.java @@ -2,9 +2,13 @@ import org.partiql.types.shape.PShape; -import java.util.HashSet; import java.util.Objects; +/** + * TODO: Improve API Economic. + *

+ * TODO: Equals and HashCode. + */ public class RangeTrait extends PTrait { private final Number minValue; private final Number maxValue; @@ -20,7 +24,8 @@ public Number minValue() { if (minValue == null) { return super.minValue(); } - return Math.max(super.minValue().doubleValue(), minValue.doubleValue()); + + return Math.max(shape.minValue().doubleValue(), minValue.doubleValue()); } @Override @@ -28,20 +33,22 @@ public Number maxValue() { if (maxValue == null) { return super.maxValue(); } - return Math.min(super.maxValue().doubleValue(), maxValue.doubleValue()); + return Math.min(shape.maxValue().doubleValue(), maxValue.doubleValue()); } @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) return false; - RangeTrait that = (RangeTrait) o; - if (!Objects.equals(minValue, that.minValue)) return false; + // TODO: Revisit Equals and hasCode function + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) return false; + RangeTrait that = (RangeTrait) obj; if (!Objects.equals(maxValue, that.maxValue)) return false; - return this.shape.equals(that.shape); + if (!Objects.equals(minValue, that.minValue)) return false; + return Objects.equals(shape, that.shape); } + // TODO: Revisit Equals and hasCode function @Override public int hashCode() { - return Objects.hash(super.hashCode()); + return shape.hashCode(); } } diff --git a/partiql-types/src/main/java/org/partiql/types/shape/trait/RequiredTrait.java b/partiql-types/src/main/java/org/partiql/types/shape/trait/RequiredTrait.java index b76e6fb12..121210f69 100644 --- a/partiql-types/src/main/java/org/partiql/types/shape/trait/RequiredTrait.java +++ b/partiql-types/src/main/java/org/partiql/types/shape/trait/RequiredTrait.java @@ -2,9 +2,13 @@ import org.partiql.types.shape.PShape; -import java.util.HashSet; import java.util.Objects; +/** + * TODO: Improve API Economic. + *

+ * TODO: Equals and HashCode. + */ public class RequiredTrait extends PTrait { public RequiredTrait(PShape shape) { @@ -17,14 +21,16 @@ public boolean isOptional() { } @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) return false; - RequiredTrait that = (RequiredTrait) o; - return this.shape.equals(that.shape); + // TODO: Revisit Equals and hasCode function + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) return false; + RequiredTrait that = (RequiredTrait) obj; + return Objects.equals(shape, that.shape); } + // TODO: Revisit Equals and hasCode function @Override public int hashCode() { - return Objects.hash(super.hashCode()); + return shape.hashCode(); } } diff --git a/partiql-types/src/main/java/org/partiql/types/shape/trait/UniqueTrait.java b/partiql-types/src/main/java/org/partiql/types/shape/trait/UniqueTrait.java index 0fc8572c9..0e6d39a29 100644 --- a/partiql-types/src/main/java/org/partiql/types/shape/trait/UniqueTrait.java +++ b/partiql-types/src/main/java/org/partiql/types/shape/trait/UniqueTrait.java @@ -2,10 +2,15 @@ import org.partiql.types.shape.PShape; -import java.util.HashSet; +import java.util.Collection; import java.util.List; import java.util.Objects; +/** + * TODO: Improve API Economic. + *

+ * TODO: Equals and HashCode. + */ public class UniqueTrait extends PTrait { private final List identifier; @@ -15,20 +20,25 @@ public UniqueTrait(PShape shape, List identifier) { } @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) return false; - UniqueTrait that = (UniqueTrait) o; - if (!new HashSet<>(this.identifier).containsAll(that.identifier)) { - return false; - } - if (!new HashSet<>(that.identifier).containsAll(this.identifier)) { - return false; - } - return this.shape.equals(that.shape); + public Collection unique() { + Collection unique = shape.unique(); + unique.addAll(identifier); + return unique; } + @Override + // TODO: Revisit Equals and hasCode function + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) return false; + UniqueTrait that = (UniqueTrait) obj; + if (!identifier.containsAll(that.identifier)) return false; + if (!that.identifier.containsAll(identifier)) return false; + return Objects.equals(shape, that.shape); + } + + // TODO: Revisit Equals and hasCode function @Override public int hashCode() { - return Objects.hash(super.hashCode()); + return shape.hashCode(); } }