241 lines
7.4 KiB
Go
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
|
|
}
|