diff --git a/pkg/sql/plan2/function/operator/like.go b/pkg/sql/plan2/function/operator/like.go new file mode 100644 index 0000000000000000000000000000000000000000..17d0fa06e9d390c2ab546a8a6fc070206fe22f6e --- /dev/null +++ b/pkg/sql/plan2/function/operator/like.go @@ -0,0 +1,174 @@ +// 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 ( + "errors" + "github.com/matrixorigin/matrixone/pkg/container/nulls" + "github.com/matrixorigin/matrixone/pkg/container/types" + "github.com/matrixorigin/matrixone/pkg/container/vector" + "github.com/matrixorigin/matrixone/pkg/encoding" + "github.com/matrixorigin/matrixone/pkg/vectorize/like" + "github.com/matrixorigin/matrixone/pkg/vm/process" +) + +var ( + errUnexpected = errors.New("unexpected case for LIKE operator") +) + +func Like(vectors []*vector.Vector, proc *process.Process) (*vector.Vector, error) { + lv, rv := vectors[0], vectors[1] + lvs, rvs := lv.Col.(*types.Bytes), rv.Col.(*types.Bytes) + rtl := 8 + + if lv.IsScalarNull() || rv.IsScalarNull() { + return proc.AllocScalarNullVector(types.Type{Oid: types.T_bool}), nil + } + + switch { + case !lv.IsScalar() && rv.IsScalar(): + vec, err := proc.AllocVector(types.Type{Oid: types.T_bool}, int64(len(lvs.Offsets)*rtl)) + if err != nil { + return nil, err + } + rs := encoding.DecodeInt64Slice(vec.Data) + rs = rs[:len(lvs.Lengths)] + if nulls.Any(lv.Nsp) { + rs, err = like.BtSliceNullAndConst(lvs, rvs.Get(0), lv.Nsp.Np, rs) + if err != nil { + return nil, err + } + vec.Nsp = lv.Nsp + } else { + rs, err = like.BtSliceAndConst(lvs, rvs.Get(0), rs) + if err != nil { + return nil, err + } + } + col := make([]bool, len(lvs.Offsets)) + rsi := 0 + for i := 0; i < len(col); i++ { + if rsi >= len(rs) { + break + } + if int64(i) == rs[rsi] { + col[i] = true + rsi++ + } else { + col[i] = false + } + } + vector.SetCol(vec, col) + return vec, nil + case lv.IsScalar() && rv.IsScalar(): // in our design, this case should deal while pruning extends. + vec := proc.AllocScalarVector(types.Type{Oid: types.T_bool}) + rs := make([]int64, 1) + rs, err := like.BtConstAndConst(lvs.Get(0), rvs.Get(0), rs) + if err != nil { + return nil, err + } + col := make([]bool, 1) + if rs == nil { + col[0] = false + } else { + col[0] = rs[0] == int64(0) + } + vector.SetCol(vec, col) + return vec, nil + case lv.IsScalar() && !rv.IsScalar(): + vec, err := proc.AllocVector(types.Type{Oid: types.T_bool}, int64(len(rvs.Offsets)*rtl)) + if err != nil { + return nil, err + } + rs := encoding.DecodeInt64Slice(vec.Data) + rs = rs[:len(rvs.Lengths)] + if nulls.Any(rv.Nsp) { + rs, err = like.BtConstAndSliceNull(lvs.Get(0), rvs, rv.Nsp.Np, rs) + if err != nil { + return nil, err + } + vec.Nsp = rv.Nsp + } else { + rs, err = like.BtConstAndSlice(lvs.Get(0), rvs, rs) + if err != nil { + return nil, err + } + } + col := make([]bool, len(rvs.Offsets)) + rsi := 0 + for i := 0; i < len(col); i++ { + if rsi >= len(rs) { + break + } + if int64(i) == rs[rsi] { + col[i] = true + rsi++ + } else { + col[i] = false + } + } + vector.SetCol(vec, col) + return vec, nil + case !lv.IsScalar() && !rv.IsScalar(): + vec, err := proc.AllocVector(types.Type{Oid: types.T_bool}, int64(len(lvs.Offsets)*rtl)) + if err != nil { + return nil, err + } + rs := encoding.DecodeInt64Slice(vec.Data) + rs = rs[:len(rvs.Lengths)] + if nulls.Any(rv.Nsp) && nulls.Any(lv.Nsp) { + nsp := lv.Nsp.Or(rv.Nsp) + rs, err = like.BtSliceNullAndSliceNull(lvs, rvs, nsp.Np, rs) + if err != nil { + return nil, err + } + vec.Nsp = nsp + } else if nulls.Any(rv.Nsp) && !nulls.Any(lv.Nsp) { + rs, err = like.BtSliceNullAndSliceNull(lvs, rvs, rv.Nsp.Np, rs) + if err != nil { + return nil, err + } + vec.Nsp = rv.Nsp + } else if !nulls.Any(rv.Nsp) && nulls.Any(lv.Nsp) { + rs, err = like.BtSliceNullAndSliceNull(lvs, rvs, lv.Nsp.Np, rs) + if err != nil { + return nil, err + } + //vector.SetCol(vec, rs) + vec.Nsp = lv.Nsp + } else { + rs, err = like.BtSliceAndSlice(lvs, rvs, rs) + if err != nil { + return nil, err + } + } + col := make([]bool, len(lvs.Offsets)) + rsi := 0 + for i := 0; i < len(col); i++ { + if rsi >= len(rs) { + break + } + if int64(i) == rs[rsi] { + col[i] = true + rsi++ + } else { + col[i] = false + } + } + vector.SetCol(vec, col) + return vec, nil + } + return nil, errUnexpected +} diff --git a/pkg/sql/plan2/function/operator/like_test.go b/pkg/sql/plan2/function/operator/like_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b538cdd36499624ca02c24dea3908873aea2a1f1 --- /dev/null +++ b/pkg/sql/plan2/function/operator/like_test.go @@ -0,0 +1,198 @@ +// 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/types" + "github.com/matrixorigin/matrixone/pkg/container/vector" + "github.com/matrixorigin/matrixone/pkg/vm/mheap" + "github.com/matrixorigin/matrixone/pkg/vm/mmu/guest" + "github.com/matrixorigin/matrixone/pkg/vm/mmu/host" + "github.com/matrixorigin/matrixone/pkg/vm/process" + "github.com/stretchr/testify/require" + "testing" +) + +func TestLpadVarchar(t *testing.T) { + cases := []struct { + name string + vecs []*vector.Vector + proc *process.Process + wantBytes []bool + }{ + { + name: "TEST01", + vecs: makeLikeVectors("RUNOOB.COM", "%COM", true, true), + proc: process.New(mheap.New(nil)), + wantBytes: []bool{true}, + }, + { + name: "TEST02", + vecs: makeLikeVectors("aaa", "aaa", true, true), + proc: process.New(mheap.New(nil)), + wantBytes: []bool{true}, + }, + { + name: "TEST03", + vecs: makeLikeVectors("123", "1%", true, true), + proc: process.New(mheap.New(nil)), + wantBytes: []bool{true}, + }, + { + name: "TEST04", + vecs: makeLikeVectors("SALESMAN", "%SAL%", true, true), + proc: process.New(mheap.New(nil)), + wantBytes: []bool{true}, + }, + { + name: "TEST05", + vecs: makeLikeVectors("MANAGER@@@", "MAN_", true, true), + proc: process.New(mheap.New(nil)), + wantBytes: []bool{false}, + }, + { + name: "TEST06", + vecs: makeLikeVectors("MANAGER@@@", "_", true, true), + proc: process.New(mheap.New(nil)), + wantBytes: []bool{false}, + }, + { + name: "TEST07", + vecs: makeLikeVectors("hello@world", "hello_world", true, true), + proc: process.New(mheap.New(nil)), + wantBytes: []bool{true}, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + likeRes, err := Like(c.vecs, c.proc) + if err != nil { + t.Fatal(err) + } + require.Equal(t, c.wantBytes, likeRes.Col.([]bool)) + }) + } +} + +func TestLpadVarchar2(t *testing.T) { + procs := makeProcess() + cases := []struct { + name string + vecs []*vector.Vector + proc *process.Process + wantBytes []bool + wantScalar bool + }{ + { + name: "TEST01", + vecs: makeLikeVectors("RUNOOB.COM", "%COM", false, true), + proc: procs, + wantBytes: []bool{true}, + wantScalar: false, + }, + { + name: "TEST02", + vecs: makeLikeVectors("aaa", "aaa", false, true), + proc: procs, + wantBytes: []bool{true}, + wantScalar: false, + }, + { + name: "TEST03", + vecs: makeLikeVectors("123", "1%", false, true), + proc: procs, + wantBytes: []bool{true}, + wantScalar: false, + }, + { + name: "TEST04", + vecs: makeLikeVectors("SALESMAN", "%SAL%", false, true), + proc: procs, + wantBytes: []bool{true}, + wantScalar: false, + }, + { + name: "TEST05", + vecs: makeLikeVectors("MANAGER@@@", "MAN_", false, true), + proc: procs, + wantBytes: []bool{false}, + wantScalar: false, + }, + { + name: "TEST06", + vecs: makeLikeVectors("MANAGER@@@", "_", false, true), + proc: procs, + wantBytes: []bool{false}, + wantScalar: false, + }, + { + name: "TEST07", + vecs: makeLikeVectors("hello@world", "hello_world", false, true), + proc: procs, + wantBytes: []bool{true}, + wantScalar: false, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + likeRes, err := Like(c.vecs, c.proc) + if err != nil { + t.Fatal(err) + } + require.Equal(t, c.wantBytes, likeRes.Col.([]bool)) + require.Equal(t, c.wantScalar, likeRes.IsScalar()) + }) + } +} + +func makeProcess() *process.Process { + hm := host.New(1 << 40) + gm := guest.New(1<<40, hm) + return process.New(mheap.New(gm)) +} + +func makeLikeVectors(src string, patten string, isSrcConst bool, isPattenConst bool) []*vector.Vector { + resVectors := make([]*vector.Vector, 2) + srcBytes := &types.Bytes{ + Data: []byte(src), + Offsets: []uint32{0}, + Lengths: []uint32{uint32(len(src))}, + } + pattenBytes := &types.Bytes{ + Data: []byte(patten), + Offsets: []uint32{0}, + Lengths: []uint32{uint32(len(patten))}, + } + + resVectors[0] = &vector.Vector{ + Col: srcBytes, + Nsp: &nulls.Nulls{}, + Typ: types.Type{Oid: types.T_varchar, Size: 24}, + IsConst: isSrcConst, + Length: 10, + } + + resVectors[1] = &vector.Vector{ + Col: pattenBytes, + Nsp: &nulls.Nulls{}, + Typ: types.Type{Oid: types.T_varchar, Size: 24}, + IsConst: isPattenConst, + Length: 10, + } + return resVectors +} diff --git a/pkg/sql/plan2/function/operators.go b/pkg/sql/plan2/function/operators.go index 89e867b4bb30c45a1c4d6f818fa23aecb2c0a45f..c7cbc6b48ed153137e3f5ada15f56d899feeca9b 100644 --- a/pkg/sql/plan2/function/operators.go +++ b/pkg/sql/plan2/function/operators.go @@ -1282,10 +1282,7 @@ var operators = map[int][]Function{ Layout: BINARY_LOGICAL_OPERATOR, Args: nil, ReturnTyp: types.T_bool, - Fn: func(vs []*vector.Vector, proc *process.Process) (*vector.Vector, error) { - _, _ = vs[0].Col.(*types.Bytes), vs[1].Col.(*types.Bytes) - return nil, nil - }, + Fn: operator.Like, TypeCheckFn: func(inputTypes []types.T, _ []types.T) (match bool) { if len(inputTypes) != 2 { return false