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

234 lines
7.2 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 (
"encoding/json"
"time"
"github.com/cockroachdb/apd/v3"
"github.com/cockroachdb/errors"
"github.com/dolthub/dolt/go/store/prolly/tree"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
"github.com/dolthub/go-mysql-server/sql/types"
"github.com/dolthub/vitess/go/vt/proto/query"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// GMSCast handles the conversion from a GMS expression's type to its Doltgres type that is most similar.
type GMSCast struct {
sqlChild sql.Expression
}
var _ sql.Expression = (*GMSCast)(nil)
// NewGMSCast returns a new *GMSCast.
func NewGMSCast(child sql.Expression) *GMSCast {
return &GMSCast{
sqlChild: child,
}
}
// Children implements the sql.Expression interface.
func (c *GMSCast) Children() []sql.Expression {
return []sql.Expression{c.sqlChild}
}
// Child returns the child that is being cast.
func (c *GMSCast) Child() sql.Expression {
return c.sqlChild
}
// DoltgresType returns the DoltgresType that the cast evaluates to. This is the same value that is returned by Type().
func (c *GMSCast) DoltgresType(ctx *sql.Context) *pgtypes.DoltgresType {
// GMSCast shouldn't receive a DoltgresType, but we shouldn't error if it happens
if t, ok := c.sqlChild.Type(ctx).(*pgtypes.DoltgresType); ok {
return t
}
return pgtypes.FromGmsType(c.sqlChild.Type(ctx))
}
// Eval implements the sql.Expression interface.
func (c *GMSCast) Eval(ctx *sql.Context, row sql.Row) (any, error) {
val, err := c.sqlChild.Eval(ctx, row)
if err != nil {
return nil, err
}
if val == nil {
return nil, nil
}
// GMSCast shouldn't receive a DoltgresType, but we shouldn't error if it happens
if _, ok := c.sqlChild.Type(ctx).(*pgtypes.DoltgresType); ok {
return val, nil
}
sqlTyp := c.sqlChild.Type(ctx)
switch sqlTyp.Type() {
// Boolean types are a special case because of how they are translated on the wire in Postgres. If we identify a
// boolean result, we want to convert it from an int back to a boolean.
case query.Type_INT8:
if sqlTyp == types.Boolean {
newVal, _, err := types.Int32.Convert(ctx, val)
if err != nil {
return nil, err
}
if _, ok := newVal.(int32); !ok {
return nil, errors.Errorf("GMSCast expected type `int32`, got `%T`", val)
}
if newVal.(int32) == 0 {
return false, nil
} else {
return true, nil
}
}
fallthrough
// In Postgres, Int32 is generally the smallest value returned. But we convert int8 and int16 to this type during
// schema conversion, which means we must do so here as well to avoid runtime panics.
case query.Type_INT16, query.Type_INT24, query.Type_INT32, query.Type_YEAR:
newVal, _, err := types.Int32.Convert(ctx, val)
if err != nil {
return nil, err
}
if _, ok := newVal.(int32); !ok {
return nil, errors.Errorf("GMSCast expected type `int32`, got `%T`", val)
}
return newVal, nil
case query.Type_INT64, query.Type_BIT, query.Type_UINT8, query.Type_UINT16, query.Type_UINT24, query.Type_UINT32:
newVal, _, err := types.Int64.Convert(ctx, val)
if err != nil {
return nil, err
}
if _, ok := newVal.(int64); !ok {
return nil, errors.Errorf("GMSCast expected type `int64`, got `%T`", val)
}
return newVal, nil
case query.Type_UINT64:
// Postgres doesn't have a "public" Uint64 type, so we return a Numeric value
newVal, _, err := types.InternalDecimalType.Convert(ctx, val)
if err != nil {
return nil, err
}
dec, ok := newVal.(*apd.Decimal)
if !ok {
return nil, errors.Errorf("GMSCast expected type `*apd.Decimal`, got `%T`", val)
}
return dec, nil
case query.Type_FLOAT32:
newVal, _, err := types.Float32.Convert(ctx, val)
if err != nil {
return nil, err
}
if _, ok := newVal.(float32); !ok {
return nil, errors.Errorf("GMSCast expected type `float32`, got `%T`", val)
}
return newVal, nil
case query.Type_FLOAT64:
newVal, _, err := types.Float64.Convert(ctx, val)
if err != nil {
return nil, err
}
if _, ok := newVal.(float64); !ok {
return nil, errors.Errorf("GMSCast expected type `float64`, got `%T`", val)
}
return newVal, nil
case query.Type_DECIMAL:
newVal, _, err := types.InternalDecimalType.Convert(ctx, val)
if err != nil {
return nil, err
}
dec, ok := newVal.(*apd.Decimal)
if !ok {
return nil, errors.Errorf("GMSCast expected type `*apd.Decimal`, got `%T`", val)
}
return dec, nil
case query.Type_DATE, query.Type_DATETIME, query.Type_TIMESTAMP:
if val, ok := val.(time.Time); ok {
return val, nil
}
return nil, errors.Errorf("GMSCast expected type `Time`, got `%T`", val)
case query.Type_TIME:
if val, ok := val.(types.Timespan); ok {
return val.String(), nil
}
return nil, errors.Errorf("GMSCast expected type `Timespan`, got `%T`", val)
case query.Type_CHAR, query.Type_VARCHAR, query.Type_TEXT, query.Type_BINARY, query.Type_VARBINARY, query.Type_BLOB, query.Type_SET, query.Type_ENUM:
newVal, _, err := types.LongText.Convert(ctx, val)
if err != nil {
return nil, err
}
switch newVal := newVal.(type) {
case string:
return newVal, nil
case sql.StringWrapper:
return newVal.Unwrap(ctx)
default:
return nil, errors.Errorf("GMSCast expected type `string`, got `%T`", val)
}
case query.Type_JSON:
switch val := val.(type) {
case types.JSONDocument:
return val.JSONString()
case tree.IndexedJsonDocument:
return val.String(), nil
default:
// TODO: there are particular dolt tables (dolt_constraint_violations) that return json-marshallable structs
// that we need to handle here like this
bytes, err := json.Marshal(val)
return string(bytes), err
}
case query.Type_NULL_TYPE:
return nil, nil
case query.Type_GEOMETRY:
return nil, errors.Errorf("GMS geometry types are not supported")
default:
return nil, errors.Errorf("GMS type `%s` is not supported", c.sqlChild.Type(ctx).String())
}
}
// IsNullable implements the sql.Expression interface.
func (c *GMSCast) IsNullable(ctx *sql.Context) bool {
return true
}
// Resolved implements the sql.Expression interface.
func (c *GMSCast) Resolved() bool {
return c.sqlChild.Resolved()
}
// String implements the sql.Expression interface.
func (c *GMSCast) String() string {
if gf, ok := c.sqlChild.(*expression.GetField); ok {
return gf.Name()
}
return c.sqlChild.String()
}
// Type implements the sql.Expression interface.
func (c *GMSCast) Type(ctx *sql.Context) sql.Type {
return c.DoltgresType(ctx)
}
// WithChildren implements the sql.Expression interface.
func (c *GMSCast) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(c, len(children), 1)
}
return &GMSCast{
sqlChild: children[0],
}, nil
}