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