From c883119ebc8a7a52d3d898e59bee37f066d4704d Mon Sep 17 00:00:00 2001 From: ou yuanning <45346669+ouyuanning@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:25:28 +0800 Subject: [PATCH] fix bug: prefix_xx get incorrect result (#21075) fix bug: prefix_xx get incorrect result Approved by: @qingxinhome, @sukki37 --- pkg/sql/plan/function/func_prefix.go | 78 ++++++++++--- pkg/sql/plan/function/func_prefix_test.go | 132 ++++++++++++++++++++++ 2 files changed, 195 insertions(+), 15 deletions(-) create mode 100644 pkg/sql/plan/function/func_prefix_test.go diff --git a/pkg/sql/plan/function/func_prefix.go b/pkg/sql/plan/function/func_prefix.go index 11357c733dc4b..d32aca53b3b05 100644 --- a/pkg/sql/plan/function/func_prefix.go +++ b/pkg/sql/plan/function/func_prefix.go @@ -34,8 +34,9 @@ func PrefixEq(parameters []*vector.Vector, result vector.FunctionResultWrapper, res := vector.MustFixedColWithTypeCheck[bool](result.GetResultVector()) lcol, larea := vector.MustVarlenaRawData(lvec) + lvecHasNull := lvec.HasNull() - if lvec.GetSorted() { + if lvec.GetSorted() && !lvecHasNull { lowerBound := sort.Search(len(lcol), func(i int) bool { return bytes.Compare(rval, lcol[i].GetByteSlice(larea)) <= 0 }) @@ -55,8 +56,21 @@ func PrefixEq(parameters []*vector.Vector, result vector.FunctionResultWrapper, res[i] = false } } else { - for i := 0; i < length; i++ { - res[i] = bytes.HasPrefix(lcol[i].GetByteSlice(larea), rval) + if lvecHasNull { + lNulls := lvec.GetNulls() + rNulls := result.GetResultVector().GetNulls() + for i := uint64(0); i < uint64(length); i++ { + if lNulls.Contains(i) { + res[i] = false + rNulls.Add(i) + } else { + res[i] = bytes.HasPrefix(lcol[i].GetByteSlice(larea), rval) + } + } + } else { + for i := 0; i < length; i++ { + res[i] = bytes.HasPrefix(lcol[i].GetByteSlice(larea), rval) + } } } @@ -70,8 +84,9 @@ func PrefixBetween(parameters []*vector.Vector, result vector.FunctionResultWrap res := vector.MustFixedColWithTypeCheck[bool](result.GetResultVector()) icol, iarea := vector.MustVarlenaRawData(ivec) + ivecHasNull := ivec.HasNull() - if ivec.GetSorted() { + if ivec.GetSorted() && !ivecHasNull { lowerBound := sort.Search(len(icol), func(i int) bool { return types.PrefixCompare(icol[i].GetByteSlice(iarea), lval) >= 0 }) @@ -90,9 +105,23 @@ func PrefixBetween(parameters []*vector.Vector, result vector.FunctionResultWrap res[i] = false } } else { - for i := 0; i < length; i++ { - val := icol[i].GetByteSlice(iarea) - res[i] = types.PrefixCompare(val, lval) >= 0 && types.PrefixCompare(val, rval) <= 0 + if ivecHasNull { + iNulls := ivec.GetNulls() + rNulls := result.GetResultVector().GetNulls() + for i := uint64(0); i < uint64(length); i++ { + if iNulls.Contains(i) { + res[i] = false + rNulls.Add(i) + } else { + val := icol[i].GetByteSlice(iarea) + res[i] = types.PrefixCompare(val, lval) >= 0 && types.PrefixCompare(val, rval) <= 0 + } + } + } else { + for i := 0; i < length; i++ { + val := icol[i].GetByteSlice(iarea) + res[i] = types.PrefixCompare(val, lval) >= 0 && types.PrefixCompare(val, rval) <= 0 + } } } @@ -132,8 +161,9 @@ func (op *implPrefixIn) doPrefixIn(parameters []*vector.Vector, result vector.Fu res := vector.MustFixedColWithTypeCheck[bool](result.GetResultVector()) lcol, larea := vector.MustVarlenaRawData(lvec) + lvecHasNull := lvec.HasNull() - if lvec.GetSorted() { + if lvec.GetSorted() && !lvecHasNull { rval := op.vals[0] rpos := 0 rlen := len(op.vals) @@ -155,13 +185,31 @@ func (op *implPrefixIn) doPrefixIn(parameters []*vector.Vector, result vector.Fu res[i] = bytes.HasPrefix(lval, rval) } } else { - for i := 0; i < length; i++ { - lval := lcol[i].GetByteSlice(larea) - rpos, _ := sort.Find(len(op.vals), func(j int) int { - return types.PrefixCompare(lval, op.vals[j]) - }) - - res[i] = rpos < len(op.vals) && bytes.HasPrefix(lval, op.vals[rpos]) + if lvecHasNull { + lNulls := lvec.GetNulls() + rNulls := result.GetResultVector().GetNulls() + for i := uint64(0); i < uint64(length); i++ { + if lNulls.Contains(i) { + res[i] = false + rNulls.Add(i) + } else { + lval := lcol[i].GetByteSlice(larea) + rpos, _ := sort.Find(len(op.vals), func(j int) int { + return types.PrefixCompare(lval, op.vals[j]) + }) + + res[i] = rpos < len(op.vals) && bytes.HasPrefix(lval, op.vals[rpos]) + } + } + } else { + for i := 0; i < length; i++ { + lval := lcol[i].GetByteSlice(larea) + rpos, _ := sort.Find(len(op.vals), func(j int) int { + return types.PrefixCompare(lval, op.vals[j]) + }) + + res[i] = rpos < len(op.vals) && bytes.HasPrefix(lval, op.vals[rpos]) + } } } diff --git a/pkg/sql/plan/function/func_prefix_test.go b/pkg/sql/plan/function/func_prefix_test.go new file mode 100644 index 0000000000000..e9040ed760246 --- /dev/null +++ b/pkg/sql/plan/function/func_prefix_test.go @@ -0,0 +1,132 @@ +// Copyright 2021 Matrix Origin +// +// 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 function + +import ( + "testing" + + "github.com/matrixorigin/matrixone/pkg/container/types" + "github.com/matrixorigin/matrixone/pkg/testutil" + "github.com/stretchr/testify/require" +) + +func TestPrefixEq(t *testing.T) { + tcs := []tcTemp{ + { + info: "& test prefix_eq", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"aa12", "bb22", "aa33", "aa44"}, []bool{false, false, false, false}), + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"aa"}, []bool{false, false, false}), + }, + expect: NewFunctionTestResult(types.T_bool.ToType(), false, + []bool{true, false, true, true}, []bool{false, false, false, false}), + }, + { + info: "& test prefix_eq with null", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"aa12", "bb22", "aa33", "aa44"}, []bool{false, false, true, false}), + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"aa"}, []bool{false, false, false}), + }, + expect: NewFunctionTestResult(types.T_bool.ToType(), false, + []bool{true, false, false, true}, []bool{false, false, true, false}), + }, + } + + proc := testutil.NewProcess() + for _, tc := range tcs { + fcTC := NewFunctionTestCase(proc, + tc.inputs, tc.expect, PrefixEq) + s, info := fcTC.Run() + require.True(t, s, info, tc.info) + } +} + +func TestPrefixIn(t *testing.T) { + tcs := []tcTemp{ + { + info: "& test prefix_in", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"aa12", "ab23", "bb34"}, []bool{false, false, false}), + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"aa", "bb", "ss"}, []bool{false, false, false}), + }, + expect: NewFunctionTestResult(types.T_bool.ToType(), false, + []bool{true, false, true}, []bool{false, false, false}), + }, + { + info: "& test prefix_in with null", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"aa12", "ab23", "bb34"}, []bool{false, false, true}), + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"aa", "bb", "ss"}, []bool{false, false, true}), + }, + expect: NewFunctionTestResult(types.T_bool.ToType(), false, + []bool{true, false, false}, []bool{false, false, true}), + }, + } + + proc := testutil.NewProcess() + for _, tc := range tcs { + fcTC := NewFunctionTestCase(proc, + tc.inputs, tc.expect, newImplPrefixIn().doPrefixIn) + s, info := fcTC.Run() + require.True(t, s, info, tc.info) + } +} + +func TestPrefixBetween(t *testing.T) { + tcs := []tcTemp{ + { + info: "& test prefix_in", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"aa11", "aa22", "bb22", "ss34"}, []bool{false, false, false, false}), + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"aa"}, []bool{false}), + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"kk"}, []bool{false}), + }, + expect: NewFunctionTestResult(types.T_bool.ToType(), false, + []bool{true, true, true, false}, []bool{false, false, false, false}), + }, + { + info: "& test prefix_in with null", + inputs: []FunctionTestInput{ + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"aa11", "aa22", "bb22", "ss34"}, []bool{false, true, false, false}), + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"aa"}, []bool{false}), + NewFunctionTestInput(types.T_varchar.ToType(), + []string{"kk"}, []bool{false}), + }, + expect: NewFunctionTestResult(types.T_bool.ToType(), false, + []bool{true, false, true, false}, []bool{false, true, false, false}), + }, + } + + proc := testutil.NewProcess() + for _, tc := range tcs { + fcTC := NewFunctionTestCase(proc, + tc.inputs, tc.expect, PrefixBetween) + s, info := fcTC.Run() + require.True(t, s, info, tc.info) + } +}