diff --git a/pkg/sql/colexec/aggexec/bitops2.go b/pkg/sql/colexec/aggexec/bitops2.go index f5b9db84f1bfd..1454b498bf64a 100644 --- a/pkg/sql/colexec/aggexec/bitops2.go +++ b/pkg/sql/colexec/aggexec/bitops2.go @@ -15,6 +15,8 @@ package aggexec import ( + "bytes" + "math" "slices" "github.com/matrixorigin/matrixone/pkg/common/moerr" @@ -39,6 +41,11 @@ type bitOpExecFixed[T types.Ints | types.UInts] struct { type bitOpExecBytes struct { aggExec op bitOp + // width is the byte length of the argument type (BINARY(N)/VARBINARY(N)). + // It drives the length of the neutral value produced for empty or + // all-NULL groups so the result preserves the argument's byte length, + // matching MySQL binary-string aggregate semantics. + width int32 } func (op bitOp) compute(a, b uint64) uint64 { @@ -151,6 +158,27 @@ func (exec *bitOpExecFixed[T]) Flush() ([]*vector.Vector, error) { exec.state[i].vecs[0] = nil exec.state[i].length = 0 exec.state[i].capacity = 0 + + // Replace NULL entries with neutral values per MySQL semantics: + // BIT_AND: neutral is all bits set (max uint64) + // BIT_OR, BIT_XOR: neutral is 0 + if exec.op == bitAnd { + aggs := vector.MustFixedColNoTypeCheck[uint64](vecs[i]) + for j := 0; j < vecs[i].Length(); j++ { + if vecs[i].IsNull(uint64(j)) { + vecs[i].UnsetNull(uint64(j)) + aggs[j] = math.MaxUint64 + } + } + } else { + aggs := vector.MustFixedColNoTypeCheck[uint64](vecs[i]) + for j := 0; j < vecs[i].Length(); j++ { + if vecs[i].IsNull(uint64(j)) { + vecs[i].UnsetNull(uint64(j)) + aggs[j] = 0 + } + } + } } return vecs, nil } @@ -176,8 +204,10 @@ func (exec *bitOpExecBytes) BatchFill(offset int, groups []uint64, vectors []*ve x, y := exec.getXY(grp - 1) value := vectors[0].GetBytesAt(int(idx)) if exec.state[x].vecs[0].IsNull(uint64(y)) { + if err := vector.SetBytesAt(exec.state[x].vecs[0], int(y), value, exec.mp); err != nil { + return err + } exec.state[x].vecs[0].UnsetNull(uint64(y)) - vector.SetBytesAt(exec.state[x].vecs[0], int(y), value, exec.mp) } else { oldValue := exec.state[x].vecs[0].GetBytesAt(int(y)) // computeBytes will update oldValue in place @@ -209,9 +239,11 @@ func (exec *bitOpExecBytes) BatchMerge(next AggFuncExec, offset int, groups []ui continue } if exec.state[x1].vecs[0].IsNull(uint64(y1)) { - exec.state[x1].vecs[0].UnsetNull(uint64(y1)) otherValue := other.state[x2].vecs[0].GetBytesAt(int(y2)) - vector.SetBytesAt(exec.state[x1].vecs[0], int(y1), otherValue, exec.mp) + if err := vector.SetBytesAt(exec.state[x1].vecs[0], int(y1), otherValue, exec.mp); err != nil { + return err + } + exec.state[x1].vecs[0].UnsetNull(uint64(y1)) } else { oldValue := exec.state[x1].vecs[0].GetBytesAt(int(y1)) otherValue := other.state[x2].vecs[0].GetBytesAt(int(y2)) @@ -232,11 +264,40 @@ func (exec *bitOpExecBytes) SetExtraInformation(partialResult any, _ int) error func (exec *bitOpExecBytes) Flush() ([]*vector.Vector, error) { // transfer vector to result vecs := make([]*vector.Vector, len(exec.state)) + + // Neutral values for empty or all-NULL groups, per MySQL binary-string + // aggregate semantics. The neutral value has the same byte length as the + // argument type (BINARY(N)/VARBINARY(N)), driven by exec.width: + // BIT_AND: all bits set -> width bytes of 0xFF + // BIT_OR / BIT_XOR: all bits clear -> width bytes of 0x00 + var neutral []byte + if exec.op == bitAnd { + neutral = bytes.Repeat([]byte{0xFF}, int(exec.width)) + } else { + neutral = bytes.Repeat([]byte{0x00}, int(exec.width)) + } + for i := range vecs { vecs[i] = exec.state[i].vecs[0] exec.state[i].vecs[0] = nil exec.state[i].length = 0 exec.state[i].capacity = 0 + + for j := 0; j < vecs[i].Length(); j++ { + if vecs[i].IsNull(uint64(j)) { + vecs[i].UnsetNull(uint64(j)) + if err := vector.SetBytesAt(vecs[i], j, neutral, exec.mp); err != nil { + // Ownership of vecs[0..i] has been transferred from + // exec.state — free them before returning to avoid leaks. + for k := 0; k <= i; k++ { + if vecs[k] != nil { + vecs[k].Free(exec.mp) + } + } + return nil, err + } + } + } } return vecs, nil } @@ -261,6 +322,7 @@ func makeBitOpExecBytes(mp *mpool.MPool, id int64, param types.Type, op bitOp) A var bitOp bitOpExecBytes bitOp.mp = mp bitOp.op = op + bitOp.width = param.Width bitOp.aggInfo = aggInfo{ aggId: id, isDistinct: false, diff --git a/pkg/sql/colexec/aggexec/bitops2_test.go b/pkg/sql/colexec/aggexec/bitops2_test.go new file mode 100644 index 0000000000000..074ff52da84bf --- /dev/null +++ b/pkg/sql/colexec/aggexec/bitops2_test.go @@ -0,0 +1,530 @@ +// Copyright 2026 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 aggexec + +import ( + "math" + "testing" + + "github.com/matrixorigin/matrixone/pkg/common/mpool" + "github.com/matrixorigin/matrixone/pkg/container/types" + "github.com/matrixorigin/matrixone/pkg/container/vector" + "github.com/stretchr/testify/require" +) + +// makeBitAggExec creates a bit aggregate exec directly without going through +// the function registry (which is not set up in unit tests). +func makeBitAggExec(mp *mpool.MPool, aggID int64, param types.Type) AggFuncExec { + switch aggID { + case AggIdOfBitAnd: + return makeBitAndExec(mp, aggID, false, param) + case AggIdOfBitOr: + return makeBitOrExec(mp, aggID, false, param) + case AggIdOfBitXor: + return makeBitXorExec(mp, aggID, false, param) + default: + panic("unknown bit agg id") + } +} + +func TestBitOpsInt64(t *testing.T) { + mp := mpool.MustNewZero() + typ := types.T_int64.ToType() + + makeInputVec := func(values []int64, nulls []bool) *vector.Vector { + vec := vector.NewVec(typ) + for i, v := range values { + isNull := false + if nulls != nil && nulls[i] { + isNull = true + } + require.NoError(t, vector.AppendFixed(vec, v, isNull, mp)) + } + return vec + } + + t.Run("normal values", func(t *testing.T) { + // BIT_AND: 3 & 5 & 7 = 1 + // BIT_OR: 3 | 5 | 7 = 7 + // BIT_XOR: 3 ^ 5 ^ 7 = 1 (3^5=6, 6^7=1) + vec := makeInputVec([]int64{3, 5, 7}, nil) + defer vec.Free(mp) + + for _, tc := range []struct { + name string + aggID int64 + expect uint64 + }{ + {"bit_and", AggIdOfBitAnd, 1}, + {"bit_or", AggIdOfBitOr, 7}, + {"bit_xor", AggIdOfBitXor, 1}, + } { + t.Run(tc.name, func(t *testing.T) { + exec := makeBitAggExec(mp, tc.aggID, typ) + require.NoError(t, exec.GroupGrow(1)) + require.NoError(t, exec.BulkFill(0, []*vector.Vector{vec})) + + results, err := exec.Flush() + require.NoError(t, err) + require.Len(t, results, 1) + require.Equal(t, tc.expect, vector.MustFixedColNoTypeCheck[uint64](results[0])[0]) + + exec.Free() + for _, r := range results { + r.Free(mp) + } + }) + } + }) + + t.Run("empty group returns neutral values", func(t *testing.T) { + for _, tc := range []struct { + name string + aggID int64 + expect uint64 + }{ + {"bit_and empty", AggIdOfBitAnd, math.MaxUint64}, + {"bit_or empty", AggIdOfBitOr, 0}, + {"bit_xor empty", AggIdOfBitXor, 0}, + } { + t.Run(tc.name, func(t *testing.T) { + exec := makeBitAggExec(mp, tc.aggID, typ) + require.NoError(t, exec.GroupGrow(1)) + // no Fill/BulkFill/BatchFill called — simulating empty group + + results, err := exec.Flush() + require.NoError(t, err) + require.Len(t, results, 1) + require.Equal(t, tc.expect, vector.MustFixedColNoTypeCheck[uint64](results[0])[0]) + + exec.Free() + for _, r := range results { + r.Free(mp) + } + }) + } + }) + + t.Run("all-NULL group returns neutral values", func(t *testing.T) { + vec := makeInputVec([]int64{0, 0, 0}, []bool{true, true, true}) + defer vec.Free(mp) + + for _, tc := range []struct { + name string + aggID int64 + expect uint64 + }{ + {"bit_and all null", AggIdOfBitAnd, math.MaxUint64}, + {"bit_or all null", AggIdOfBitOr, 0}, + {"bit_xor all null", AggIdOfBitXor, 0}, + } { + t.Run(tc.name, func(t *testing.T) { + exec := makeBitAggExec(mp, tc.aggID, typ) + require.NoError(t, exec.GroupGrow(1)) + require.NoError(t, exec.BulkFill(0, []*vector.Vector{vec})) + + results, err := exec.Flush() + require.NoError(t, err) + require.Len(t, results, 1) + require.Equal(t, tc.expect, vector.MustFixedColNoTypeCheck[uint64](results[0])[0]) + + exec.Free() + for _, r := range results { + r.Free(mp) + } + }) + } + }) + + t.Run("mixed null and non-null", func(t *testing.T) { + // BIT_AND: 6 & NULL & 7 = 6 & 7 = 6 + // BIT_OR: 6 | NULL | 7 = 6 | 7 = 7 + // BIT_XOR: 6 ^ NULL ^ 7 = 6 ^ 7 = 1 + vec := makeInputVec([]int64{6, 0, 7}, []bool{false, true, false}) + defer vec.Free(mp) + + for _, tc := range []struct { + name string + aggID int64 + expect uint64 + }{ + {"bit_and mixed", AggIdOfBitAnd, 6}, + {"bit_or mixed", AggIdOfBitOr, 7}, + {"bit_xor mixed", AggIdOfBitXor, 1}, + } { + t.Run(tc.name, func(t *testing.T) { + exec := makeBitAggExec(mp, tc.aggID, typ) + require.NoError(t, exec.GroupGrow(1)) + require.NoError(t, exec.BulkFill(0, []*vector.Vector{vec})) + + results, err := exec.Flush() + require.NoError(t, err) + require.Len(t, results, 1) + require.Equal(t, tc.expect, vector.MustFixedColNoTypeCheck[uint64](results[0])[0]) + + exec.Free() + for _, r := range results { + r.Free(mp) + } + }) + } + }) +} + +func TestBitOpsMultipleGroups(t *testing.T) { + mp := mpool.MustNewZero() + typ := types.T_int64.ToType() + + makeInputVec := func(values []int64) *vector.Vector { + vec := vector.NewVec(typ) + for _, v := range values { + require.NoError(t, vector.AppendFixed(vec, v, false, mp)) + } + return vec + } + + t.Run("multiple groups with mixed data", func(t *testing.T) { + // Group 1: 3 & 5 & 7 = 1 + // Group 2: empty (no values) → neutral + // Group 3: 6 & 2 = 2 for AND, 6|2=6 for OR, 6^2=4 for XOR + vec := makeInputVec([]int64{3, 5, 7, 6, 2}) + defer vec.Free(mp) + // Row 0 → group 1, Row 1 → group 1, Row 2 → group 1 + // Row 3 → group 3, Row 4 → group 3 + // Group 2 intentionally left empty + groups := []uint64{1, 1, 1, 3, 3} + + for _, tc := range []struct { + name string + aggID int64 + expectGroup1 uint64 + expectGroup2 uint64 + expectGroup3 uint64 + }{ + {"bit_and multi-group", AggIdOfBitAnd, 1, math.MaxUint64, 2}, + {"bit_or multi-group", AggIdOfBitOr, 7, 0, 6}, + {"bit_xor multi-group", AggIdOfBitXor, 1, 0, 4}, + } { + t.Run(tc.name, func(t *testing.T) { + exec := makeBitAggExec(mp, tc.aggID, typ) + require.NoError(t, exec.GroupGrow(3)) + require.NoError(t, exec.BatchFill(0, groups, []*vector.Vector{vec})) + + results, err := exec.Flush() + require.NoError(t, err) + require.Len(t, results, 1) + + aggs := vector.MustFixedColNoTypeCheck[uint64](results[0]) + require.Equal(t, tc.expectGroup1, aggs[0]) + require.Equal(t, tc.expectGroup2, aggs[1]) + require.Equal(t, tc.expectGroup3, aggs[2]) + + exec.Free() + for _, r := range results { + r.Free(mp) + } + }) + } + }) +} + +func TestBitOpsBytes(t *testing.T) { + mp := mpool.MustNewZero() + + makeInputVec := func(typ types.Type, values [][]byte, nulls []bool) *vector.Vector { + vec := vector.NewVec(typ) + for i, v := range values { + isNull := false + if nulls != nil && nulls[i] { + isNull = true + } + require.NoError(t, vector.AppendBytes(vec, v, isNull, mp)) + } + return vec + } + + // bytesN builds a slice of n bytes filled with b. + bytesN := func(b byte, n int) []byte { + out := make([]byte, n) + for i := range out { + out[i] = b + } + return out + } + + t.Run("normal values byte bit_or", func(t *testing.T) { + typ := types.New(types.T_binary, 1, 0) + // BIT_OR: 0x01 | 0x02 | 0x04 = 0x07 + vec := makeInputVec(typ, [][]byte{{0x01}, {0x02}, {0x04}}, nil) + defer vec.Free(mp) + + exec := makeBitAggExec(mp, AggIdOfBitOr, typ) + require.NoError(t, exec.GroupGrow(1)) + require.NoError(t, exec.BulkFill(0, []*vector.Vector{vec})) + + results, err := exec.Flush() + require.NoError(t, err) + require.Len(t, results, 1) + require.Equal(t, []byte{0x07}, results[0].GetBytesAt(0)) + + exec.Free() + for _, r := range results { + r.Free(mp) + } + }) + + // Neutral values for empty or all-NULL groups must preserve the argument + // byte length: BIT_AND -> width bytes of 0xFF, BIT_OR/BIT_XOR -> width + // bytes of 0x00. Cover both BINARY(N) and VARBINARY(N) for several widths. + for _, dim := range []struct { + name string + oid types.T + width int32 + }{ + {"binary(3)", types.T_binary, 3}, + {"binary(10)", types.T_binary, 10}, + {"varbinary(4)", types.T_varbinary, 4}, + } { + typ := types.New(dim.oid, dim.width, 0) + w := int(dim.width) + + for _, tc := range []struct { + name string + aggID int64 + expect []byte + }{ + {"bit_and", AggIdOfBitAnd, bytesN(0xFF, w)}, + {"bit_or", AggIdOfBitOr, bytesN(0x00, w)}, + {"bit_xor", AggIdOfBitXor, bytesN(0x00, w)}, + } { + t.Run(dim.name+" empty group "+tc.name, func(t *testing.T) { + exec := makeBitAggExec(mp, tc.aggID, typ) + require.NoError(t, exec.GroupGrow(1)) + + results, err := exec.Flush() + require.NoError(t, err) + require.Len(t, results, 1) + require.False(t, results[0].IsNull(0)) + require.Equal(t, tc.expect, results[0].GetBytesAt(0)) + + exec.Free() + for _, r := range results { + r.Free(mp) + } + }) + + t.Run(dim.name+" all-NULL group "+tc.name, func(t *testing.T) { + vec := makeInputVec(typ, [][]byte{{0x01}, {0x02}}, []bool{true, true}) + defer vec.Free(mp) + + exec := makeBitAggExec(mp, tc.aggID, typ) + require.NoError(t, exec.GroupGrow(1)) + require.NoError(t, exec.BulkFill(0, []*vector.Vector{vec})) + + results, err := exec.Flush() + require.NoError(t, err) + require.Len(t, results, 1) + require.False(t, results[0].IsNull(0)) + require.Equal(t, tc.expect, results[0].GetBytesAt(0)) + + exec.Free() + for _, r := range results { + r.Free(mp) + } + }) + } + } + + t.Run("multiple groups one empty preserves width", func(t *testing.T) { + typ := types.New(types.T_binary, 3, 0) + // group 1 has values, group 2 is empty -> neutral, group 3 has values + vec := makeInputVec(typ, [][]byte{{0x0F, 0x0F, 0x0F}, {0xF0, 0xF0, 0xF0}}, nil) + defer vec.Free(mp) + groups := []uint64{1, 3} + + exec := makeBitAggExec(mp, AggIdOfBitOr, typ) + require.NoError(t, exec.GroupGrow(3)) + require.NoError(t, exec.BatchFill(0, groups, []*vector.Vector{vec})) + + results, err := exec.Flush() + require.NoError(t, err) + require.Len(t, results, 1) + require.Equal(t, []byte{0x0F, 0x0F, 0x0F}, results[0].GetBytesAt(0)) + require.False(t, results[0].IsNull(1)) + require.Equal(t, bytesN(0x00, 3), results[0].GetBytesAt(1)) + require.Equal(t, []byte{0xF0, 0xF0, 0xF0}, results[0].GetBytesAt(2)) + + exec.Free() + for _, r := range results { + r.Free(mp) + } + }) + + // Merging an all-NULL group into an empty group and then flushing must + // still produce a width-length neutral value (covers the bytes Merge path). + t.Run("merge all-NULL groups preserves neutral width", func(t *testing.T) { + typ := types.New(types.T_binary, 4, 0) + + exec1 := makeBitAggExec(mp, AggIdOfBitAnd, typ) + require.NoError(t, exec1.GroupGrow(1)) + exec2 := makeBitAggExec(mp, AggIdOfBitAnd, typ) + require.NoError(t, exec2.GroupGrow(1)) + // feed exec2 an all-NULL row so its single group stays NULL + nullVec := makeInputVec(typ, [][]byte{{0x01}}, []bool{true}) + defer nullVec.Free(mp) + require.NoError(t, exec2.BulkFill(0, []*vector.Vector{nullVec})) + + // merge exec2's group 0 into exec1's group 0 + require.NoError(t, exec1.Merge(exec2, 0, 0)) + + results, err := exec1.Flush() + require.NoError(t, err) + require.Len(t, results, 1) + require.False(t, results[0].IsNull(0)) + require.Equal(t, bytesN(0xFF, 4), results[0].GetBytesAt(0)) + + exec1.Free() + exec2.Free() + for _, r := range results { + r.Free(mp) + } + }) + + // When SetBytesAt fails during BulkFill (e.g. OOM on varlena allocation), + // the state must stay NULL and the error must propagate — never leave a + // non-NULL slot with a default/empty payload. + t.Run("BulkFill OOM leaves state NULL", func(t *testing.T) { + for _, dim := range []struct { + name string + oid types.T + width int32 + }{ + {"binary(64)", types.T_binary, 64}, + {"varbinary(64)", types.T_varbinary, 64}, + } { + t.Run(dim.name, func(t *testing.T) { + origCap := mpool.GlobalCap() + mpool.InitCap(100 * 1024 * 1024) // 100 MiB + t.Cleanup(func() { mpool.InitCap(origCap) }) + capped, err := mpool.NewMPool("test", 2*1024*1024, 0) // 2 MiB + require.NoError(t, err) + t.Cleanup(func() { mpool.DeleteMPool(capped) }) + + typ := types.New(dim.oid, dim.width, 0) + exec := makeBitAggExec(capped, AggIdOfBitAnd, typ) + t.Cleanup(exec.Free) + require.NoError(t, exec.GroupGrow(1)) + + // Exhaust the off-heap capacity. Track allocations so + // they can be freed after the test (t.Cleanup runs LIFO, + // so they are released before exec.Free and DeleteMPool). + var exhaust [][]byte + t.Cleanup(func() { + for _, b := range exhaust { + capped.Free(b) + } + }) + for { + b, e := capped.Alloc(4096, true) + if e != nil { + break + } + exhaust = append(exhaust, b) + } + + // Input vector from a normal (unlimited) mpool. + inputVec := vector.NewVec(typ) + t.Cleanup(func() { inputVec.Free(mp) }) + val := make([]byte, 64) + for i := range val { + val[i] = byte(i) + } + require.NoError(t, vector.AppendBytes(inputVec, val, false, mp)) + + err = exec.BulkFill(0, []*vector.Vector{inputVec}) + require.Error(t, err, "BulkFill should fail when SetBytesAt cannot allocate") + + // Verify the state slot is still NULL directly. + require.True(t, exec.(*bitOpExecBytes).state[0].vecs[0].IsNull(0), + "state slot must remain NULL after failed SetBytesAt") + }) + } + }) + + // Same contract for Merge: a failed SetBytesAt while copying the first + // non-NULL value into an empty target group must leave the target NULL. + t.Run("Merge OOM leaves state NULL", func(t *testing.T) { + for _, dim := range []struct { + name string + oid types.T + width int32 + }{ + {"binary(64)", types.T_binary, 64}, + {"varbinary(64)", types.T_varbinary, 64}, + } { + t.Run(dim.name, func(t *testing.T) { + origCap := mpool.GlobalCap() + mpool.InitCap(100 * 1024 * 1024) // 100 MiB + t.Cleanup(func() { mpool.InitCap(origCap) }) + capped, err := mpool.NewMPool("test", 2*1024*1024, 0) // 2 MiB + require.NoError(t, err) + t.Cleanup(func() { mpool.DeleteMPool(capped) }) + + typ := types.New(dim.oid, dim.width, 0) + + // exec1: empty target (all-NULL), uses capped mpool. + exec1 := makeBitAggExec(capped, AggIdOfBitAnd, typ) + t.Cleanup(exec1.Free) + require.NoError(t, exec1.GroupGrow(1)) + + // exec2: has a real value, uses normal (unlimited) mpool. + exec2 := makeBitAggExec(mp, AggIdOfBitAnd, typ) + t.Cleanup(exec2.Free) + require.NoError(t, exec2.GroupGrow(1)) + inputVec := vector.NewVec(typ) + t.Cleanup(func() { inputVec.Free(mp) }) + val := make([]byte, 64) + for i := range val { + val[i] = byte(i) + } + require.NoError(t, vector.AppendBytes(inputVec, val, false, mp)) + require.NoError(t, exec2.BulkFill(0, []*vector.Vector{inputVec})) + + // Exhaust the off-heap capacity. Freed via t.Cleanup (LIFO) + // before the execs and the capped pool are torn down. + var exhaust [][]byte + t.Cleanup(func() { + for _, b := range exhaust { + capped.Free(b) + } + }) + for { + b, e := capped.Alloc(4096, true) + if e != nil { + break + } + exhaust = append(exhaust, b) + } + + err = exec1.Merge(exec2, 0, 0) + require.Error(t, err, "Merge should fail when SetBytesAt cannot allocate") + + // Verify the state slot is still NULL directly. + require.True(t, exec1.(*bitOpExecBytes).state[0].vecs[0].IsNull(0), + "target slot must remain NULL after failed Merge SetBytesAt") + }) + } + }) +} diff --git a/test/distributed/cases/dtype/binary.result b/test/distributed/cases/dtype/binary.result index 3aa1d4157f963..20e140b235b25 100644 Binary files a/test/distributed/cases/dtype/binary.result and b/test/distributed/cases/dtype/binary.result differ diff --git a/test/distributed/cases/dtype/binary.test b/test/distributed/cases/dtype/binary.test index c59a04785fc83..349930604605f 100644 --- a/test/distributed/cases/dtype/binary.test +++ b/test/distributed/cases/dtype/binary.test @@ -163,6 +163,30 @@ select id, bit_and(b) from bit01 group by id; select id, bit_or(b) from bit01 group by id; select id, bit_xor(b) from bit01 group by id; +-- hex/length for binary aggregate neutral values +select id, hex(bit_and(b)), length(bit_and(b)) from bit01 group by id order by id; +select id, hex(bit_or(b)), length(bit_or(b)) from bit01 group by id order by id; +select id, hex(bit_xor(b)), length(bit_xor(b)) from bit01 group by id order by id; + +-- empty table without group by: zero-row aggregate returns neutral values +drop table if exists bit02; +create table bit02(id char(1), b binary(10)); +select hex(bit_and(b)), length(bit_and(b)) from bit02; +select hex(bit_or(b)), length(bit_or(b)) from bit02; +select hex(bit_xor(b)), length(bit_xor(b)) from bit02; + +-- mixed NULL and non-NULL rows in the same group: NULLs are ignored +insert into bit02 values('a', '111'); +insert into bit02 values('a', null); +insert into bit02 values('a', '110'); +insert into bit02 values('b', null); +insert into bit02 values('b', '001'); +insert into bit02 values('c', null); +select id, hex(bit_and(b)), length(bit_and(b)) from bit02 group by id order by id; +select id, hex(bit_or(b)), length(bit_or(b)) from bit02 group by id order by id; +select id, hex(bit_xor(b)), length(bit_xor(b)) from bit02 group by id order by id; +drop table bit02; + -- order by select * from bit01 order by b desc; select * from bit01 order by b; diff --git a/test/distributed/cases/function/func_aggr_avg.result b/test/distributed/cases/function/func_aggr_avg.result index 2181657ad57b2..ec0d935126dac 100644 --- a/test/distributed/cases/function/func_aggr_avg.result +++ b/test/distributed/cases/function/func_aggr_avg.result @@ -184,28 +184,28 @@ drop table t2; CREATE TABLE t1 (a int, b int); select count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1; ➤ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] 𝄀 -0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null +0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 select a,count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1 group by a; ➤ a[4,32,0] ¦ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] insert into t1 values (1,null); select a,count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1 group by a; ➤ a[4,32,0] ¦ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] 𝄀 -1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null +1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 insert into t1 values (1,null); insert into t1 values (2,null); select a,count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1 group by a; ➤ a[4,32,0] ¦ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] 𝄀 -1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null 𝄀 -2 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null +1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 𝄀 +2 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 insert into t1 values (2,1); select a,count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1 group by a; ➤ a[4,32,0] ¦ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] 𝄀 -1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null 𝄀 +1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 𝄀 2 ¦ 1 ¦ 1 ¦ 1.0 ¦ 0.0 ¦ 1 ¦ 1 ¦ 1 ¦ 1 insert into t1 values (3,1); select a,count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1 group by a; ➤ a[4,32,0] ¦ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] 𝄀 -1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null 𝄀 +1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 𝄀 2 ¦ 1 ¦ 1 ¦ 1.0 ¦ 0.0 ¦ 1 ¦ 1 ¦ 1 ¦ 1 𝄀 3 ¦ 1 ¦ 1 ¦ 1.0 ¦ 0.0 ¦ 1 ¦ 1 ¦ 1 ¦ 1 drop table t1; diff --git a/test/distributed/cases/function/func_aggr_bitwise.result b/test/distributed/cases/function/func_aggr_bitwise.result index dbc610f86fe91..62bc025578196 100644 --- a/test/distributed/cases/function/func_aggr_bitwise.result +++ b/test/distributed/cases/function/func_aggr_bitwise.result @@ -1,37 +1,37 @@ SELECT bit_and(null), bit_or(null), bit_xor(null); bit_and(null) bit_or(null) bit_xor(null) -null null null +18446744073709551615 0 0 CREATE TABLE t1 (a int, b int); INSERT INTO t1 VALUES (1,null); INSERT INTO t1 VALUES (1,null); INSERT INTO t1 VALUES (2,null); select a, BIT_AND(b), BIT_OR(b), BIT_XOR(b) from t1 group by a; a BIT_AND(b) BIT_OR(b) BIT_XOR(b) -1 null null null -2 null null null +1 18446744073709551615 0 0 +2 18446744073709551615 0 0 SELECT a, OCT(BIT_AND(b)), OCT(BIT_OR(b)), OCT(BIT_XOR(b)) FROM t1 GROUP BY a; a OCT(BIT_AND(b)) OCT(BIT_OR(b)) OCT(BIT_XOR(b)) -1 null null null -2 null null null +1 1777777777777777777777 0 0 +2 1777777777777777777777 0 0 SELECT OCT(BIT_AND(b)), OCT(BIT_OR(b)), OCT(BIT_XOR(b)) FROM t1; OCT(BIT_AND(b)) OCT(BIT_OR(b)) OCT(BIT_XOR(b)) -null null null +1777777777777777777777 0 0 INSERT INTO t1 VALUES (3,123); SELECT a, OCT(BIT_AND(b)), OCT(BIT_OR(b)), OCT(BIT_XOR(b)) FROM t1 GROUP BY a; a OCT(BIT_AND(b)) OCT(BIT_OR(b)) OCT(BIT_XOR(b)) -1 null null null -2 null null null +1 1777777777777777777777 0 0 +2 1777777777777777777777 0 0 3 173 173 173 INSERT INTO t1 VALUES (2,124124), (3, 4951312); SELECT a, OCT(BIT_AND(b)), OCT(BIT_OR(b)), OCT(BIT_XOR(b)) FROM t1 GROUP BY a; a OCT(BIT_AND(b)) OCT(BIT_OR(b)) OCT(BIT_XOR(b)) -1 null null null +1 1777777777777777777777 0 0 2 362334 362334 362334 3 20 22706573 22706553 INSERT INTO t1 VALUES (4,-4124124); SELECT a, OCT(BIT_AND(b)), OCT(BIT_OR(b)), OCT(BIT_XOR(b)) FROM t1 GROUP BY a; a OCT(BIT_AND(b)) OCT(BIT_OR(b)) OCT(BIT_XOR(b)) -1 null null null +1 1777777777777777777777 0 0 2 362334 362334 362334 3 20 22706573 22706553 4 1777777777777760211044 1777777777777760211044 1777777777777760211044 @@ -162,11 +162,11 @@ invalid argument aggregate function bit_and, bad value [DOUBLE] create table t1(a bigint); select BIT_AND(a),BIT_OR(a), BIT_XOR(a) from t1; BIT_AND(a) BIT_OR(a) BIT_XOR(a) -null null null +18446744073709551615 0 0 insert into t1 values(null),(null),(null),(null); select BIT_AND(a),BIT_OR(a), BIT_XOR(a) from t1; BIT_AND(a) BIT_OR(a) BIT_XOR(a) -null null null +18446744073709551615 0 0 insert into t1 values(12417249128419),(124124125124151),(5124125151415),(124125152651515); select BIT_AND(a),BIT_OR(a), BIT_XOR(a) from t1; BIT_AND(a) BIT_OR(a) BIT_XOR(a) @@ -206,18 +206,22 @@ INSERT INTO t1 VALUES (1,10),(1,20),(2,NULL),(2,NULL),(3,50); select Fld1, BIT_AND(Fld2),BIT_OR(Fld2), BIT_XOR(Fld2) as q from t1 group by Fld1 having q is not null; Fld1 BIT_AND(Fld2) BIT_OR(Fld2) q 1 0 30 30 +2 18446744073709551615 0 0 3 50 50 50 select Fld1, BIT_AND(Fld2),BIT_OR(Fld2), BIT_XOR(Fld2) from t1 group by Fld1 having BIT_AND(Fld2) is not null; Fld1 BIT_AND(Fld2) BIT_OR(Fld2) BIT_XOR(Fld2) 1 0 30 30 +2 18446744073709551615 0 0 3 50 50 50 select Fld1, BIT_AND(Fld2),BIT_OR(Fld2), BIT_XOR(Fld2) from t1 group by Fld1 having BIT_OR(Fld2) is not null; Fld1 BIT_AND(Fld2) BIT_OR(Fld2) BIT_XOR(Fld2) 1 0 30 30 +2 18446744073709551615 0 0 3 50 50 50 select Fld1, BIT_AND(Fld2),BIT_OR(Fld2), BIT_XOR(Fld2) from t1 group by Fld1 having BIT_XOR(Fld2) is not null; Fld1 BIT_AND(Fld2) BIT_OR(Fld2) BIT_XOR(Fld2) 1 0 30 30 +2 18446744073709551615 0 0 3 50 50 50 drop table t1; SELECT BIT_AND(1)BIT_OR(3), BIT_AND(3)>BIT_XOR(5); diff --git a/test/distributed/cases/function/func_aggr_count.result b/test/distributed/cases/function/func_aggr_count.result index 0876f37a3e5a1..96639487d93b6 100644 --- a/test/distributed/cases/function/func_aggr_count.result +++ b/test/distributed/cases/function/func_aggr_count.result @@ -201,28 +201,28 @@ DROP TABLE t1; CREATE TABLE t1 (a int, b int); select count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1; ➤ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] 𝄀 -0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null +0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 select a,count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1 group by a; ➤ a[4,32,0] ¦ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] insert into t1 values (1,null); select a,count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1 group by a; ➤ a[4,32,0] ¦ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] 𝄀 -1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null +1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 insert into t1 values (1,null); insert into t1 values (2,null); select a,count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1 group by a; ➤ a[4,32,0] ¦ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] 𝄀 -1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null 𝄀 -2 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null +1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 𝄀 +2 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 insert into t1 values (2,1); select a,count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1 group by a; ➤ a[4,32,0] ¦ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] 𝄀 -1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null 𝄀 +1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 𝄀 2 ¦ 1 ¦ 1 ¦ 1.0 ¦ 0.0 ¦ 1 ¦ 1 ¦ 1 ¦ 1 insert into t1 values (3,1); select a,count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1 group by a; ➤ a[4,32,0] ¦ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] 𝄀 -1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null 𝄀 +1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 𝄀 2 ¦ 1 ¦ 1 ¦ 1.0 ¦ 0.0 ¦ 1 ¦ 1 ¦ 1 ¦ 1 𝄀 3 ¦ 1 ¦ 1 ¦ 1.0 ¦ 0.0 ¦ 1 ¦ 1 ¦ 1 ¦ 1 explain analyze select count(b) from t1; diff --git a/test/distributed/cases/function/func_aggr_max.result b/test/distributed/cases/function/func_aggr_max.result index a43d9edf8370a..cc3d7dc6802c0 100644 --- a/test/distributed/cases/function/func_aggr_max.result +++ b/test/distributed/cases/function/func_aggr_max.result @@ -228,28 +228,28 @@ drop table t2; CREATE TABLE t1 (a int, b int); select count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1; ➤ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] 𝄀 -0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null +0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 select a,count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1 group by a; ➤ a[4,32,0] ¦ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] insert into t1 values (1,null); select a,count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1 group by a; ➤ a[4,32,0] ¦ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] 𝄀 -1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null +1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 insert into t1 values (1,null); insert into t1 values (2,null); select a,count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1 group by a; ➤ a[4,32,0] ¦ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] 𝄀 -1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null 𝄀 -2 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null +1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 𝄀 +2 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 insert into t1 values (2,1); select a,count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1 group by a; ➤ a[4,32,0] ¦ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] 𝄀 -1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null 𝄀 +1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 𝄀 2 ¦ 1 ¦ 1 ¦ 1.0 ¦ 0.0 ¦ 1 ¦ 1 ¦ 1 ¦ 1 insert into t1 values (3,1); select a,count(b), sum(b), avg(b), std(b), min(b), max(b), bit_and(b), bit_or(b) from t1 group by a; ➤ a[4,32,0] ¦ count(b)[-5,64,0] ¦ sum(b)[-5,64,0] ¦ avg(b)[8,54,0] ¦ std(b)[8,54,0] ¦ min(b)[4,32,0] ¦ max(b)[4,32,0] ¦ bit_and(b)[-5,64,0] ¦ bit_or(b)[-5,64,0] 𝄀 -1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null ¦ null 𝄀 +1 ¦ 0 ¦ null ¦ null ¦ null ¦ null ¦ null ¦ 18446744073709551615 ¦ 0 𝄀 2 ¦ 1 ¦ 1 ¦ 1.0 ¦ 0.0 ¦ 1 ¦ 1 ¦ 1 ¦ 1 𝄀 3 ¦ 1 ¦ 1 ¦ 1.0 ¦ 0.0 ¦ 1 ¦ 1 ¦ 1 ¦ 1 drop table t1;