// 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 }