// 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" "strings" "github.com/cockroachdb/errors" "github.com/dolthub/go-mysql-server/sql" vitess "github.com/dolthub/vitess/go/vt/sqlparser" "github.com/dolthub/doltgresql/core" "github.com/dolthub/doltgresql/core/id" "github.com/dolthub/doltgresql/server/functions/framework" pgtypes "github.com/dolthub/doltgresql/server/types" ) // Array represents an ARRAY[...] expression. type Array struct { children []sql.Expression coercedType *pgtypes.DoltgresType } var _ vitess.Injectable = (*Array)(nil) var _ sql.Expression = (*Array)(nil) // NewArray returns a new *Array. func NewArray(coercedType sql.Type) (*Array, error) { var arrayCoercedType *pgtypes.DoltgresType if dt, ok := coercedType.(*pgtypes.DoltgresType); ok { if dt.IsEmptyType() { // DoltgresType pointer can be nil } else if dt.IsArrayType() { arrayCoercedType = dt } else if !dt.IsEmptyType() { return nil, errors.Errorf("cannot cast array to %s", coercedType.String()) } } else if coercedType != nil { return nil, errors.Errorf("cannot cast array to %s", coercedType.String()) } return &Array{ children: nil, coercedType: arrayCoercedType, }, nil } // Children implements the sql.Expression interface. func (array *Array) Children() []sql.Expression { return array.children } // Eval implements the sql.Expression interface. func (array *Array) Eval(ctx *sql.Context, row sql.Row) (any, error) { resultTyp := array.coercedType.ArrayBaseType() values := make([]any, len(array.children)) castsColl, err := core.GetCastsCollectionFromContext(ctx, "") if err != nil { return nil, err } for i, expr := range array.children { val, err := expr.Eval(ctx, row) if err != nil { return nil, err } if val == nil { values[i] = val continue } doltgresType, ok := expr.Type(ctx).(*pgtypes.DoltgresType) if !ok { return nil, errors.Errorf("expected DoltgresType, but got %s", expr.Type(ctx).String()) } // We always cast the element, as there may be parameter restrictions in place cast, err := castsColl.GetImplicitCast(ctx, doltgresType, resultTyp) if err != nil { return nil, err } if !cast.ID.IsValid() { return nil, errors.Errorf("cannot find cast function from %s to %s", doltgresType.String(), resultTyp.String()) } values[i], err = cast.Eval(ctx, val, doltgresType, resultTyp) if err != nil { return nil, err } } return values, nil } // IsNullable implements the sql.Expression interface. func (array *Array) IsNullable(ctx *sql.Context) bool { // TODO: verify if this is actually nullable return false } // Resolved implements the sql.Expression interface. func (array *Array) Resolved() bool { for _, child := range array.children { if child == nil || !child.Resolved() { return false } } return true } // String implements the sql.Expression interface. func (array *Array) String() string { sb := strings.Builder{} sb.WriteString("ARRAY[") for i, child := range array.children { if i > 0 { sb.WriteString(", ") } if child == nil { sb.WriteString("...") } else { sb.WriteString(child.String()) } } sb.WriteRune(']') return sb.String() } // Type implements the sql.Expression interface. func (array *Array) Type(ctx *sql.Context) sql.Type { return array.coercedType } // WithChildren implements the sql.Expression interface. func (array *Array) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) { resultType, err := array.getTargetType(ctx, children...) if err != nil { return nil, err } return &Array{ children: children, coercedType: resultType, }, nil } // WithResolvedChildren implements the vitess.InjectableExpression interface. func (array *Array) WithResolvedChildren(ctx context.Context, children []any) (any, error) { newExpressions := make([]sql.Expression, len(children)) for i, resolvedChild := range children { resolvedExpression, ok := resolvedChild.(sql.Expression) if !ok { return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", resolvedChild) } newExpressions[i] = resolvedExpression } return array.WithChildren(ctx.(*sql.Context), newExpressions...) } // getTargetType returns the evaluated type for this expression. // Returns the "anyarray" type if the type combination is invalid. func (array *Array) getTargetType(ctx *sql.Context, children ...sql.Expression) (*pgtypes.DoltgresType, error) { var childrenTypes []*pgtypes.DoltgresType for _, child := range children { if child != nil { childType, ok := child.Type(ctx).(*pgtypes.DoltgresType) if !ok { // We use "anyarray" as the indeterminate/invalid type return pgtypes.AnyArray, nil } childrenTypes = append(childrenTypes, childType) } } targetType, _, err := framework.FindCommonType(ctx, childrenTypes) if err != nil { return nil, errors.Errorf("ARRAY %s", err.Error()) } // If the common type is unresolved (e.g. a user-defined composite type seen before the analyzer runs), // look it up from the type collection so that ToArrayType can find the array type ID. if !targetType.IsResolvedType() { schemaName := targetType.ID.SchemaName() if schemaName == "" { schemaName, _ = core.GetCurrentSchema(ctx) } if typeColl, tcErr := core.GetTypesCollectionFromContext(ctx, ""); tcErr == nil && typeColl != nil { if resolved, rErr := typeColl.GetType(ctx, id.NewType(schemaName, targetType.ID.TypeName())); rErr == nil && resolved != nil { targetType = resolved } } } return targetType.ToArrayType(), nil }