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

241 lines
7.4 KiB
Go

// Copyright 2024 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 expression
import (
"context"
"fmt"
"strings"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/core"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// ExplicitCast represents a VALUE::TYPE expression.
type ExplicitCast struct {
sqlChild sql.Expression
castToType *pgtypes.DoltgresType
domainNullable bool
domainChecks sql.CheckConstraints
}
var _ vitess.Injectable = (*ExplicitCast)(nil)
var _ sql.Expression = (*ExplicitCast)(nil)
// NewExplicitCastInjectable returns an incomplete *ExplicitCast that must be resolved through the vitess.Injectable interface.
func NewExplicitCastInjectable(castToType sql.Type) (*ExplicitCast, error) {
pgtype, ok := castToType.(*pgtypes.DoltgresType)
if !ok {
return nil, errors.Errorf("cast expects a Doltgres type as the target type")
}
return &ExplicitCast{
sqlChild: nil,
castToType: pgtype,
}, nil
}
// NewExplicitCast returns a new *ExplicitCast expression.
func NewExplicitCast(expr sql.Expression, toType *pgtypes.DoltgresType) *ExplicitCast {
toType = checkForDomainType(toType)
return &ExplicitCast{
sqlChild: expr,
castToType: toType,
}
}
// Children implements the sql.Expression interface.
func (c *ExplicitCast) Children() []sql.Expression {
return []sql.Expression{c.sqlChild}
}
// Child returns the child that is being cast.
func (c *ExplicitCast) Child() sql.Expression {
return c.sqlChild
}
// Eval implements the sql.Expression interface.
func (c *ExplicitCast) Eval(ctx *sql.Context, row sql.Row) (any, error) {
if !c.castToType.IsResolvedType() {
return nil, errors.Errorf("cannot call ExplicitCast.Eval with unresolved cast to type: %s", c.castToType.String())
}
val, err := c.sqlChild.Eval(ctx, row)
if err != nil {
return nil, err
}
sourceType, ok := c.sqlChild.Type(ctx).(*pgtypes.DoltgresType)
if !ok {
// We'll leverage GMSCast to handle the conversion from a GMS type to a Doltgres type.
// Rather than re-evaluating the expression, we put the result in a literal.
gmsCast := NewGMSCast(expression.NewLiteral(val, c.sqlChild.Type(ctx)))
val, err = gmsCast.Eval(ctx, row)
if err != nil {
return nil, err
}
sourceType = gmsCast.DoltgresType(ctx)
}
baseCastToType := checkForDomainType(c.castToType)
castsColl, err := core.GetCastsCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
cast, err := castsColl.GetExplicitCast(ctx, sourceType, baseCastToType)
if err != nil {
return nil, err
}
if !cast.ID.IsValid() {
return nil, errors.Errorf(
"EXPLICIT CAST: cast from `%s` to `%s` does not exist: %s",
sourceType.String(), c.castToType.String(), c.sqlChild.String(),
)
}
if val == nil {
if c.castToType.TypType == pgtypes.TypeType_Domain && !c.domainNullable {
return nil, pgtypes.ErrDomainDoesNotAllowNullValues.New(c.castToType.Name())
}
if !cast.Function.IsValid() {
return nil, nil
}
}
castResult, err := cast.Eval(ctx, val, sourceType, c.castToType)
if err != nil {
// For string types and string array types, we intentionally ignore the error as using a length-restricted cast
// is a way to intentionally truncate the data. All string types will always return the truncated result, even
// during an error, so it's safe to use.
castToType := c.castToType
if c.castToType.IsArrayType() {
castToType = c.castToType.ArrayBaseType()
}
// A nil result will be returned if there's a critical error, which we should never ignore.
if castToType.TypCategory != pgtypes.TypeCategory_StringTypes || castResult == nil {
return nil, err
}
}
if c.castToType.TypType == pgtypes.TypeType_Domain {
for _, check := range c.domainChecks {
res, err := sql.EvaluateCondition(ctx, check.Expr, sql.Row{castResult})
if err != nil {
return nil, err
}
if sql.IsFalse(res) {
return nil, pgtypes.ErrDomainValueViolatesCheckConstraint.New(c.castToType.Name(), check.Name)
}
}
}
return castResult, nil
}
// IsNullable implements the sql.Expression interface.
func (c *ExplicitCast) IsNullable(ctx *sql.Context) bool {
// TODO: verify if this is actually nullable
return true
}
// Resolved implements the sql.Expression interface.
func (c *ExplicitCast) Resolved() bool {
if c.sqlChild != nil && c.sqlChild.Resolved() {
return true
}
return false
}
// String implements the sql.Expression interface.
func (c *ExplicitCast) String() string {
var sqlChild string
if c.sqlChild == nil {
sqlChild = "unresolved"
} else {
sqlChild = c.sqlChild.String()
}
// type needs to be upper-case to match InputExpression in AliasExpr
return fmt.Sprintf("%s::%s", sqlChild, strings.ToUpper(c.castToType.String()))
}
// Type implements the sql.Expression interface.
func (c *ExplicitCast) Type(ctx *sql.Context) sql.Type {
return c.castToType
}
// WithChildren implements the sql.Expression interface.
func (c *ExplicitCast) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(c, len(children), 1)
}
return &ExplicitCast{
sqlChild: children[0],
castToType: c.castToType,
domainNullable: c.domainNullable,
domainChecks: c.domainChecks,
}, nil
}
// WithResolvedChildren implements the vitess.InjectableExpression interface.
func (c *ExplicitCast) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 1 {
return nil, errors.Errorf("invalid vitess child count, expected `1` but got `%d`", len(children))
}
resolvedExpression, ok := children[0].(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[0])
}
if !c.castToType.IsResolvedType() {
sqlCtx, ok := ctx.(*sql.Context)
if !ok {
return nil, errors.Errorf("%T requires a SQL context for type resolution", c)
}
typeColl, err := core.GetTypesCollectionFromContext(sqlCtx, "")
if err != nil {
return nil, err
}
resolvedType, err := typeColl.ResolveType(sqlCtx, c.castToType.ID)
if err != nil {
return nil, err
}
c.castToType = resolvedType
}
if !c.castToType.IsDefined {
return nil, pgtypes.ErrTypeIsOnlyAShell.New(c.castToType.Name())
}
return &ExplicitCast{
sqlChild: resolvedExpression,
castToType: c.castToType,
domainNullable: c.domainNullable,
domainChecks: c.domainChecks,
}, nil
}
// WithCastToType returns a copy of the expression with castToType replaced.
func (c *ExplicitCast) WithCastToType(t *pgtypes.DoltgresType) sql.Expression {
ec := *c
ec.castToType = t
return &ec
}
// WithDomainConstraints returns a copy of the expression with domain constraints defined.
func (c *ExplicitCast) WithDomainConstraints(nullable bool, checks sql.CheckConstraints) sql.Expression {
ec := *c
ec.domainNullable = nullable
ec.domainChecks = checks
return &ec
}