199 lines
6.4 KiB
Go
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
|
|
}
|