// 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" "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/server/functions/framework" pgtypes "github.com/dolthub/doltgresql/server/types" ) // IsDistinctFrom represents IS DISTINCT FROM expression. type IsDistinctFrom struct { leftExpr sql.Expression rightExpr sql.Expression staticLeftLiteral *expression.Literal staticRightLiteral *expression.Literal notEqualFunc *framework.CompiledFunction } var _ vitess.Injectable = (*IsDistinctFrom)(nil) var _ sql.Expression = (*IsDistinctFrom)(nil) // NewIsDistinctFrom returns a new *IsDistinctFrom. func NewIsDistinctFrom() *IsDistinctFrom { return &IsDistinctFrom{ leftExpr: nil, rightExpr: nil, } } // Children implements the sql.Expression interface. func (n *IsDistinctFrom) Children() []sql.Expression { return []sql.Expression{n.leftExpr, n.rightExpr} } // Eval implements the sql.Expression interface. func (n *IsDistinctFrom) Eval(ctx *sql.Context, row sql.Row) (any, error) { left, err := n.leftExpr.Eval(ctx, row) if err != nil { return nil, err } right, err := n.rightExpr.Eval(ctx, row) if err != nil { return nil, err } if left == nil && right == nil { return false, nil } else if left == nil || right == nil { return true, nil } n.staticLeftLiteral.Val = left n.staticRightLiteral.Val = right if n.notEqualFunc == nil { return nil, errors.Errorf("input types do not match: %s %s", n.leftExpr.Type(ctx).String(), n.rightExpr.Type(ctx).String()) } return n.notEqualFunc.Eval(ctx, row) } // IsNullable implements the sql.Expression interface. func (n *IsDistinctFrom) IsNullable(ctx *sql.Context) bool { return true } // Resolved implements the sql.Expression interface. func (n *IsDistinctFrom) Resolved() bool { if n.leftExpr == nil || n.rightExpr == nil { return false } return n.leftExpr.Resolved() && n.rightExpr.Resolved() } // String implements the sql.Expression interface. func (n *IsDistinctFrom) String() string { return n.leftExpr.String() + " IS DISTINCT FROM " + n.rightExpr.String() } // Type implements the sql.Expression interface. func (n *IsDistinctFrom) Type(ctx *sql.Context) sql.Type { return pgtypes.Bool } // WithChildren implements the sql.Expression interface. func (n *IsDistinctFrom) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) { if len(children) != 2 { return nil, sql.ErrInvalidChildrenNumber.New(n, len(children), 2) } // This allows evaluating the arguments separate from function.Eval() in order to resolve NULL values. // This follows the same logic as InTuple expression. allValidChildren := true leftType, ok := children[0].Type(ctx).(*pgtypes.DoltgresType) if !ok { allValidChildren = false } rightType, ok := children[1].Type(ctx).(*pgtypes.DoltgresType) if !ok { allValidChildren = false } staticLeftLiteral := expression.NewLiteral(nil, leftType) staticRightLiteral := expression.NewLiteral(nil, rightType) if allValidChildren { cf := framework.GetBinaryFunction(framework.Operator_BinaryNotEqual).Compile(ctx, "internal_binary_operator_func_<>", staticLeftLiteral, staticRightLiteral) return &IsDistinctFrom{ leftExpr: children[0], rightExpr: children[1], staticLeftLiteral: staticLeftLiteral, staticRightLiteral: staticRightLiteral, notEqualFunc: cf, }, nil } return &IsDistinctFrom{ leftExpr: children[0], rightExpr: children[1], }, nil } // WithResolvedChildren implements the vitess.InjectableExpression interface. func (n *IsDistinctFrom) 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].(sql.Expression) if !ok { return nil, errors.Errorf("expected vitess child to be an expression but has type `%T`", children[1]) } return n.WithChildren(ctx.(*sql.Context), left, right) }