diff --git a/server/ast/alter_table.go b/server/ast/alter_table.go index 9374c85f9b..3013dd75fb 100644 --- a/server/ast/alter_table.go +++ b/server/ast/alter_table.go @@ -79,6 +79,8 @@ func nodeAlterTableCmds( statement, err = nodeAlterTableAddColumn(cmd, tableName, ifExists) case *tree.AlterTableDropColumn: statement, err = nodeAlterTableDropColumn(cmd, tableName, ifExists) + case *tree.AlterTableDropConstraint: + statement, err = nodeAlterTableDropConstraint(cmd, tableName, ifExists) case *tree.AlterTableRenameColumn: statement, err = nodeAlterTableRenameColumn(cmd, tableName, ifExists) case *tree.AlterTableSetDefault: @@ -116,6 +118,8 @@ func nodeAlterTableAddConstraint( } switch constraintDef := node.ConstraintDef.(type) { + case *tree.CheckConstraintTableDef: + return nodeCheckConstraintTableDef(constraintDef, tableName, ifExists) case *tree.UniqueConstraintTableDef: return nodeUniqueConstraintTableDef(constraintDef, tableName, ifExists) case *tree.ForeignKeyConstraintTableDef: diff --git a/server/ast/constraint_table_def.go b/server/ast/constraint_table_def.go index 6c1693a5c7..67a740aced 100644 --- a/server/ast/constraint_table_def.go +++ b/server/ast/constraint_table_def.go @@ -22,6 +22,73 @@ import ( "github.com/dolthub/doltgresql/postgres/parser/sem/tree" ) +// nodeCheckConstraintTableDef converts a tree.CheckConstraintTableDef instance +// into a vitess.DDL instance that can be executed by GMS. |tableName| identifies +// the table being altered, and |ifExists| indicates whether the IF EXISTS clause +// was specified. +func nodeCheckConstraintTableDef( + node *tree.CheckConstraintTableDef, + tableName vitess.TableName, + ifExists bool) (*vitess.DDL, error) { + + if node.NoInherit { + return nil, fmt.Errorf("NO INHERIT is not yet supported for check constraints") + } + + expr, err := nodeExpr(node.Expr) + if err != nil { + return nil, err + } + + return &vitess.DDL{ + Action: "alter", + Table: tableName, + IfExists: ifExists, + ConstraintAction: "add", + TableSpec: &vitess.TableSpec{ + Constraints: []*vitess.ConstraintDefinition{ + { + Name: node.Name.String(), + Details: &vitess.CheckConstraintDefinition{ + Expr: expr, + Enforced: true, + }, + }, + }, + }, + }, nil +} + +// nodeAlterTableDropConstraint converts a tree.AlterTableDropConstraint instance +// into a vitess.DDL instance that can be executed by GMS. |tableName| identifies +// the table being altered, and |ifExists| indicates whether the IF EXISTS clause +// was specified. +func nodeAlterTableDropConstraint( + node *tree.AlterTableDropConstraint, + tableName vitess.TableName, + ifExists bool) (*vitess.DDL, error) { + + if node.DropBehavior == tree.DropCascade { + return nil, fmt.Errorf("CASCADE is not yet supported for drop constraint") + } + + if node.IfExists { + return nil, fmt.Errorf("IF EXISTS is not yet supported for drop constraint") + } + + return &vitess.DDL{ + Action: "alter", + Table: tableName, + IfExists: ifExists, + ConstraintAction: "drop", + TableSpec: &vitess.TableSpec{ + Constraints: []*vitess.ConstraintDefinition{ + {Name: node.Constraint.String()}, + }, + }, + }, nil +} + // nodeUniqueConstraintTableDef converts a tree.UniqueConstraintTableDef instance // into a vitess.DDL instance that can be executed by GMS. |tableName| identifies // the table being altered, and |ifExists| indicates whether the IF EXISTS clause @@ -48,18 +115,19 @@ func nodeUniqueConstraintTableDef( return nil, err } + indexType := "unique" if node.PrimaryKey { - return &vitess.DDL{ - Action: "alter", - Table: tableName, - IfExists: ifExists, - IndexSpec: &vitess.IndexSpec{ - Action: "create", - Type: "primary", - Columns: columns, - }, - }, nil - } else { - return nil, fmt.Errorf("Only PRIMARY KEY constraints are supported currently") + indexType = "primary" } + + return &vitess.DDL{ + Action: "alter", + Table: tableName, + IfExists: ifExists, + IndexSpec: &vitess.IndexSpec{ + Action: "create", + Type: indexType, + Columns: columns, + }, + }, nil } diff --git a/server/ast/expr.go b/server/ast/expr.go index 6db2fa4b64..1150eb68d4 100644 --- a/server/ast/expr.go +++ b/server/ast/expr.go @@ -719,6 +719,9 @@ func nodeExpr(node tree.Expr) (vitess.Expr, error) { return retExpr, nil case *tree.StrVal: // TODO: determine what to do when node.WasScannedAsBytes() is true + // For string literals, we mark the type as unknown, because Postgres has + // more permissive implicit casting rules for literals than it does for strongly + // typed values from a schema for example. unknownLiteral := pgexprs.NewUnknownLiteral(node.RawString()) return vitess.InjectedExpr{ Expression: unknownLiteral, diff --git a/server/ast/select.go b/server/ast/select.go index 7fea2caee4..f55768ee4a 100644 --- a/server/ast/select.go +++ b/server/ast/select.go @@ -16,6 +16,7 @@ package ast import ( "fmt" + "strings" vitess "github.com/dolthub/vitess/go/vt/sqlparser" @@ -157,10 +158,16 @@ func nodeSelectExpr(node tree.SelectExpr) (vitess.SelectExpr, error) { if ce, ok := expr.(*tree.CastExpr); ok && node.As == "" { node.As = tree.UnrestrictedName(tree.AsString(ce.Expr)) } + // To be consistent with vitess handling, InputExpression always gets its outer qoutes trimmed + inputExpression := tree.AsString(&node) + if strings.HasPrefix(inputExpression, "'") && strings.HasSuffix(inputExpression, "'") { + inputExpression = inputExpression[1 : len(inputExpression)-1] + } + return &vitess.AliasedExpr{ Expr: vitessExpr, As: vitess.NewColIdent(string(node.As)), - InputExpression: tree.AsString(&node), + InputExpression: inputExpression, }, nil } } diff --git a/testing/go/alter_table_test.go b/testing/go/alter_table_test.go index 154e681012..d662c10c9d 100644 --- a/testing/go/alter_table_test.go +++ b/testing/go/alter_table_test.go @@ -57,6 +57,81 @@ func TestAlterTable(t *testing.T) { }, }, }, + { + Name: "Add Unique Constraint", + SetUpScript: []string{ + "create table t1 (pk int primary key, c1 int);", + "insert into t1 values (1,1);", + "create table t2 (pk int primary key, c1 int);", + "insert into t2 values (1,1);", + }, + Assertions: []ScriptTestAssertion{ + { + // Add a secondary unique index using create index + Query: "CREATE UNIQUE INDEX ON t1(c1);", + Expected: []sql.Row{}, + }, + { + // Test that the unique constraint is working + Query: "INSERT INTO t1 VALUES (2, 1);", + ExpectedErr: "unique", + }, + { + // Add a secondary unique index using alter table + Query: "ALTER TABLE t2 ADD CONSTRAINT uniq1 UNIQUE (c1);", + Expected: []sql.Row{}, + }, + { + // Test that the unique constraint is working + Query: "INSERT INTO t2 VALUES (2, 1);", + ExpectedErr: "unique", + }, + }, + }, + { + Name: "Add Check Constraint", + SetUpScript: []string{ + "create table t1 (pk int primary key, c1 int);", + "insert into t1 values (1,1);", + }, + Assertions: []ScriptTestAssertion{ + { + // Add a check constraint that is already violated by the existing data + Query: "ALTER TABLE t1 ADD CONSTRAINT constraint1 CHECK (c1 > 100);", + ExpectedErr: "violated", + }, + { + // Add a check constraint + Query: "ALTER TABLE t1 ADD CONSTRAINT constraint1 CHECK (c1 < 100);", + Expected: []sql.Row{}, + }, + { + Query: "INSERT INTO t1 VALUES (2, 2);", + Expected: []sql.Row{}, + }, + { + Query: "INSERT INTO t1 VALUES (3, 101);", + ExpectedErr: "violated", + }, + }, + }, + { + Name: "Drop Constraint", + SetUpScript: []string{ + "create table t1 (pk int primary key, c1 int);", + "ALTER TABLE t1 ADD CONSTRAINT constraint1 CHECK (c1 > 100);", + }, + Assertions: []ScriptTestAssertion{ + { + Query: "ALTER TABLE t1 DROP CONSTRAINT constraint1;", + Expected: []sql.Row{}, + }, + { + Query: "INSERT INTO t1 VALUES (1, 1);", + Expected: []sql.Row{}, + }, + }, + }, { Name: "Add Primary Key", SetUpScript: []string{