// Copyright 2023 Dolthub, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package ast import ( "sort" "github.com/cockroachdb/errors" vitess "github.com/dolthub/vitess/go/vt/sqlparser" "github.com/dolthub/doltgresql/postgres/parser/sem/tree" "github.com/dolthub/doltgresql/utils" ) // assignTableDef handles tree.TableDef nodes for *vitess.DDL targets. Some table defs, such as indexes, affect other // defs, such as columns, and they're therefore dependent on columns being handled first. It is up to the caller to // ensure that all defs have been ordered properly before calling. assignTableDefs handles the sort for you, so this // notice is only relevant when individually calling assignTableDef. func assignTableDef(ctx *Context, node tree.TableDef, target *vitess.DDL) error { switch node := node.(type) { case *tree.CheckConstraintTableDef: if target.TableSpec == nil { target.TableSpec = &vitess.TableSpec{} } expr, err := nodeExpr(ctx, node.Expr) if err != nil { return err } target.TableSpec.Constraints = append(target.TableSpec.Constraints, &vitess.ConstraintDefinition{ Name: string(node.Name), Details: &vitess.CheckConstraintDefinition{ Expr: expr, Enforced: true, }, }) return nil case *tree.ColumnTableDef: if target.TableSpec == nil { target.TableSpec = &vitess.TableSpec{} } columnDef, err := nodeColumnTableDef(ctx, node) if err != nil { return err } target.TableSpec.AddColumn(columnDef) if node.References.Table != nil { fkDef, err := nodeForeignKeyDefinitionFromColumnTableDef(ctx, node.Name, node) if err != nil { return err } target.TableSpec.Constraints = append(target.TableSpec.Constraints, &vitess.ConstraintDefinition{ Name: string(node.References.ConstraintName), Details: fkDef, }) } return nil case *tree.ForeignKeyConstraintTableDef: if target.TableSpec == nil { target.TableSpec = &vitess.TableSpec{} } fkDef, err := nodeForeignKeyConstraintTableDef(ctx, node, false) if err != nil { return err } target.TableSpec.Constraints = append(target.TableSpec.Constraints, &vitess.ConstraintDefinition{ Name: string(node.Name), Details: fkDef, }) return nil case *tree.IndexTableDef: if target.TableSpec == nil { target.TableSpec = &vitess.TableSpec{} } indexDef, err := nodeIndexTableDef(ctx, node) if err != nil { return err } target.TableSpec.Indexes = append(target.TableSpec.Indexes, indexDef) return nil case *tree.LikeTableDef: if len(node.Options) > 0 { return errors.Errorf("options for LIKE are not yet supported") } tableName, err := nodeTableName(ctx, &node.Name) if err != nil { return err } target.OptLike = &vitess.OptLike{ LikeTables: []vitess.TableName{tableName}, } return nil case *tree.UniqueConstraintTableDef: if target.TableSpec == nil { target.TableSpec = &vitess.TableSpec{} } indexDef, err := nodeIndexTableDef(ctx, &node.IndexTableDef) if err != nil { return err } indexDef.Info.Unique = true indexDef.Info.Primary = node.PrimaryKey // If we're setting a primary key, then we need to make sure that all of the columns are also set to NOT NULL if indexDef.Info.Primary { tableColumns := utils.SliceToMapValues(target.TableSpec.Columns, func(col *vitess.ColumnDefinition) string { return col.Name.String() }) for _, indexedColumn := range indexDef.Fields { if column, ok := tableColumns[indexedColumn.Column.String()]; ok { column.Type.Null = false column.Type.NotNull = true } } } target.TableSpec.Indexes = append(target.TableSpec.Indexes, indexDef) return nil case nil: return nil default: return errors.Errorf("unknown table definition encountered") } } // nodeForeignKeyDefinitionFromColumnTableDef returns a vitess ForeignKeyDefinition from the specified column // definition |node|. func nodeForeignKeyDefinitionFromColumnTableDef(ctx *Context, fromColumn tree.Name, node *tree.ColumnTableDef) (*vitess.ForeignKeyDefinition, error) { if node == nil { return nil, nil } references := node.References fkConstraintTableDef := &tree.ForeignKeyConstraintTableDef{ Name: references.ConstraintName, FromCols: []tree.Name{fromColumn}, Table: *references.Table, ToCols: []tree.Name{references.Col}, Actions: references.Actions, Match: references.Match, } return nodeForeignKeyConstraintTableDef(ctx, fkConstraintTableDef, false) } // assignTableDefs handles tree.TableDefs nodes for *vitess.DDL targets. This also sorts table defs by whether they're // dependent on other table defs evaluating first. Some table defs, such as indexes, affect other defs, such as columns, // and they're therefore dependent on columns being handled first. func assignTableDefs(ctx *Context, node tree.TableDefs, target *vitess.DDL) error { sortedNode := make(tree.TableDefs, len(node)) copy(sortedNode, node) sort.Slice(sortedNode, func(i, j int) bool { var cmps [2]int for cmpsIdx := range []tree.TableDef{sortedNode[i], sortedNode[j]} { switch sortedNode[i].(type) { case *tree.IndexTableDef: cmps[cmpsIdx] = 1 case *tree.UniqueConstraintTableDef: cmps[cmpsIdx] = 2 default: cmps[cmpsIdx] = 0 } } return cmps[0] < cmps[1] }) for i := range sortedNode { if err := assignTableDef(ctx, sortedNode[i], target); err != nil { return err } } return nil }