145 lines
5.2 KiB
Go
145 lines
5.2 KiB
Go
// Copyright 2025 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 compare
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/dolthub/go-mysql-server/sql"
|
|
"github.com/dolthub/go-mysql-server/sql/expression"
|
|
|
|
"github.com/dolthub/doltgresql/server/functions/framework"
|
|
pgtypes "github.com/dolthub/doltgresql/server/types"
|
|
)
|
|
|
|
// CompareRecords compares two record values using the specified comparison operator, |op| and returns a result
|
|
// indicating if the comparison was true, false, or indeterminate (nil).
|
|
//
|
|
// More info on rules for comparing records:
|
|
// https://www.postgresql.org/docs/current/functions-comparisons.html#ROW-WISE-COMPARISON
|
|
func CompareRecords(ctx *sql.Context, op framework.Operator, v1 interface{}, v2 interface{}) (res any, err error) {
|
|
leftRecord, rightRecord, err := checkRecordArgs(v1, v2)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO: This can be a hot path when filtering a large table against a tuple, for example.
|
|
// We can reduce branching by splitting each case into their individual functions, which will also make the code
|
|
// more readable. A downside would be a ton of repeated code.
|
|
var hasNull bool
|
|
var leftLiteral, rightLiteral expression.Literal
|
|
for i := 0; i < len(leftRecord); i++ {
|
|
// NULL values are by definition not comparable
|
|
if leftRecord[i].Value == nil || rightRecord[i].Value == nil {
|
|
switch op {
|
|
case framework.Operator_BinaryEqual, framework.Operator_BinaryNotEqual:
|
|
hasNull = true
|
|
continue
|
|
default:
|
|
return nil, nil
|
|
}
|
|
}
|
|
leftLiteral.Val = leftRecord[i].Value
|
|
leftLiteral.Typ = leftRecord[i].Type
|
|
rightLiteral.Val = rightRecord[i].Value
|
|
rightLiteral.Typ = rightRecord[i].Type
|
|
|
|
switch op {
|
|
case framework.Operator_BinaryEqual:
|
|
res, err = callComparisonFunction(ctx, framework.Operator_BinaryEqual, &leftLiteral, &rightLiteral)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if res == false {
|
|
return false, nil
|
|
}
|
|
case framework.Operator_BinaryNotEqual:
|
|
res, err = callComparisonFunction(ctx, framework.Operator_BinaryNotEqual, &leftLiteral, &rightLiteral)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if res == true {
|
|
return true, nil
|
|
}
|
|
// Records are compared by evaluating each field in order of significance, so if the fields are equal, we need
|
|
// to compare the next field.
|
|
case framework.Operator_BinaryLessThan, framework.Operator_BinaryLessOrEqual,
|
|
framework.Operator_BinaryGreaterThan, framework.Operator_BinaryGreaterOrEqual:
|
|
switch op {
|
|
case framework.Operator_BinaryLessThan, framework.Operator_BinaryLessOrEqual:
|
|
res, err = callComparisonFunction(ctx, framework.Operator_BinaryLessThan, &leftLiteral, &rightLiteral)
|
|
default:
|
|
res, err = callComparisonFunction(ctx, framework.Operator_BinaryGreaterThan, &leftLiteral, &rightLiteral)
|
|
}
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if res == true {
|
|
return true, nil
|
|
}
|
|
// If equals fields are equal, move onto the next field, else return false
|
|
res, err = callComparisonFunction(ctx, framework.Operator_BinaryEqual, &leftLiteral, &rightLiteral)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if res == false {
|
|
return false, nil
|
|
}
|
|
default:
|
|
return false, fmt.Errorf("unsupported binary operator: %s", op)
|
|
}
|
|
}
|
|
|
|
if hasNull {
|
|
return nil, nil
|
|
}
|
|
|
|
// Every field is equal
|
|
switch op {
|
|
case framework.Operator_BinaryEqual, framework.Operator_BinaryLessOrEqual, framework.Operator_BinaryGreaterOrEqual:
|
|
return true, nil
|
|
default:
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
// checkRecordArgs asserts that |v1| and |v2| are both []pgtypes.RecordValue, and that they have the same number of
|
|
// elements, then returns them. If any problems were detected, an error is returned instead.
|
|
func checkRecordArgs(v1, v2 interface{}) (leftRecord, rightRecord []pgtypes.RecordValue, err error) {
|
|
var ok bool
|
|
leftRecord, ok = v1.([]pgtypes.RecordValue)
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("expected a RecordValue, but got %T", v1)
|
|
}
|
|
rightRecord, ok = v2.([]pgtypes.RecordValue)
|
|
if !ok {
|
|
return nil, nil, fmt.Errorf("expected a RecordValue, but got %T", v2)
|
|
}
|
|
if len(leftRecord) != len(rightRecord) {
|
|
return nil, nil, fmt.Errorf("unequal number of entries in row expressions")
|
|
}
|
|
|
|
return leftRecord, rightRecord, nil
|
|
}
|
|
|
|
// callComparisonFunction invokes the binary comparison function for the specified operator |op| with the two arguments
|
|
// |leftLiteral| and |rightLiteral|. The result and any error are returned.
|
|
func callComparisonFunction(ctx *sql.Context, op framework.Operator, leftLiteral, rightLiteral sql.Expression) (result any, err error) {
|
|
intermediateFunction := framework.GetBinaryFunction(op)
|
|
compiledFunction := intermediateFunction.Compile(
|
|
ctx, "_internal_record_comparison_function", leftLiteral, rightLiteral)
|
|
return compiledFunction.Eval(ctx, nil)
|
|
}
|