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

213 lines
6.2 KiB
Go

// Copyright 2026 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"
"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"
)
// ColumnAccess represents an ARRAY[...] expression.
type ColumnAccess struct {
colName string
colNameIdx int
colTyp *pgtypes.DoltgresType
child sql.Expression
}
var _ vitess.Injectable = (*ColumnAccess)(nil)
var _ sql.Expression = (*ColumnAccess)(nil)
// NewColumnAccess returns a new *ColumnAccess.
func NewColumnAccess(colName string, colIdx int) (*ColumnAccess, error) {
if len(colName) > 0 {
return &ColumnAccess{
colName: colName,
colNameIdx: -1,
colTyp: nil,
child: nil,
}, nil
} else {
return &ColumnAccess{
colName: "",
colNameIdx: colIdx,
colTyp: nil,
child: nil,
}, nil
}
}
// Children implements the sql.Expression interface.
func (expr *ColumnAccess) Children() []sql.Expression {
return []sql.Expression{expr.child}
}
// Eval implements the sql.Expression interface.
func (expr *ColumnAccess) Eval(ctx *sql.Context, row sql.Row) (any, error) {
field, err := expr.child.Eval(ctx, row)
if err != nil {
return nil, err
}
if field == nil {
return nil, nil
}
recordVals, ok := field.([]pgtypes.RecordValue)
if !ok {
if len(expr.colName) > 0 {
return nil, errors.Errorf("column notation .%s applied to type %s, which is not a composite type",
expr.colName, expr.child.Type(ctx).String())
} else {
return nil, errors.Errorf("column notation .@%d applied to type %s, which is not a composite type",
expr.colNameIdx+1, expr.child.Type(ctx).String())
}
}
return recordVals[expr.colNameIdx].Value, nil
}
// IsNullable implements the sql.Expression interface.
func (expr *ColumnAccess) IsNullable(ctx *sql.Context) bool {
return true
}
// Resolved implements the sql.Expression interface.
func (expr *ColumnAccess) Resolved() bool {
return expr.child != nil && expr.child.Resolved()
}
// String implements the sql.Expression interface.
func (expr *ColumnAccess) String() string {
if expr.child == nil {
return "COLUMN_ACCESS"
}
if len(expr.colName) > 0 {
return fmt.Sprintf("(%s).%s", expr.child.String(), expr.colName)
} else {
return fmt.Sprintf("(%s).@%d", expr.child.String(), expr.colNameIdx+1)
}
}
// Type implements the sql.Expression interface.
func (expr *ColumnAccess) Type(ctx *sql.Context) sql.Type {
if expr.colTyp != nil {
return expr.colTyp
}
if expr.child == nil {
return nil
}
typ, ok := expr.child.Type(ctx).(*pgtypes.DoltgresType)
if !ok {
return pgtypes.Unknown
}
return typ.CompositeAttrs[expr.colNameIdx].Type
}
// WithChildren implements the sql.Expression interface.
func (expr *ColumnAccess) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(expr, len(children), 1)
}
child := children[0]
childType := child.Type(ctx)
doltgresType, ok := childType.(*pgtypes.DoltgresType)
if !ok {
if _, ok := child.(*expression.BindVar); ok {
return &ColumnAccess{
colName: expr.colName,
colNameIdx: expr.colNameIdx,
colTyp: expr.colTyp,
child: child,
}, nil
}
return nil, errors.New("column access is only valid for Doltgres types")
}
if !doltgresType.IsResolvedType() {
typeColl, err := core.GetTypesCollectionFromContext(ctx, "")
if err != nil {
return nil, err
}
resolvedType, err := typeColl.ResolveType(ctx, doltgresType.ID)
if err != nil {
return nil, err
}
doltgresType = resolvedType
}
if !doltgresType.IsDefined {
return nil, pgtypes.ErrTypeIsOnlyAShell.New(doltgresType.Name())
}
if !doltgresType.IsCompositeType() {
if len(expr.colName) > 0 {
return nil, errors.Errorf("column notation .%s applied to type %s, which is not a composite type",
expr.colName, child.Type(ctx).String())
} else {
return nil, errors.Errorf("column notation .@%d applied to type %s, which is not a composite type",
expr.colNameIdx+1, child.Type(ctx).String())
}
}
var idx int
if len(expr.colName) > 0 {
idx = -1
for _, attr := range doltgresType.CompositeAttrs {
if attr.Name == expr.colName {
idx = int(attr.Num - 1)
break
}
}
if idx == -1 {
return nil, errors.Errorf(`column "%s" not found in data type %s`,
expr.colName, doltgresType.String())
}
} else {
if expr.colNameIdx < 0 || expr.colNameIdx >= len(doltgresType.CompositeAttrs) {
return nil, errors.Errorf("column notation .@%d applied to type %s is out of bounds",
expr.colNameIdx+1, child.Type(ctx).String())
}
idx = expr.colNameIdx
}
return &ColumnAccess{
colName: expr.colName,
colNameIdx: idx,
colTyp: doltgresType.CompositeAttrs[idx].Type,
child: child,
}, nil
}
// WithResolvedChildren implements the vitess.InjectableExpression interface.
func (expr *ColumnAccess) 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 expr.WithChildren(ctx.(*sql.Context), newExpressions...)
}
// WithType returns this expression with the given type set, as it must be set within the analyzer.
func (expr *ColumnAccess) WithType(typ *pgtypes.DoltgresType) sql.Expression {
ne := *expr
ne.colTyp = typ
return &ne
}