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

265 lines
9.3 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 (
"context"
"fmt"
"github.com/cockroachdb/errors"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
"github.com/dolthub/go-mysql-server/sql/hash"
"github.com/dolthub/go-mysql-server/sql/plan"
"github.com/dolthub/go-mysql-server/sql/types"
vitess "github.com/dolthub/vitess/go/vt/sqlparser"
"github.com/dolthub/doltgresql/server/functions/framework"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// InSubquery represents a VALUE IN (SELECT ...) expression.
type InSubquery struct {
leftExpr sql.Expression
rightExpr *plan.Subquery
// These variables are used so that we can resolve the comparison functions once and reuse them as we iterate over rows.
// These are assigned in WithChildren, so refer there for more information.
leftLiteral *expression.Literal
rightLiterals []*expression.Literal
compFuncs []framework.Function
}
var _ vitess.Injectable = (*InSubquery)(nil)
var _ sql.Expression = (*InSubquery)(nil)
var _ expression.BinaryExpression = (*InSubquery)(nil)
// nilKey is the hash of a row with a single nil value.
var nilKey, _ = hash.HashOf(nil, nil, sql.NewRow(nil))
// NewInSubquery returns a new *InSubquery.
func NewInSubquery() *InSubquery {
return &InSubquery{}
}
// Children implements the sql.Expression interface.
func (in *InSubquery) Children() []sql.Expression {
return []sql.Expression{in.leftExpr, in.rightExpr}
}
// Eval implements the sql.Expression interface.
func (in *InSubquery) Eval(ctx *sql.Context, row sql.Row) (any, error) {
if len(in.compFuncs) == 0 {
return nil, errors.Errorf("%T: cannot Eval as it has not been fully resolved", in)
}
left, err := in.leftExpr.Eval(ctx, row)
if err != nil {
return nil, err
}
// The NULL handling for IN expressions is tricky. According to
// https://www.postgresql.org/docs/16/functions-comparisons.html#FUNCTIONS-COMPARISONS-IN-SCALAR:
// To comply with the SQL standard, IN() returns NULL not only if the expression on the left hand side is NULL, but
// also if no match is found in the list and one of the expressions in the list is NULL.
leftNull := left == nil
if types.NumColumns(in.Left().Type(ctx)) != types.NumColumns(in.Right().Type(ctx)) {
return nil, sql.ErrInvalidOperandColumns.New(types.NumColumns(in.Left().Type(ctx)), types.NumColumns(in.Right().Type(ctx)))
}
right := in.rightExpr
// TODO: does this work for all pg values?
values, err := right.HashMultiple(ctx, row)
if err != nil {
return nil, err
}
// NULL IN (list) returns NULL. NULL IN (empty list) returns 0
if leftNull {
if values.Size() == 0 {
return false, nil
}
return nil, nil
}
// TODO: it might be possible for the left value to hash to a different value than the right even though they pass
// an equality check. We need to perform a type conversion here to catch this case.
key, err := hash.HashOf(ctx, nil, sql.NewRow(left))
if err != nil {
return nil, err
}
// If the hashed values don't contain the left value hash, we know it's not there.
// If we do find the hash of the left value, we still need to check for equality,
// since non-equal values could have the same hash in some cases.
val, notFoundErr := values.Get(key)
if notFoundErr != nil {
if _, nilValNotFoundErr := values.Get(nilKey); nilValNotFoundErr == nil {
return nil, nil
}
return false, nil
}
var r sql.Row
rowVal, ok := val.([]any)
if !ok {
r = sql.Row{val}
} else {
r = sql.NewRow(rowVal...)
}
return in.valuesEqual(ctx, left, r)
}
// valuesEqual returns true if the left value is equal to the row provided using the equality functions previously
// assigned to |compFuncs| during analysis. If the left value is a single scalar, then |row| has a single value as
// well. Otherwise, (left is a tuple), |row| has a matching number of values.
func (in *InSubquery) valuesEqual(ctx *sql.Context, left interface{}, row sql.Row) (bool, error) {
// Note that we have to edit the literals in place, since the comparison functions reference them directly.
in.leftLiteral.Val = left
for i, v := range row {
in.rightLiterals[i].Val = v
}
for _, compFunc := range in.compFuncs {
result, err := compFunc.Eval(ctx, nil)
if err != nil {
return false, err
}
if !result.(bool) {
return false, nil
}
}
return true, nil
}
// IsNullable implements the sql.Expression interface.
func (in *InSubquery) IsNullable(ctx *sql.Context) bool {
return true
}
// Resolved implements the sql.Expression interface.
func (in *InSubquery) Resolved() bool {
if in.leftExpr == nil || !in.leftExpr.Resolved() || in.rightExpr == nil || !in.rightExpr.Resolved() || len(in.compFuncs) == 0 {
return false
}
for _, compFunc := range in.compFuncs {
if !compFunc.Resolved() {
return false
}
}
return true
}
// String implements the sql.Expression interface.
func (in *InSubquery) String() string {
if in.leftExpr == nil || in.rightExpr == nil {
return "? IN ?"
}
return fmt.Sprintf("%s IN %s", in.leftExpr.String(), in.rightExpr.String())
}
// Type implements the sql.Expression interface.
func (in *InSubquery) Type(ctx *sql.Context) sql.Type {
return pgtypes.Bool
}
// WithChildren implements the sql.Expression interface.
func (in *InSubquery) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
if len(children) != 2 {
return nil, sql.ErrInvalidChildrenNumber.New(in, len(children), 2)
}
sq, ok := children[1].(*plan.Subquery)
if !ok {
return nil, errors.Errorf("%T: expected right child to be `%T` but has type `%T`", in, &plan.Subquery{}, children[1])
}
// We'll only resolve the comparison functions once we have all Doltgres types.
// We may see GMS types during some analyzer steps, so we should wait until those are done.
if leftType, ok := children[0].Type(ctx).(*pgtypes.DoltgresType); ok {
// Rather than finding and resolving a comparison function every time we call Eval, we resolve them once and
// reuse the functions. We also want to avoid re-assigning the parameters of the comparison functions since that
// will also cause the functions to resolve again. To do this, we store expressions within our struct that the
// functions reference, so we can freely switch the values within the literals without changing anything
// regarding the comparison functions. This is usually unsafe, but since we're verifying the types returned by
// the parameters, and assigning the values to our own literals, we do not have to worry. This offers a
// significant speedup as function resolution is very expensive, so we want to do it as few times as possible
// (preferably once).
// We need a comparison function for each type in the query result
sch := sq.Query.Schema(ctx)
leftLiteral := expression.NewLiteral(nil, leftType)
rightLiterals := make([]*expression.Literal, len(sch))
compFuncs := make([]framework.Function, len(sch))
allValidChildren := true
for i, rightCol := range sch {
rightType, ok := rightCol.Type.(*pgtypes.DoltgresType)
if !ok {
allValidChildren = false
break
}
rightLiterals[i] = expression.NewLiteral(nil, rightType)
compFuncs[i] = framework.GetBinaryFunction(framework.Operator_BinaryEqual).Compile(ctx, "internal_in_comparison", leftLiteral, rightLiterals[i])
if compFuncs[i] == nil {
return nil, errors.Errorf("operator does not exist: %s = %s", leftType.String(), rightType.String())
}
if compFuncs[i].Type(ctx).(*pgtypes.DoltgresType).ID != pgtypes.Bool.ID {
// This should never happen, but this is just to be safe
return nil, errors.Errorf("%T: found equality comparison that does not return a bool", in)
}
}
if allValidChildren {
return &InSubquery{
leftExpr: children[0],
rightExpr: sq,
leftLiteral: leftLiteral,
rightLiterals: rightLiterals,
compFuncs: compFuncs,
}, nil
}
}
return &InSubquery{
leftExpr: children[0],
rightExpr: sq,
}, nil
}
// WithResolvedChildren implements the vitess.InjectableExpression interface.
func (in *InSubquery) WithResolvedChildren(ctx context.Context, children []any) (any, error) {
if len(children) != 2 {
return nil, errors.Errorf("invalid vitess child count, expected `2` but got `%d`", len(children))
}
left, ok := children[0].(sql.Expression)
if !ok {
return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[0])
}
right, ok := children[1].(*plan.Subquery)
if !ok {
return nil, errors.Errorf("expected vitess child to be a *plan.Subquery but has type `%T`", children[1])
}
return in.WithChildren(ctx.(*sql.Context), left, right)
}
// Left implements the expression.BinaryExpression interface.
func (in *InSubquery) Left() sql.Expression {
return in.leftExpr
}
// Right implements the expression.BinaryExpression interface.
func (in *InSubquery) Right() sql.Expression {
return in.rightExpr
}