chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user