From f5ebc6dc61304c174861200283754a017c7c3afa Mon Sep 17 00:00:00 2001
From: ou yuanning <45346669+ouyuanning@users.noreply.github.com>
Date: Tue, 16 Aug 2022 14:20:11 +0800
Subject: [PATCH] Add bit or/and/xor function (#4471)

Add bit or/and/xor function support.
(they are bit operator, not bool, not agg)

Approved by: @aressu1985, @aunjgr
---
 pkg/sql/plan/base_binder.go                   |   6 +
 pkg/sql/plan/function/function_id.go          |   6 +
 pkg/sql/plan/function/operator/op_bit.go      | 102 +++++++
 pkg/sql/plan/function/operator/op_bit_test.go | 237 +++++++++++++++
 pkg/sql/plan/function/operators.go            | 282 ++++++++++++++++++
 .../cases/function/func_string_lpad_rpad.test |   2 +-
 6 files changed, 634 insertions(+), 1 deletion(-)
 create mode 100644 pkg/sql/plan/function/operator/op_bit.go
 create mode 100644 pkg/sql/plan/function/operator/op_bit_test.go

diff --git a/pkg/sql/plan/base_binder.go b/pkg/sql/plan/base_binder.go
index d7b96790f..043f1de3a 100644
--- a/pkg/sql/plan/base_binder.go
+++ b/pkg/sql/plan/base_binder.go
@@ -409,6 +409,12 @@ func (b *baseBinder) bindBinaryExpr(astExpr *tree.BinaryExpr, depth int32, isRoo
 		return b.bindFuncExprImplByAstExpr("/", []tree.Expr{astExpr.Left, astExpr.Right}, depth)
 	case tree.INTEGER_DIV:
 		return b.bindFuncExprImplByAstExpr("div", []tree.Expr{astExpr.Left, astExpr.Right}, depth)
+	case tree.BIT_XOR:
+		return b.bindFuncExprImplByAstExpr("^", []tree.Expr{astExpr.Left, astExpr.Right}, depth)
+	case tree.BIT_OR:
+		return b.bindFuncExprImplByAstExpr("|", []tree.Expr{astExpr.Left, astExpr.Right}, depth)
+	case tree.BIT_AND:
+		return b.bindFuncExprImplByAstExpr("&", []tree.Expr{astExpr.Left, astExpr.Right}, depth)
 	}
 	return nil, errors.New("", fmt.Sprintf("'%v' operator is not supported now", astExpr.Op.ToString()))
 }
diff --git a/pkg/sql/plan/function/function_id.go b/pkg/sql/plan/function/function_id.go
index a307d69e6..b956472ca 100644
--- a/pkg/sql/plan/function/function_id.go
+++ b/pkg/sql/plan/function/function_id.go
@@ -50,6 +50,9 @@ const (
 	ISNOT                  //ISNOT
 	ISNULL                 //ISNULL
 	ISNOTNULL              //ISNOTNULL
+	OP_BIT_AND             // &
+	OP_BIT_OR              // |
+	OP_BIT_XOR             // ^
 
 	ABS            // ABS
 	ACOS           // ACOS
@@ -273,6 +276,9 @@ var functionIdRegister = map[string]int32{
 	"ifnull":      ISNULL,
 	"is_not_null": ISNOTNULL,
 	"isnotnull":   ISNOTNULL,
+	"&":           OP_BIT_AND,
+	"|":           OP_BIT_OR,
+	"^":           OP_BIT_XOR,
 	// aggregate
 	"max":                   MAX,
 	"min":                   MIN,
diff --git a/pkg/sql/plan/function/operator/op_bit.go b/pkg/sql/plan/function/operator/op_bit.go
new file mode 100644
index 000000000..f466da69a
--- /dev/null
+++ b/pkg/sql/plan/function/operator/op_bit.go
@@ -0,0 +1,102 @@
+// Copyright 2022 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 operator
+
+import (
+	"github.com/matrixorigin/matrixone/pkg/container/nulls"
+	"github.com/matrixorigin/matrixone/pkg/container/vector"
+	"github.com/matrixorigin/matrixone/pkg/vm/process"
+	"golang.org/x/exp/constraints"
+)
+
+type opBitT interface {
+	constraints.Integer
+}
+
+type opBitFun[T opBitT] func(v1, v2 T) T
+
+func opBitXor[T opBitT](v1, v2 T) T {
+	return v1 ^ v2
+}
+
+func opBitOr[T opBitT](v1, v2 T) T {
+	return v1 | v2
+}
+
+func opBitAnd[T opBitT](v1, v2 T) T {
+	return v1 & v2
+}
+
+func OpBitAndFun[T opBitT](args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
+	return Arith[T, T](args, proc, args[0].GetType(), func(xs, ys, rs *vector.Vector) error {
+		return goOpBitGeneral(xs, ys, rs, opBitAnd[T])
+	})
+}
+
+func OpBitOrFun[T opBitT](args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
+	return Arith[T, T](args, proc, args[0].GetType(), func(xs, ys, rs *vector.Vector) error {
+		return goOpBitGeneral(xs, ys, rs, opBitOr[T])
+	})
+}
+
+func OpBitXorFun[T opBitT](args []*vector.Vector, proc *process.Process) (*vector.Vector, error) {
+	return Arith[T, T](args, proc, args[0].GetType(), func(xs, ys, rs *vector.Vector) error {
+		return goOpBitGeneral(xs, ys, rs, opBitXor[T])
+	})
+}
+
+func goOpBitGeneral[T opBitT](xs, ys, rs *vector.Vector, bfn opBitFun[T]) error {
+	xt, yt, rt := vector.MustTCols[T](xs), vector.MustTCols[T](ys), vector.MustTCols[T](rs)
+	if xs.IsScalar() {
+		if nulls.Any(ys.Nsp) {
+			for i, y := range yt {
+				if !nulls.Contains(rs.Nsp, uint64(i)) {
+					rt[i] = bfn(xt[0], y)
+				}
+			}
+		} else {
+			for i, y := range yt {
+				rt[i] = bfn(xt[0], y)
+			}
+		}
+		return nil
+	} else if ys.IsScalar() {
+		if nulls.Any(xs.Nsp) {
+			for i, x := range xt {
+				if !nulls.Contains(rs.Nsp, uint64(i)) {
+					rt[i] = bfn(x, yt[0])
+				}
+			}
+		} else {
+			for i, x := range xt {
+				rt[i] = bfn(x, yt[0])
+			}
+		}
+		return nil
+	} else {
+		if nulls.Any(rs.Nsp) {
+			for i, x := range xt {
+				if !nulls.Contains(rs.Nsp, uint64(i)) {
+					rt[i] = bfn(x, yt[i])
+				}
+			}
+		} else {
+			for i, x := range xt {
+				rt[i] = bfn(x, yt[i])
+			}
+		}
+		return nil
+	}
+}
diff --git a/pkg/sql/plan/function/operator/op_bit_test.go b/pkg/sql/plan/function/operator/op_bit_test.go
new file mode 100644
index 000000000..8d77f2d0d
--- /dev/null
+++ b/pkg/sql/plan/function/operator/op_bit_test.go
@@ -0,0 +1,237 @@
+// Copyright 2022 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 operator
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/matrixorigin/matrixone/pkg/container/vector"
+	"github.com/matrixorigin/matrixone/pkg/testutil"
+	"github.com/stretchr/testify/require"
+)
+
+func TestOpXorGeneral(t *testing.T) {
+	testCases := []arg{
+		{
+			info: "1 ^ 2", proc: testutil.NewProc(),
+			vs: []*vector.Vector{
+				testutil.MakeScalarInt64(1, 1),
+				testutil.MakeScalarInt64(2, 1),
+			},
+			match:  true,
+			err:    false,
+			expect: testutil.MakeScalarInt64(3, 1),
+		},
+
+		{
+			info: "-1 ^ 2", proc: testutil.NewProc(),
+			vs: []*vector.Vector{
+				testutil.MakeScalarInt64(-1, 1),
+				testutil.MakeScalarInt64(2, 1),
+			},
+			match:  true,
+			err:    false,
+			expect: testutil.MakeScalarInt64(-3, 1),
+		},
+
+		{
+			info: "null ^ 2", proc: testutil.NewProc(),
+			vs: []*vector.Vector{
+				testutil.MakeScalarNull(1),
+				testutil.MakeScalarInt64(2, 1),
+			},
+			match:  true,
+			err:    false,
+			expect: testutil.MakeScalarNull(1),
+		},
+
+		{
+			info: "a ^ 2", proc: testutil.NewProc(),
+			vs: []*vector.Vector{
+				testutil.MakeInt64Vector([]int64{-1, 0, 3, 0}, []uint64{1, 3}),
+				testutil.MakeScalarInt64(2, 1),
+			},
+			match:  true,
+			err:    false,
+			expect: testutil.MakeInt64Vector([]int64{-3, 0, 1, 0}, []uint64{1, 3}),
+		},
+
+		{
+			info: "a ^ b", proc: testutil.NewProc(),
+			vs: []*vector.Vector{
+				testutil.MakeInt64Vector([]int64{-1, 0, 3, 0}, []uint64{1, 3}),
+				testutil.MakeInt64Vector([]int64{2, 3, 2, 4}, []uint64{1, 3}),
+			},
+			match:  true,
+			err:    false,
+			expect: testutil.MakeInt64Vector([]int64{-3, 0, 1, 0}, []uint64{1, 3}),
+		},
+	}
+
+	for i, tc := range testCases {
+		t.Run(tc.info, func(t *testing.T) {
+			got, ergot := OpBitXorFun[int64](tc.vs, tc.proc)
+			if tc.err {
+				require.Errorf(t, ergot, fmt.Sprintf("case '%d' expected error, but no error happens", i))
+			} else {
+				require.NoError(t, ergot)
+				require.True(t, testutil.CompareVectors(tc.expect, got), "got vector is different with expected")
+			}
+		})
+	}
+}
+
+func TestOpOrGeneral(t *testing.T) {
+	testCases := []arg{
+		{
+			info: "1 | 2", proc: testutil.NewProc(),
+			vs: []*vector.Vector{
+				testutil.MakeScalarInt64(1, 1),
+				testutil.MakeScalarInt64(2, 1),
+			},
+			match:  true,
+			err:    false,
+			expect: testutil.MakeScalarInt64(3, 1),
+		},
+
+		{
+			info: "-1 | 2", proc: testutil.NewProc(),
+			vs: []*vector.Vector{
+				testutil.MakeScalarInt64(-1, 1),
+				testutil.MakeScalarInt64(2, 1),
+			},
+			match:  true,
+			err:    false,
+			expect: testutil.MakeScalarInt64(-1, 1),
+		},
+
+		{
+			info: "null | 2", proc: testutil.NewProc(),
+			vs: []*vector.Vector{
+				testutil.MakeScalarNull(1),
+				testutil.MakeScalarInt64(2, 1),
+			},
+			match:  true,
+			err:    false,
+			expect: testutil.MakeScalarNull(1),
+		},
+
+		{
+			info: "a | 2", proc: testutil.NewProc(),
+			vs: []*vector.Vector{
+				testutil.MakeInt64Vector([]int64{1, 0, 3, 0}, []uint64{1, 3}),
+				testutil.MakeScalarInt64(2, 1),
+			},
+			match:  true,
+			err:    false,
+			expect: testutil.MakeInt64Vector([]int64{3, 0, 3, 0}, []uint64{1, 3}),
+		},
+
+		{
+			info: "a | b", proc: testutil.NewProc(),
+			vs: []*vector.Vector{
+				testutil.MakeInt64Vector([]int64{1, 0, 3, 0}, []uint64{1, 3}),
+				testutil.MakeInt64Vector([]int64{2, 3, 2, 4}, []uint64{1, 3}),
+			},
+			match:  true,
+			err:    false,
+			expect: testutil.MakeInt64Vector([]int64{3, 0, 3, 0}, []uint64{1, 3}),
+		},
+	}
+
+	for i, tc := range testCases {
+		t.Run(tc.info, func(t *testing.T) {
+			got, ergot := OpBitOrFun[int64](tc.vs, tc.proc)
+			if tc.err {
+				require.Errorf(t, ergot, fmt.Sprintf("case '%d' expected error, but no error happens", i))
+			} else {
+				require.NoError(t, ergot)
+				require.True(t, testutil.CompareVectors(tc.expect, got), "got vector is different with expected")
+			}
+		})
+	}
+}
+
+func TestOpAndGeneral(t *testing.T) {
+	testCases := []arg{
+		{
+			info: "1 & 2", proc: testutil.NewProc(),
+			vs: []*vector.Vector{
+				testutil.MakeScalarInt64(1, 1),
+				testutil.MakeScalarInt64(2, 1),
+			},
+			match:  true,
+			err:    false,
+			expect: testutil.MakeScalarInt64(0, 1),
+		},
+
+		{
+			info: "-1 & 2", proc: testutil.NewProc(),
+			vs: []*vector.Vector{
+				testutil.MakeScalarInt64(-1, 1),
+				testutil.MakeScalarInt64(2, 1),
+			},
+			match:  true,
+			err:    false,
+			expect: testutil.MakeScalarInt64(2, 1),
+		},
+
+		{
+			info: "null & 2", proc: testutil.NewProc(),
+			vs: []*vector.Vector{
+				testutil.MakeScalarNull(1),
+				testutil.MakeScalarInt64(2, 1),
+			},
+			match:  true,
+			err:    false,
+			expect: testutil.MakeScalarNull(1),
+		},
+
+		{
+			info: "a & 2", proc: testutil.NewProc(),
+			vs: []*vector.Vector{
+				testutil.MakeInt64Vector([]int64{1, 0, 3, 0}, []uint64{1, 3}),
+				testutil.MakeScalarInt64(2, 1),
+			},
+			match:  true,
+			err:    false,
+			expect: testutil.MakeInt64Vector([]int64{0, 0, 2, 0}, []uint64{1, 3}),
+		},
+
+		{
+			info: "a & b", proc: testutil.NewProc(),
+			vs: []*vector.Vector{
+				testutil.MakeInt64Vector([]int64{1, 0, 3, 0}, []uint64{1, 3}),
+				testutil.MakeInt64Vector([]int64{2, 3, 2, 4}, []uint64{1, 3}),
+			},
+			match:  true,
+			err:    false,
+			expect: testutil.MakeInt64Vector([]int64{0, 0, 2, 0}, []uint64{1, 3}),
+		},
+	}
+
+	for i, tc := range testCases {
+		t.Run(tc.info, func(t *testing.T) {
+			got, ergot := OpBitAndFun[int64](tc.vs, tc.proc)
+			if tc.err {
+				require.Errorf(t, ergot, fmt.Sprintf("case '%d' expected error, but no error happens", i))
+			} else {
+				require.NoError(t, ergot)
+				require.True(t, testutil.CompareVectors(tc.expect, got), "got vector is different with expected")
+			}
+		})
+	}
+}
diff --git a/pkg/sql/plan/function/operators.go b/pkg/sql/plan/function/operators.go
index bd9b1b13f..b8493f093 100644
--- a/pkg/sql/plan/function/operators.go
+++ b/pkg/sql/plan/function/operators.go
@@ -427,6 +427,288 @@ var operators = map[int]Functions{
 		},
 	},
 
+	OP_BIT_XOR: {
+		Id: OP_BIT_XOR,
+		Overloads: []Function{
+			{
+				Index:  0,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_uint8,
+					types.T_uint8,
+				},
+				ReturnTyp: types.T_uint8,
+				Fn:        operator.OpBitXorFun[uint8],
+			},
+			{
+				Index:  1,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_int8,
+					types.T_int8,
+				},
+				ReturnTyp: types.T_int8,
+				Fn:        operator.OpBitXorFun[int8],
+			},
+			{
+				Index:  2,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_uint16,
+					types.T_uint16,
+				},
+				ReturnTyp: types.T_uint16,
+				Fn:        operator.OpBitXorFun[uint16],
+			},
+			{
+				Index:  3,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_int16,
+					types.T_int16,
+				},
+				ReturnTyp: types.T_int16,
+				Fn:        operator.OpBitXorFun[int16],
+			},
+			{
+				Index:  4,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_uint32,
+					types.T_uint32,
+				},
+				ReturnTyp: types.T_uint32,
+				Fn:        operator.OpBitXorFun[uint32],
+			},
+			{
+				Index:  5,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_int32,
+					types.T_int32,
+				},
+				ReturnTyp: types.T_int32,
+				Fn:        operator.OpBitXorFun[int32],
+			},
+			{
+				Index:  6,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_uint64,
+					types.T_uint64,
+				},
+				ReturnTyp: types.T_uint64,
+				Fn:        operator.OpBitXorFun[uint64],
+			},
+			{
+				Index:  7,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_int64,
+					types.T_int64,
+				},
+				ReturnTyp: types.T_int64,
+				Fn:        operator.OpBitXorFun[int64],
+			},
+		},
+	},
+
+	OP_BIT_OR: {
+		Id: OP_BIT_OR,
+		Overloads: []Function{
+			{
+				Index:  0,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_uint8,
+					types.T_uint8,
+				},
+				ReturnTyp: types.T_uint8,
+				Fn:        operator.OpBitOrFun[uint8],
+			},
+			{
+				Index:  1,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_int8,
+					types.T_int8,
+				},
+				ReturnTyp: types.T_int8,
+				Fn:        operator.OpBitOrFun[int8],
+			},
+			{
+				Index:  2,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_uint16,
+					types.T_uint16,
+				},
+				ReturnTyp: types.T_uint16,
+				Fn:        operator.OpBitOrFun[uint16],
+			},
+			{
+				Index:  3,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_int16,
+					types.T_int16,
+				},
+				ReturnTyp: types.T_int16,
+				Fn:        operator.OpBitOrFun[int16],
+			},
+			{
+				Index:  4,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_uint32,
+					types.T_uint32,
+				},
+				ReturnTyp: types.T_uint32,
+				Fn:        operator.OpBitOrFun[uint32],
+			},
+			{
+				Index:  5,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_int32,
+					types.T_int32,
+				},
+				ReturnTyp: types.T_int32,
+				Fn:        operator.OpBitOrFun[int32],
+			},
+			{
+				Index:  6,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_uint64,
+					types.T_uint64,
+				},
+				ReturnTyp: types.T_uint64,
+				Fn:        operator.OpBitOrFun[uint64],
+			},
+			{
+				Index:  7,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_int64,
+					types.T_int64,
+				},
+				ReturnTyp: types.T_int64,
+				Fn:        operator.OpBitOrFun[int64],
+			},
+		},
+	},
+
+	OP_BIT_AND: {
+		Id: OP_BIT_AND,
+		Overloads: []Function{
+			{
+				Index:  0,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_uint8,
+					types.T_uint8,
+				},
+				ReturnTyp: types.T_uint8,
+				Fn:        operator.OpBitAndFun[uint8],
+			},
+			{
+				Index:  1,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_int8,
+					types.T_int8,
+				},
+				ReturnTyp: types.T_int8,
+				Fn:        operator.OpBitAndFun[int8],
+			},
+			{
+				Index:  2,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_uint16,
+					types.T_uint16,
+				},
+				ReturnTyp: types.T_uint16,
+				Fn:        operator.OpBitAndFun[uint16],
+			},
+			{
+				Index:  3,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_int16,
+					types.T_int16,
+				},
+				ReturnTyp: types.T_int16,
+				Fn:        operator.OpBitAndFun[int16],
+			},
+			{
+				Index:  4,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_uint32,
+					types.T_uint32,
+				},
+				ReturnTyp: types.T_uint32,
+				Fn:        operator.OpBitAndFun[uint32],
+			},
+			{
+				Index:  5,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_int32,
+					types.T_int32,
+				},
+				ReturnTyp: types.T_int32,
+				Fn:        operator.OpBitAndFun[int32],
+			},
+			{
+				Index:  6,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_uint64,
+					types.T_uint64,
+				},
+				ReturnTyp: types.T_uint64,
+				Fn:        operator.OpBitAndFun[uint64],
+			},
+			{
+				Index:  7,
+				Flag:   plan.Function_STRICT,
+				Layout: COMPARISON_OPERATOR,
+				Args: []types.T{
+					types.T_int64,
+					types.T_int64,
+				},
+				ReturnTyp: types.T_int64,
+				Fn:        operator.OpBitAndFun[int64],
+			},
+		},
+	},
+
 	ISNOT: {
 		Id: ISNOT,
 		Overloads: []Function{
diff --git a/test/cases/function/func_string_lpad_rpad.test b/test/cases/function/func_string_lpad_rpad.test
index 8d31fe2c0..3e05ac8cd 100644
--- a/test/cases/function/func_string_lpad_rpad.test
+++ b/test/cases/function/func_string_lpad_rpad.test
@@ -89,7 +89,7 @@ SELECT ((+0) IN
 (32767.1)));
 
 
--- @bvt:issue#3619
+-- @bvt:issue#3588
 SELECT ((rpad(1.0,2048,1)) = ('4(') ^ (0.1));
 -- @bvt:issue
 
-- 
GitLab