Skip to content

Commit 1fcceb5

Browse files
authored
Support nil in Tablet (#98)
1 parent c8eaf12 commit 1fcceb5

File tree

6 files changed

+286
-7
lines changed

6 files changed

+286
-7
lines changed

.github/workflows/go.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ jobs:
2626
matrix:
2727
os: [macos-latest, ubuntu-latest, windows-latest]
2828
go: ['1.13', 'stable']
29+
exclude:
30+
- os: macos-latest
31+
go: '1.13'
2932
steps:
3033

3134
- name: Set up Go ${{ matrix.go }}

client/bitmap.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package client
21+
22+
type BitMap struct {
23+
size int
24+
bits []byte
25+
}
26+
27+
var BitUtil = []byte{1, 2, 4, 8, 16, 32, 64, 128}
28+
var UnmarkBitUtil = []byte{
29+
0xFE, // 11111110
30+
0xFD, // 11111101
31+
0xFB, // 11111011
32+
0xF7, // 11110111
33+
0xEF, // 11101111
34+
0xDF, // 11011111
35+
0xBF, // 10111111
36+
0x7F, // 01111111
37+
}
38+
39+
func NewBitMap(size int) *BitMap {
40+
bitMap := &BitMap{
41+
size: size,
42+
bits: make([]byte, (size+7)/8),
43+
}
44+
return bitMap
45+
}
46+
47+
func (b *BitMap) Mark(position int) {
48+
b.bits[position/8] |= BitUtil[position%8]
49+
}
50+
51+
func (b *BitMap) UnMark(position int) {
52+
b.bits[position/8] &= UnmarkBitUtil[position%8]
53+
}
54+
55+
func (b *BitMap) IsMarked(position int) bool {
56+
return (b.bits[position/8] & BitUtil[position%8]) != 0
57+
}
58+
59+
func (b *BitMap) IsAllUnmarked() bool {
60+
for i := 0; i < b.size/8; i++ {
61+
if b.bits[i] != 0 {
62+
return false
63+
}
64+
}
65+
for i := 0; i < b.size%8; i++ {
66+
if (b.bits[b.size/8] & BitUtil[i]) != 0 {
67+
return false
68+
}
69+
}
70+
return true
71+
}
72+
73+
func (b *BitMap) GetBits() []byte {
74+
return b.bits
75+
}

client/tablet.go

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ package client
2222
import (
2323
"bytes"
2424
"encoding/binary"
25-
"errors"
2625
"fmt"
2726
"reflect"
2827
"sort"
@@ -38,6 +37,7 @@ type Tablet struct {
3837
measurementSchemas []*MeasurementSchema
3938
timestamps []int64
4039
values []interface{}
40+
bitMaps []*BitMap
4141
maxRowNumber int
4242
RowSize int
4343
}
@@ -69,6 +69,24 @@ func (t *Tablet) Swap(i, j int) {
6969
sortedSlice[i], sortedSlice[j] = sortedSlice[j], sortedSlice[i]
7070
}
7171
}
72+
if t.bitMaps != nil {
73+
for _, bitMap := range t.bitMaps {
74+
if bitMap != nil {
75+
isNilI := bitMap.IsMarked(i)
76+
isNilJ := bitMap.IsMarked(j)
77+
if isNilI {
78+
bitMap.Mark(j)
79+
} else {
80+
bitMap.UnMark(j)
81+
}
82+
if isNilJ {
83+
bitMap.Mark(i)
84+
} else {
85+
bitMap.UnMark(i)
86+
}
87+
}
88+
}
89+
}
7290
t.timestamps[i], t.timestamps[j] = t.timestamps[j], t.timestamps[i]
7391
}
7492

@@ -81,18 +99,27 @@ func (t *Tablet) SetTimestamp(timestamp int64, rowIndex int) {
8199
}
82100

83101
func (t *Tablet) SetValueAt(value interface{}, columnIndex, rowIndex int) error {
84-
if value == nil {
85-
return errors.New("illegal argument value can't be nil")
86-
}
87102

88103
if columnIndex < 0 || columnIndex > len(t.measurementSchemas) {
89104
return fmt.Errorf("illegal argument columnIndex %d", columnIndex)
90105
}
91106

92-
if rowIndex < 0 || rowIndex > int(t.maxRowNumber) {
107+
if rowIndex < 0 || rowIndex > t.maxRowNumber {
93108
return fmt.Errorf("illegal argument rowIndex %d", rowIndex)
94109
}
95110

111+
if value == nil {
112+
// Init the bitMap to mark nil value
113+
if t.bitMaps == nil {
114+
t.bitMaps = make([]*BitMap, len(t.values))
115+
}
116+
if t.bitMaps[columnIndex] == nil {
117+
t.bitMaps[columnIndex] = NewBitMap(t.maxRowNumber)
118+
}
119+
// Mark the nil value position
120+
t.bitMaps[columnIndex].Mark(rowIndex)
121+
}
122+
96123
switch t.measurementSchemas[columnIndex].DataType {
97124
case BOOLEAN:
98125
values := t.values[columnIndex].([]bool)
@@ -167,11 +194,15 @@ func (t *Tablet) GetValueAt(columnIndex, rowIndex int) (interface{}, error) {
167194
return nil, fmt.Errorf("illegal argument columnIndex %d", columnIndex)
168195
}
169196

170-
if rowIndex < 0 || rowIndex > int(t.maxRowNumber) {
197+
if rowIndex < 0 || rowIndex > t.maxRowNumber {
171198
return nil, fmt.Errorf("illegal argument rowIndex %d", rowIndex)
172199
}
173200

174201
schema := t.measurementSchemas[columnIndex]
202+
203+
if t.bitMaps != nil && t.bitMaps[columnIndex] != nil && t.bitMaps[columnIndex].IsMarked(rowIndex) {
204+
return nil, nil
205+
}
175206
switch schema.DataType {
176207
case BOOLEAN:
177208
return t.values[columnIndex].([]bool)[rowIndex], nil
@@ -235,6 +266,15 @@ func (t *Tablet) getValuesBytes() ([]byte, error) {
235266
return nil, fmt.Errorf("illegal datatype %v", schema.DataType)
236267
}
237268
}
269+
if t.bitMaps != nil {
270+
for _, bitMap := range t.bitMaps {
271+
columnHasNil := bitMap != nil && !bitMap.IsAllUnmarked()
272+
binary.Write(buff, binary.BigEndian, columnHasNil)
273+
if columnHasNil {
274+
binary.Write(buff, binary.BigEndian, bitMap.GetBits()[0:t.RowSize/8+1])
275+
}
276+
}
277+
}
238278
return buff.Bytes(), nil
239279
}
240280

@@ -245,6 +285,7 @@ func (t *Tablet) Sort() error {
245285

246286
func (t *Tablet) Reset() {
247287
t.RowSize = 0
288+
t.bitMaps = nil
248289
}
249290

250291
func NewTablet(deviceId string, measurementSchemas []*MeasurementSchema, maxRowNumber int) (*Tablet, error) {

client/tablet_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,89 @@ func TestTablet_GetValueAt(t *testing.T) {
342342
}
343343
}
344344

345+
func TestTablet_GetNilValueAt(t *testing.T) {
346+
type args struct {
347+
columnIndex int
348+
rowIndex int
349+
}
350+
tests := []struct {
351+
name string
352+
args args
353+
want interface{}
354+
wantErr bool
355+
}{
356+
{
357+
name: "INT32",
358+
args: args{
359+
columnIndex: 0,
360+
rowIndex: 0,
361+
},
362+
want: int32(256),
363+
wantErr: false,
364+
}, {
365+
name: "FLOAT64",
366+
args: args{
367+
columnIndex: 1,
368+
rowIndex: 0,
369+
},
370+
want: nil,
371+
wantErr: false,
372+
}, {
373+
name: "INT64",
374+
args: args{
375+
columnIndex: 2,
376+
rowIndex: 0,
377+
},
378+
want: int64(65535),
379+
wantErr: false,
380+
}, {
381+
name: "FLOAT32",
382+
args: args{
383+
columnIndex: 3,
384+
rowIndex: 0,
385+
},
386+
want: float32(36.5),
387+
wantErr: false,
388+
}, {
389+
name: "STRING",
390+
args: args{
391+
columnIndex: 4,
392+
rowIndex: 0,
393+
},
394+
want: "Hello World!",
395+
wantErr: false,
396+
}, {
397+
name: "BOOLEAN",
398+
args: args{
399+
columnIndex: 5,
400+
rowIndex: 0,
401+
},
402+
want: true,
403+
wantErr: false,
404+
},
405+
}
406+
if tablet, err := createTablet(1); err == nil {
407+
tablet.SetValueAt(int32(256), 0, 0)
408+
tablet.SetValueAt(nil, 1, 0)
409+
tablet.SetValueAt(int64(65535), 2, 0)
410+
tablet.SetValueAt(float32(36.5), 3, 0)
411+
tablet.SetValueAt("Hello World!", 4, 0)
412+
tablet.SetValueAt(true, 5, 0)
413+
for _, tt := range tests {
414+
t.Run(tt.name, func(t *testing.T) {
415+
got, err := tablet.GetValueAt(tt.args.columnIndex, tt.args.rowIndex)
416+
if (err != nil) != tt.wantErr {
417+
t.Errorf("Tablet.GetValueAt() error = %v, wantErr %v", err, tt.wantErr)
418+
return
419+
}
420+
if !reflect.DeepEqual(got, tt.want) {
421+
t.Errorf("Tablet.GetValueAt() = %v, want %v", got, tt.want)
422+
}
423+
})
424+
}
425+
}
426+
}
427+
345428
func TestTablet_Sort(t *testing.T) {
346429

347430
tests := []struct {

example/session_example.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,11 @@ func createTablet(rowCount int) (*client.Tablet, error) {
531531
ts++
532532
tablet.SetTimestamp(ts, row)
533533
tablet.SetValueAt(rand.Int31(), 0, row)
534-
tablet.SetValueAt(rand.Float64(), 1, row)
534+
if row%2 == 1 {
535+
tablet.SetValueAt(rand.Float64(), 1, row)
536+
} else {
537+
tablet.SetValueAt(nil, 1, row)
538+
}
535539
tablet.SetValueAt(rand.Int63(), 2, row)
536540
tablet.SetValueAt(rand.Float32(), 3, row)
537541
tablet.SetValueAt(fmt.Sprintf("Test Device %d", row+1), 4, row)

test/e2e/e2e_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,79 @@ func (s *e2eTestSuite) Test_InsertAlignedTablet() {
236236
assert.Equal(status, "12")
237237
s.session.DeleteStorageGroup("root.ln.**")
238238
}
239+
240+
func (s *e2eTestSuite) Test_InsertAlignedTabletWithNilValue() {
241+
var timeseries = []string{"root.ln.device1.**"}
242+
s.session.DeleteTimeseries(timeseries)
243+
if tablet, err := createTabletWithNil(12); err == nil {
244+
status, err := s.session.InsertAlignedTablet(tablet, false)
245+
s.checkError(status, err)
246+
tablet.Reset()
247+
} else {
248+
log.Fatal(err)
249+
}
250+
var timeout int64 = 1000
251+
ds, err := s.session.ExecuteQueryStatement("select count(status) from root.ln.device1", &timeout)
252+
assert := s.Require()
253+
assert.NoError(err)
254+
defer ds.Close()
255+
assert.True(ds.Next())
256+
var status string
257+
assert.NoError(ds.Scan(&status))
258+
assert.Equal(status, "12")
259+
s.session.DeleteStorageGroup("root.ln.**")
260+
}
261+
262+
func createTabletWithNil(rowCount int) (*client.Tablet, error) {
263+
tablet, err := client.NewTablet("root.ln.device1", []*client.MeasurementSchema{
264+
{
265+
Measurement: "restart_count",
266+
DataType: client.INT32,
267+
}, {
268+
Measurement: "price",
269+
DataType: client.DOUBLE,
270+
}, {
271+
Measurement: "tick_count",
272+
DataType: client.INT64,
273+
}, {
274+
Measurement: "temperature",
275+
DataType: client.FLOAT,
276+
}, {
277+
Measurement: "description",
278+
DataType: client.TEXT,
279+
},
280+
{
281+
Measurement: "status",
282+
DataType: client.BOOLEAN,
283+
},
284+
}, rowCount)
285+
286+
if err != nil {
287+
return nil, err
288+
}
289+
ts := time.Now().UTC().UnixNano() / 1000000
290+
for row := 0; row < int(rowCount); row++ {
291+
ts++
292+
tablet.SetTimestamp(ts, row)
293+
tablet.SetValueAt(rand.Int31(), 0, row)
294+
if row%2 == 1 {
295+
tablet.SetValueAt(rand.Float64(), 1, row)
296+
} else {
297+
tablet.SetValueAt(nil, 1, row)
298+
}
299+
tablet.SetValueAt(rand.Int63(), 2, row)
300+
if row%3 == 1 {
301+
tablet.SetValueAt(rand.Float32(), 3, row)
302+
} else {
303+
tablet.SetValueAt(nil, 3, row)
304+
}
305+
tablet.SetValueAt(fmt.Sprintf("Test Device %d", row+1), 4, row)
306+
tablet.SetValueAt(bool(ts%2 == 0), 5, row)
307+
tablet.RowSize++
308+
}
309+
return tablet, nil
310+
}
311+
239312
func createTablet(rowCount int) (*client.Tablet, error) {
240313
tablet, err := client.NewTablet("root.ln.device1", []*client.MeasurementSchema{
241314
{

0 commit comments

Comments
 (0)