Files
2026-07-13 12:32:25 +08:00

495 lines
16 KiB
Go

// 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 (
"fmt"
"github.com/cockroachdb/errors"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/sirupsen/logrus"
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
pgnodes "github.com/dolthub/doltgresql/server/node"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// nodeAlterTable handles *tree.AlterTable nodes.
func nodeAlterTable(ctx *Context, node *tree.AlterTable) (vitess.Statement, error) {
if node == nil {
return nil, nil
}
treeTableName := node.Table.ToTableName()
tableName, err := nodeTableName(ctx, &treeTableName)
if err != nil {
return nil, err
}
// We don't implement sequence addition/modification as a DDL since it's not in GMS, so we only support one of these
// at a time. If there are more than one, then we'll return an error in the command loop.
if len(node.Cmds) == 1 {
cmd, ok := node.Cmds[0].(*tree.AlterTableComputed)
if ok {
return nodeAlterTableComputed(ctx, treeTableName, cmd)
}
if vcmd, ok := node.Cmds[0].(*tree.AlterTableValidateConstraint); ok {
return vitess.InjectedStatement{
Statement: pgnodes.NewValidateConstraint(
tableName.SchemaQualifier.String(),
tableName.Name.String(),
bareIdentifier(vcmd.Constraint),
),
Children: nil,
}, nil
}
}
statements, noOps, err := nodeAlterTableCmds(ctx, node.Cmds, tableName, node.IfExists)
if err != nil {
return nil, err
}
// If there are no valid statements return a no-op statement
if len(noOps) > 0 && len(statements) == 0 {
return NewNoOp(noOps...), nil
}
// Otherwise emit warnings now, then return an AlterTable statement
// TODO: we don't have a way to send or store the warnings alongside a valid AlterTable statement. We could either
// get a *sql.Context here and emit warnings, or we could store the warnings in the Context and make the caller
// emit them before it sends |ReadyForQuery|
// if len(noOps) > 0 {
// emit warnings
// }
return &vitess.AlterTable{
Table: tableName,
Statements: statements,
}, nil
}
// nodeAlterTableSetSchema handles *tree.AlterTableSetSchema nodes.
func nodeAlterTableSetSchema(ctx *Context, node *tree.AlterTableSetSchema) (vitess.Statement, error) {
if node == nil {
return nil, nil
}
return nil, errors.Errorf("ALTER TABLE SET SCHEMA is not yet supported")
}
// nodeAlterTableCmds converts tree.AlterTableCmds into a slice of vitess.DDL instances that can be executed by GMS.
// |tableName| identifies the table being altered, and |ifExists| indicates whether the IF EXISTS clause was specified.
// A second slice of unsupported but safely ignored statements is return as well.
func nodeAlterTableCmds(
ctx *Context,
node tree.AlterTableCmds,
tableName vitess.TableName,
ifExists bool) ([]*vitess.DDL, []string, error) {
if len(node) == 0 {
return nil, nil, errors.Errorf("no commands specified for ALTER TABLE statement")
}
vitessDdlCmds := make([]*vitess.DDL, 0, len(node))
var unsupportedWarnings []string
for _, cmd := range node {
var err error
var statement *vitess.DDL
switch cmd := cmd.(type) {
case *tree.AlterTableAddConstraint:
statement, err = nodeAlterTableAddConstraint(ctx, cmd, tableName, ifExists)
if err != nil {
return nil, nil, err
}
vitessDdlCmds = append(vitessDdlCmds, statement)
case *tree.AlterTableAddColumn:
statement, err = nodeAlterTableAddColumn(ctx, cmd, tableName, ifExists)
if err != nil {
return nil, nil, err
}
vitessDdlCmds = append(vitessDdlCmds, statement)
// If inline constraints have been specified, set the ConstraintAction so that they get processed
if len(statement.TableSpec.Constraints) > 0 {
statement.ConstraintAction = vitess.AddStr
}
case *tree.AlterTableDropColumn:
statement, err = nodeAlterTableDropColumn(ctx, cmd, tableName, ifExists)
if err != nil {
return nil, nil, err
}
vitessDdlCmds = append(vitessDdlCmds, statement)
case *tree.AlterTableDropConstraint:
statement, err = nodeAlterTableDropConstraint(ctx, cmd, tableName, ifExists)
if err != nil {
return nil, nil, err
}
vitessDdlCmds = append(vitessDdlCmds, statement)
case *tree.AlterTableRenameColumn:
statement, err = nodeAlterTableRenameColumn(ctx, cmd, tableName, ifExists)
if err != nil {
return nil, nil, err
}
vitessDdlCmds = append(vitessDdlCmds, statement)
case *tree.AlterTableSetDefault:
statement, err = nodeAlterTableSetDefault(ctx, cmd, tableName, ifExists)
if err != nil {
return nil, nil, err
}
vitessDdlCmds = append(vitessDdlCmds, statement)
case *tree.AlterTableDropNotNull:
statement, err = nodeAlterTableDropNotNull(ctx, cmd, tableName, ifExists)
if err != nil {
return nil, nil, err
}
vitessDdlCmds = append(vitessDdlCmds, statement)
case *tree.AlterTableSetNotNull:
statement, err = nodeAlterTableSetNotNull(ctx, cmd, tableName, ifExists)
if err != nil {
return nil, nil, err
}
vitessDdlCmds = append(vitessDdlCmds, statement)
case *tree.AlterTableAlterColumnType:
statement, err = nodeAlterTableAlterColumnType(ctx, cmd, tableName, ifExists)
if err != nil {
return nil, nil, err
}
vitessDdlCmds = append(vitessDdlCmds, statement)
case *tree.AlterTableOwner:
unsupportedWarnings = append(unsupportedWarnings, fmt.Sprintf("ALTER TABLE %s OWNER TO %s", tableName.String(), cmd.Owner))
case *tree.AlterTableComputed:
return nil, nil, errors.New("This command does not currently support multiple actions in one statement")
case *tree.AlterTableSetStatistics:
// is unsupported and ignored
case *tree.AlterTableRowLevelSecurity:
// is unsupported and ignored
case *tree.AlterTableReplicaIdentity:
// is unsupported and ignored
default:
return nil, nil, errors.Errorf("ALTER TABLE with unsupported command type %T", cmd)
}
}
return vitessDdlCmds, unsupportedWarnings, nil
}
// nodeAlterTableAddConstraint converts a tree.AlterTableAddConstraint 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 nodeAlterTableAddConstraint(
ctx *Context,
node *tree.AlterTableAddConstraint,
tableName vitess.TableName,
ifExists bool) (*vitess.DDL, error) {
notValid := node.ValidationBehavior == tree.ValidationSkip
if notValid {
if _, ok := node.ConstraintDef.(*tree.UniqueConstraintTableDef); ok {
return nil, errors.Errorf("NOT VALID is not applicable to UNIQUE or PRIMARY KEY constraints")
}
}
switch constraintDef := node.ConstraintDef.(type) {
case *tree.CheckConstraintTableDef:
return nodeCheckConstraintTableDef(ctx, constraintDef, tableName, ifExists, notValid)
case *tree.UniqueConstraintTableDef:
return nodeUniqueConstraintTableDef(ctx, constraintDef, tableName, ifExists)
case *tree.ForeignKeyConstraintTableDef:
foreignKeyDefinition, err := nodeForeignKeyConstraintTableDef(ctx, constraintDef, notValid)
if err != nil {
return nil, err
}
return &vitess.DDL{
Action: "alter",
ConstraintAction: "add",
Table: tableName,
IfExists: ifExists,
TableSpec: &vitess.TableSpec{
Constraints: []*vitess.ConstraintDefinition{
{
Name: bareIdentifier(constraintDef.Name),
Details: foreignKeyDefinition,
},
},
},
}, nil
default:
return nil, errors.Errorf("ALTER TABLE with unsupported constraint "+
"definition type %T", node)
}
}
// bareIdentifier returns the string representation of a name without any quoting
// (quoted is the default Name.String() behavior)
func bareIdentifier(id tree.Name) string {
ctx := tree.NewFmtCtx(tree.FmtBareIdentifiers)
id.Format(ctx)
return ctx.CloseAndGetString()
}
// nodeAlterTableAddColumn converts a tree.AlterTableAddColumn instance into an equivalent vitess.DDL instance.
func nodeAlterTableAddColumn(ctx *Context, node *tree.AlterTableAddColumn, tableName vitess.TableName, ifExists bool) (*vitess.DDL, error) {
if node.IfNotExists {
return nil, errors.Errorf("IF NOT EXISTS on a column in an ADD COLUMN statement is not supported yet")
}
vitessColumnDef, err := nodeColumnTableDef(ctx, node.ColumnDef)
if err != nil {
return nil, err
}
tableSpec := &vitess.TableSpec{}
tableSpec.AddColumn(vitessColumnDef)
if node.ColumnDef.References.Table != nil {
constraintDef, err := nodeForeignKeyDefinitionFromColumnTableDef(ctx, node.ColumnDef.Name, node.ColumnDef)
if err != nil {
return nil, err
}
tableSpec.AddConstraint(&vitess.ConstraintDefinition{
Name: string(node.ColumnDef.References.ConstraintName),
Details: constraintDef,
})
}
return &vitess.DDL{
Action: "alter",
ColumnAction: "add",
Table: tableName,
IfExists: ifExists,
Column: vitessColumnDef.Name,
TableSpec: tableSpec,
}, nil
}
// nodeAlterTableDropColumn converts a tree.AlterTableDropColumn instance into an equivalent vitess.DDL instance.
func nodeAlterTableDropColumn(ctx *Context, node *tree.AlterTableDropColumn, tableName vitess.TableName, ifExists bool) (*vitess.DDL, error) {
if node.IfExists {
return nil, errors.Errorf("IF EXISTS on a column in a DROP COLUMN statement is not supported yet")
}
switch node.DropBehavior {
case tree.DropDefault:
case tree.DropRestrict:
return nil, errors.Errorf("ALTER TABLE DROP COLUMN does not support RESTRICT option")
case tree.DropCascade:
logrus.Warnf("CASCADE option on DROP COLUMN is not yet supported, ignoring")
default:
return nil, errors.Errorf("ALTER TABLE with unsupported drop behavior %v", node.DropBehavior)
}
return &vitess.DDL{
Action: "alter",
ColumnAction: "drop",
Table: tableName,
IfExists: ifExists,
Column: vitess.NewColIdent(node.Column.String()),
}, nil
}
// nodeAlterTableRenameColumn converts a tree.AlterTableRenameColumn instance into an equivalent vitess.DDL instance.
func nodeAlterTableRenameColumn(ctx *Context, node *tree.AlterTableRenameColumn, tableName vitess.TableName, ifExists bool) (*vitess.DDL, error) {
return &vitess.DDL{
Action: "alter",
ColumnAction: "rename",
Table: tableName,
IfExists: ifExists,
Column: vitess.NewColIdent(node.Column.String()),
ToColumn: vitess.NewColIdent(node.NewName.String()),
}, nil
}
// nodeAlterTableSetDefault converts a tree.AlterTableSetDefault instance into an equivalent vitess.DDL instance.
func nodeAlterTableSetDefault(ctx *Context, node *tree.AlterTableSetDefault, tableName vitess.TableName, ifExists bool) (*vitess.DDL, error) {
expr, err := nodeExpr(ctx, node.Default)
if err != nil {
return nil, err
}
// GMS requires the AST to wrap function expressions in parens
if _, ok := expr.(*vitess.FuncExpr); ok {
expr = &vitess.ParenExpr{Expr: expr}
}
return &vitess.DDL{
Action: "alter",
Table: tableName,
IfExists: ifExists,
DefaultSpec: &vitess.DefaultSpec{
Action: "set",
Column: vitess.NewColIdent(bareIdentifier(node.Column)),
Value: expr,
},
}, nil
}
// nodeAlterTableAlterColumnType converts a tree.AlterTableAlterColumnType instance into an equivalent vitess.DDL instance.
func nodeAlterTableAlterColumnType(ctx *Context, node *tree.AlterTableAlterColumnType, tableName vitess.TableName, ifExists bool) (*vitess.DDL, error) {
if node.Collation != "" {
return nil, errors.Errorf("ALTER TABLE with COLLATE is not supported yet")
}
if node.Using != nil {
return nil, errors.Errorf("ALTER TABLE with USING is not supported yet")
}
convertType, resolvedType, err := nodeResolvableTypeReference(ctx, node.ToType, false)
if err != nil {
return nil, err
}
if resolvedType == pgtypes.Record {
return nil, errors.Errorf(`column "%s" has pseudo-type record`, node.Column.String())
}
return &vitess.DDL{
Action: "alter",
Table: tableName,
IfExists: ifExists,
ColumnTypeSpec: &vitess.ColumnTypeSpec{
Column: vitess.NewColIdent(node.Column.String()),
Type: vitess.ColumnType{
Type: convertType.Type,
ResolvedType: resolvedType,
Length: convertType.Length,
Scale: convertType.Scale,
Charset: convertType.Charset,
},
},
}, nil
}
// nodeAlterTableDropNotNull converts a tree.AlterTableDropNotNull instance into an equivalent vitess.DDL instance.
func nodeAlterTableDropNotNull(ctx *Context, node *tree.AlterTableDropNotNull, tableName vitess.TableName, ifExists bool) (*vitess.DDL, error) {
return &vitess.DDL{
Action: "alter",
Table: tableName,
IfExists: ifExists,
NotNullSpec: &vitess.NotNullSpec{
Action: "drop",
Column: vitess.NewColIdent(node.Column.String()),
},
}, nil
}
// nodeAlterTableSetNotNull converts a tree.AlterTableSetNotNull instance into an equivalent vitess.DDL instance.
func nodeAlterTableSetNotNull(ctx *Context, node *tree.AlterTableSetNotNull, tableName vitess.TableName, ifExists bool) (*vitess.DDL, error) {
return &vitess.DDL{
Action: "alter",
Table: tableName,
IfExists: ifExists,
NotNullSpec: &vitess.NotNullSpec{
Action: "set",
Column: vitess.NewColIdent(node.Column.String()),
},
}, nil
}
// nodeAlterTableSetNotNull converts a tree.AlterTablePartition instance into an equivalent vitess.DDL instance.
func nodeAlterTablePartition(ctx *Context, node *tree.AlterTablePartition) (*vitess.AlterTable, error) {
if node == nil {
return nil, nil
}
// TODO: This is an incomplete translation because our GMS implementation doesn't support the MySQL
// equivalent of these statements either. Regardless, these are all no-ops.
treeTableName := node.Name.ToTableName()
tableName, err := nodeTableName(ctx, &treeTableName)
if err != nil {
return nil, err
}
switch node.Spec.Type {
case tree.PartitionBoundIn:
case tree.PartitionBoundFromTo:
case tree.PartitionBoundWith:
default:
return nil, errors.Errorf("ALTER TABLE with unsupported partition type %v", node.Spec.Type)
}
partitionDef := &vitess.PartitionDefinition{
Name: vitess.NewColIdent(node.Partition.String()),
}
actionStr := ""
if node.IsDetach {
actionStr = vitess.DropStr
} else {
actionStr = vitess.AddStr
}
partitionSpec := &vitess.PartitionSpec{
Action: actionStr,
Definitions: []*vitess.PartitionDefinition{partitionDef},
}
return &vitess.AlterTable{
Table: tableName,
PartitionSpecs: []*vitess.PartitionSpec{partitionSpec},
}, nil
}
// nodeAlterTableComputed converts a tree.AlterTableComputed instance into an equivalent CREATE/ALTER SEQUENCE statement.
func nodeAlterTableComputed(ctx *Context, tableName tree.TableName, node *tree.AlterTableComputed) (vitess.Statement, error) {
if len(node.Defs) > 0 {
return NotYetSupportedError("ALTER TABLE variant is not yet supported")
}
var persistence tree.Persistence
var seqName tree.TableName
var trimmedOptions []tree.SequenceOption
switch def := node.AddDefs.(type) {
case *tree.ColumnComputedDef:
if def.Expr != nil {
return NotYetSupportedError("ALTER TABLE variant is not yet supported")
}
trimmedOptions = make([]tree.SequenceOption, 0, len(def.Options)+2)
for _, opt := range def.Options {
switch opt.Name {
case tree.SeqOptOwnedBy:
return NotYetSupportedError("OWNED BY is invalid here")
case tree.SeqOptRestart:
return NotYetSupportedError("RESTART is not yet supported")
case tree.SeqOptName:
seqName = opt.SeqName
case tree.SeqOptLogged:
persistence = tree.PersistencePermanent
case tree.SeqOptUnlogged:
persistence = tree.PersistenceUnlogged
default:
trimmedOptions = append(trimmedOptions, opt)
}
}
default:
return NotYetSupportedError("ALTER TABLE variant is not yet supported")
}
trimmedOptions = append(trimmedOptions,
tree.SequenceOption{
Name: tree.SeqOptOwnedBy,
ColumnItemVal: tree.NewColumnItem(&tableName, node.Column),
},
tree.SequenceOption{
Name: tree.SeqOptViaAlterTable,
},
)
return nodeCreateSequence(ctx, &tree.CreateSequence{
IfNotExists: false,
Name: seqName,
Persistence: persistence,
Options: trimmedOptions,
})
}