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

199 lines
6.4 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 (
"github.com/cockroachdb/errors"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/postgres/parser/sem/tree"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// DoltCreateTablePlaceholderSequenceName is a Placeholder name used in translating computed columns to generated
// columns that involve a sequence, used later in analysis
const DoltCreateTablePlaceholderSequenceName = "dolt_create_table_placeholder_sequence"
// nodeColumnTableDef handles *tree.ColumnTableDef nodes.
func nodeColumnTableDef(ctx *Context, node *tree.ColumnTableDef) (*vitess.ColumnDefinition, error) {
if node == nil {
return nil, nil
}
if len(node.Nullable.ConstraintName) > 0 ||
len(node.DefaultExpr.ConstraintName) > 0 ||
len(node.UniqueConstraintName) > 0 {
return nil, errors.Errorf("non-foreign key column constraint names are not yet supported")
}
convertType, resolvedType, err := nodeResolvableTypeReference(ctx, node.Type, false)
if err != nil {
return nil, err
}
if resolvedType == pgtypes.Record {
return nil, errors.Errorf(`column "%s" has pseudo-type record`, node.Name.String())
}
var isNull vitess.BoolVal
var isNotNull vitess.BoolVal
switch node.Nullable.Nullability {
case tree.NotNull:
isNull = false
isNotNull = true
case tree.Null:
isNull = true
isNotNull = false
case tree.SilentNull:
isNull = true
isNotNull = false
default:
return nil, errors.Errorf("unknown NULL type encountered")
}
keyOpt := vitess.ColumnKeyOption(0) // colKeyNone, unexported for some reason
if node.PrimaryKey.IsPrimaryKey {
keyOpt = 1 // colKeyPrimary
isNull = false
isNotNull = true
} else if node.Unique {
keyOpt = 3 // colKeyUnique
}
defaultExpr, err := nodeExpr(ctx, node.DefaultExpr.Expr)
if err != nil {
return nil, err
}
// Wrap any default expression using a function call in parens to match MySQL's column default requirements
if _, ok := defaultExpr.(*vitess.FuncExpr); ok {
defaultExpr = &vitess.ParenExpr{Expr: defaultExpr}
}
if node.References.Table != nil {
if len(node.References.Col) == 0 {
return nil, errors.Errorf("implicit primary key matching on column foreign key is not yet supported")
}
// Callers register the foreign key via the table constraint list, so ForeignKeyDef
// is left unset here to avoid creating the same constraint twice.
}
if len(node.Computed.Options) > 0 {
return nil, errors.Errorf("sequence options are not yet supported, create a sequence separately")
}
var generated vitess.Expr
hasGeneratedExpr := node.IsComputed() && node.Computed.Expr != nil
computedByDefaultAsIdentity := node.IsComputed() && !hasGeneratedExpr && node.Computed.ByDefault
computedAsIdentity := node.IsComputed() && !hasGeneratedExpr && !node.Computed.ByDefault
if hasGeneratedExpr {
generated, err = nodeExpr(ctx, node.Computed.Expr)
if err != nil {
return nil, err
}
} else if computedAsIdentity {
generated, err = nodeExpr(ctx, &tree.FuncExpr{
Func: tree.WrapFunction("nextval"),
Exprs: tree.Exprs{
tree.NewStrVal(DoltCreateTablePlaceholderSequenceName),
},
})
if err != nil {
return nil, err
}
}
if generated != nil {
// GMS requires the AST to wrap function expressions in parens
if _, ok := generated.(*vitess.FuncExpr); ok {
generated = &vitess.ParenExpr{Expr: generated}
}
// clean up the expressions generated here. our default expression handling generates aliases that aren't
// appropriate in this context.
generated = clearAliases(generated)
}
if node.IsSerial || computedByDefaultAsIdentity || computedAsIdentity {
if resolvedType.IsEmptyType() {
return nil, errors.Errorf("serial type was not resolvable")
}
switch resolvedType.ID {
case pgtypes.Int16.ID:
resolvedType = pgtypes.Int16Serial
case pgtypes.Int32.ID:
resolvedType = pgtypes.Int32Serial
case pgtypes.Int64.ID:
resolvedType = pgtypes.Int64Serial
default:
return nil, errors.Errorf(`type "%s" cannot be serial`, resolvedType.String())
}
if defaultExpr != nil {
return nil, errors.Errorf(`multiple default values specified for column "%s"`, node.Name)
}
}
colDef := &vitess.ColumnDefinition{
Name: vitess.NewColIdent(string(node.Name)),
Type: vitess.ColumnType{
Type: convertType.Type,
ResolvedType: resolvedType,
Null: isNull,
NotNull: isNotNull,
Autoincrement: false,
Default: defaultExpr,
Length: convertType.Length,
Scale: convertType.Scale,
KeyOpt: keyOpt,
GeneratedExpr: generated,
Stored: generated != nil, // postgres generated columns are always stored, never virtual
},
}
if len(node.CheckExprs) > 0 {
// TODO: vitess does not support multiple check constraint on a single column
if len(node.CheckExprs) > 1 {
return nil, errors.Errorf("column-declared multiple CHECK expressions are not yet supported")
}
var checkConstraints = make([]*vitess.ConstraintDefinition, len(node.CheckExprs))
for i, checkExpr := range node.CheckExprs {
expr, err := nodeExpr(ctx, checkExpr.Expr)
if err != nil {
return nil, err
}
checkConstraints[i] = &vitess.ConstraintDefinition{
Name: string(checkExpr.ConstraintName),
Details: &vitess.CheckConstraintDefinition{
Expr: expr,
Enforced: true,
},
}
}
// until we support multiple constraints in a column
colDef.Type.Constraint = checkConstraints[0]
}
return colDef, nil
}
// clearAliases removes As and InputExpression from any AliasedExpr in the expression tree given. This is required
// in some contexts where we expect the expression to serialize to a string without any alias names.
func clearAliases(e vitess.Expr) vitess.Expr {
_ = vitess.Walk(func(node vitess.SQLNode) (kontinue bool, err error) {
if expr, ok := node.(*vitess.AliasedExpr); ok {
expr.As = vitess.ColIdent{}
expr.InputExpression = ""
}
return true, nil
}, e)
return e
}