This is an automated email from the ASF dual-hosted git repository.

JackieTien97 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iotdb-client-go.git


The following commit(s) were added to refs/heads/main by this push:
     new 013e3d3  Support OBJECT data type in tablet write (#175)
013e3d3 is described below

commit 013e3d3021424c286863c199e87c2fa9ae7f1020
Author: shuwenwei <[email protected]>
AuthorDate: Fri Aug 21 08:54:02 2026 +0800

    Support OBJECT data type in tablet write (#175)
---
 client/column_decoder.go                      |   2 +-
 client/protocol.go                            |   3 +
 client/rpcdataset.go                          |  12 ++
 client/rpcdataset_test.go                     |  90 +++++++++++
 client/sessiondataset.go                      |  15 ++
 client/tablet.go                              |  55 ++++++-
 client/tablet_test.go                         | 116 +++++++++++++++
 client/utils.go                               |  24 +++
 client/utils_test.go                          |  32 ++++
 common/common.go                              | 205 ++++++++++++++++++++------
 database/column/column.go                     |   4 +
 database/column/{column.go => object.go}      |  80 ++++------
 database/column/{column.go => object_test.go} |  72 ++-------
 test/e2e/e2e_table_test.go                    | 126 ++++++++++++++++
 14 files changed, 679 insertions(+), 157 deletions(-)

diff --git a/client/column_decoder.go b/client/column_decoder.go
index 0898c4f..cd20b79 100644
--- a/client/column_decoder.go
+++ b/client/column_decoder.go
@@ -244,7 +244,7 @@ func (decoder *BinaryArrayColumnDecoder) ReadColumn(reader 
*bytes.Reader, dataTy
        //    | int32         | bytes |
        //    +---------------+-------+
 
-       if TEXT != dataType {
+       if TEXT != dataType && STRING != dataType && BLOB != dataType && OBJECT 
!= dataType {
                return nil, fmt.Errorf("invalid data type: %v", dataType)
        }
 
diff --git a/client/protocol.go b/client/protocol.go
index faedfad..111e642 100644
--- a/client/protocol.go
+++ b/client/protocol.go
@@ -39,6 +39,7 @@ const (
        DATE      TSDataType = 9
        BLOB      TSDataType = 10
        STRING    TSDataType = 11
+       OBJECT    TSDataType = 12
 )
 
 var tsTypeMap = map[string]TSDataType{
@@ -52,6 +53,7 @@ var tsTypeMap = map[string]TSDataType{
        "DATE":      DATE,
        "BLOB":      BLOB,
        "STRING":    STRING,
+       "OBJECT":    OBJECT,
 }
 
 var byteToTsDataType = map[byte]TSDataType{
@@ -65,6 +67,7 @@ var byteToTsDataType = map[byte]TSDataType{
        9:  DATE,
        10: BLOB,
        11: STRING,
+       12: OBJECT,
 }
 
 func GetDataTypeByStr(name string) (TSDataType, error) {
diff --git a/client/rpcdataset.go b/client/rpcdataset.go
index d41a765..cba4d4c 100644
--- a/client/rpcdataset.go
+++ b/client/rpcdataset.go
@@ -548,6 +548,12 @@ func (s *IoTDBRpcDataSet) 
getObjectByTsBlockIndex(tsBlockColumnIndex int32) (int
                } else {
                        return binary.GetValues(), nil
                }
+       case OBJECT:
+               if binary, err := 
s.curTsBlock.GetColumn(tsBlockColumnIndex).GetBinary(s.tsBlockIndex); err != 
nil {
+                       return nil, err
+               } else {
+                       return objectBytesToString(binary.GetValues())
+               }
        case DATE:
                if value, err := 
s.curTsBlock.GetColumn(tsBlockColumnIndex).GetInt(s.tsBlockIndex); err != nil {
                        return nil, err
@@ -645,6 +651,12 @@ func (s *IoTDBRpcDataSet) 
getStringByTsBlockColumnIndexAndDataType(index int32,
                } else {
                        return bytesToHexString(v.values), nil
                }
+       case OBJECT:
+               if v, err := 
s.curTsBlock.GetColumn(index).GetBinary(s.tsBlockIndex); err != nil {
+                       return "", err
+               } else {
+                       return objectBytesToString(v.values)
+               }
        case DATE:
                v, err := s.curTsBlock.GetColumn(index).GetInt(s.tsBlockIndex)
                if err != nil {
diff --git a/client/rpcdataset_test.go b/client/rpcdataset_test.go
new file mode 100644
index 0000000..d9a5450
--- /dev/null
+++ b/client/rpcdataset_test.go
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 client
+
+import (
+       "encoding/binary"
+       "strings"
+       "testing"
+)
+
+func newObjectSessionDataSet(t *testing.T, value []byte) *SessionDataSet {
+       t.Helper()
+
+       column, err := NewBinaryColumn(0, 1, nil, []*Binary{NewBinary(value)})
+       if err != nil {
+               t.Fatalf("NewBinaryColumn() error = %v", err)
+       }
+       block, err := NewTsBlock(1, nil, column)
+       if err != nil {
+               t.Fatalf("NewTsBlock() error = %v", err)
+       }
+
+       return &SessionDataSet{ioTDBRpcDataSet: &IoTDBRpcDataSet{
+               columnNameList:                     []string{"file"},
+               columnTypeList:                     []string{"OBJECT"},
+               columnName2TsBlockColumnIndexMap:   map[string]int32{"file": 0},
+               columnIndex2TsBlockColumnIndexList: []int32{0},
+               dataTypeForTsBlockColumn:           []TSDataType{OBJECT},
+               queryResult:                        [][]byte{{1}},
+               queryResultSize:                    1,
+               curTsBlock:                         block,
+               tsBlockSize:                        1,
+               tsBlockIndex:                       0,
+       }}
+}
+
+func TestSessionDataSet_OBJECTGetters(t *testing.T) {
+       value := make([]byte, 8+len("internal/path/1.bin"))
+       binary.BigEndian.PutUint64(value[:8], 1024)
+       copy(value[8:], "internal/path/1.bin")
+       dataSet := newObjectSessionDataSet(t, value)
+
+       object, err := dataSet.GetObject("file")
+       if err != nil {
+               t.Fatalf("GetObject() error = %v", err)
+       }
+       if object != "(Object) 1.00 KB" {
+               t.Errorf("GetObject() = %#v, want %q", object, "(Object) 1.00 
KB")
+       }
+
+       object, err = dataSet.GetObjectByIndex(1)
+       if err != nil {
+               t.Fatalf("GetObjectByIndex() error = %v", err)
+       }
+       if object != "(Object) 1.00 KB" {
+               t.Errorf("GetObjectByIndex() = %#v, want %q", object, "(Object) 
1.00 KB")
+       }
+
+       stringValue, err := dataSet.GetString("file")
+       if err != nil {
+               t.Fatalf("GetString() error = %v", err)
+       }
+       if stringValue != "(Object) 1.00 KB" {
+               t.Errorf("GetString() = %q, want %q", stringValue, "(Object) 
1.00 KB")
+       }
+
+       if _, err := dataSet.GetBlob("file"); err == nil || 
!strings.Contains(err.Error(), "OBJECT") {
+               t.Fatalf("GetBlob() error = %v, want an OBJECT type error", err)
+       }
+       if _, err := dataSet.GetBlobByIndex(1); err == nil || 
!strings.Contains(err.Error(), "OBJECT") {
+               t.Fatalf("GetBlobByIndex() error = %v, want an OBJECT type 
error", err)
+       }
+}
diff --git a/client/sessiondataset.go b/client/sessiondataset.go
index 66ffab1..58a9da3 100644
--- a/client/sessiondataset.go
+++ b/client/sessiondataset.go
@@ -1,6 +1,7 @@
 package client
 
 import (
+       "errors"
        "time"
 
        "github.com/apache/iotdb-client-go/v2/rpc"
@@ -107,10 +108,24 @@ func (s *SessionDataSet) GetDate(columnName string) 
(time.Time, error) {
 }
 
 func (s *SessionDataSet) GetBlobByIndex(columnIndex int32) (*Binary, error) {
+       dataType, err := s.ioTDBRpcDataSet.getDataTypeByIndex(columnIndex)
+       if err != nil {
+               return nil, err
+       }
+       if dataType == OBJECT {
+               return nil, errors.New("OBJECT type does not support GetBlob")
+       }
        return s.ioTDBRpcDataSet.getBinaryByIndex(columnIndex)
 }
 
 func (s *SessionDataSet) GetBlob(columnName string) (*Binary, error) {
+       dataType, err := s.ioTDBRpcDataSet.getDataType(columnName)
+       if err != nil {
+               return nil, err
+       }
+       if dataType == OBJECT {
+               return nil, errors.New("OBJECT type does not support GetBlob")
+       }
        return s.ioTDBRpcDataSet.getBinary(columnName)
 }
 
diff --git a/client/tablet.go b/client/tablet.go
index d694d6f..b919be9 100644
--- a/client/tablet.go
+++ b/client/tablet.go
@@ -74,7 +74,7 @@ func (t *Tablet) Swap(i, j int) {
                case DOUBLE:
                        sortedSlice := t.values[index].([]float64)
                        sortedSlice[i], sortedSlice[j] = sortedSlice[j], 
sortedSlice[i]
-               case TEXT, BLOB, STRING:
+               case TEXT, BLOB, STRING, OBJECT:
                        sortedSlice := t.values[index].([][]byte)
                        sortedSlice[i], sortedSlice[j] = sortedSlice[j], 
sortedSlice[i]
                }
@@ -213,6 +213,8 @@ func (t *Tablet) SetValueAt(value interface{}, columnIndex, 
rowIndex int) error
                default:
                        return fmt.Errorf("illegal argument value %v %v", 
value, reflect.TypeOf(value))
                }
+       case OBJECT:
+               return fmt.Errorf("OBJECT values must be set with 
SetObjectValueAt")
        case DATE:
                values := t.values[columnIndex].([]int32)
                switch v := value.(type) {
@@ -226,6 +228,51 @@ func (t *Tablet) SetValueAt(value interface{}, 
columnIndex, rowIndex int) error
                        return fmt.Errorf("illegal argument value %v %v", 
value, reflect.TypeOf(value))
                }
        }
+       t.unmarkNullValueAt(columnIndex, rowIndex)
+       return nil
+}
+
+func (t *Tablet) unmarkNullValueAt(columnIndex, rowIndex int) {
+       if t.bitMaps != nil && t.bitMaps[columnIndex] != nil {
+               t.bitMaps[columnIndex].UnMark(rowIndex)
+       }
+}
+
+// SetObjectValueAt writes a segment of an OBJECT column value. An OBJECT 
value can be
+// written in multiple segments so that a large object does not need to be 
fully loaded
+// into memory: each segment is wrapped into a 9-byte header (1 byte isEOF 
flag followed
+// by an 8-byte big-endian offset) and then the raw content, consistent with 
the Java
+// Tablet.addValue(rowIndex, columnIndex, isEOF, offset, content). Segments of 
the same
+// object must be written in order with ascending offsets, and the last 
segment must set
+// isEOF to true.
+//
+// Parameters:
+//   - isEOF: Whether this segment is the last one of the object.
+//   - offset: The offset of this segment within the whole object.
+//   - content: The raw bytes of this segment.
+//   - columnIndex: The column index of the OBJECT column.
+//   - rowIndex: The row index to write the segment into.
+//
+// Returns:
+//   - err: An error if the column/row index is invalid or the column is not 
of type OBJECT.
+func (t *Tablet) SetObjectValueAt(isEOF bool, offset int64, content []byte, 
columnIndex, rowIndex int) error {
+       if columnIndex < 0 || columnIndex >= len(t.measurementSchemas) {
+               return fmt.Errorf("illegal argument columnIndex %d", 
columnIndex)
+       }
+       if rowIndex < 0 || rowIndex >= t.maxRowNumber {
+               return fmt.Errorf("illegal argument rowIndex %d", rowIndex)
+       }
+       if t.measurementSchemas[columnIndex].DataType != OBJECT {
+               return fmt.Errorf("column %d must be of type OBJECT", 
columnIndex)
+       }
+       value := make([]byte, len(content)+9)
+       if isEOF {
+               value[0] = 1
+       }
+       binary.BigEndian.PutUint64(value[1:9], uint64(offset))
+       copy(value[9:], content)
+       t.values[columnIndex].([][]byte)[rowIndex] = value
+       t.unmarkNullValueAt(columnIndex, rowIndex)
        return nil
 }
 
@@ -260,7 +307,7 @@ func (t *Tablet) GetValueAt(columnIndex, rowIndex int) 
(interface{}, error) {
                return t.values[columnIndex].([]float64)[rowIndex], nil
        case TEXT, STRING:
                return string(t.values[columnIndex].([][]byte)[rowIndex]), nil
-       case BLOB:
+       case BLOB, OBJECT:
                return t.values[columnIndex].([][]byte)[rowIndex], nil
        case DATE:
                return Int32ToDate(t.values[columnIndex].([]int32)[rowIndex])
@@ -313,7 +360,7 @@ func (t *Tablet) getValuesBytes() ([]byte, error) {
                        binary.Write(buff, binary.BigEndian, 
t.values[i].([]float32)[0:t.RowSize])
                case DOUBLE:
                        binary.Write(buff, binary.BigEndian, 
t.values[i].([]float64)[0:t.RowSize])
-               case TEXT, STRING, BLOB:
+               case TEXT, STRING, BLOB, OBJECT:
                        for _, s := range t.values[i].([][]byte)[0:t.RowSize] {
                                binary.Write(buff, binary.BigEndian, 
int32(len(s)))
                                binary.Write(buff, binary.BigEndian, s)
@@ -365,7 +412,7 @@ func NewTablet(insertTargetName string, measurementSchemas 
[]*MeasurementSchema,
                        tablet.values[i] = make([]float32, maxRowNumber)
                case DOUBLE:
                        tablet.values[i] = make([]float64, maxRowNumber)
-               case TEXT, STRING, BLOB:
+               case TEXT, STRING, BLOB, OBJECT:
                        tablet.values[i] = make([][]byte, maxRowNumber)
                default:
                        return nil, fmt.Errorf("illegal datatype %v", 
schema.DataType)
diff --git a/client/tablet_test.go b/client/tablet_test.go
index d6f701d..be1f480 100644
--- a/client/tablet_test.go
+++ b/client/tablet_test.go
@@ -676,3 +676,119 @@ func TestTablet_Sort(t *testing.T) {
                })
        }
 }
+
+func TestTablet_OBJECT(t *testing.T) {
+       tablet, err := NewRelationalTablet("t1", []*MeasurementSchema{
+               {Measurement: "tag1", DataType: STRING},
+               {Measurement: "obj", DataType: OBJECT},
+       }, []ColumnCategory{TAG, FIELD}, 4)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       if got := tablet.getColumnCategories(); !reflect.DeepEqual(got, 
[]int8{0, 1}) {
+               t.Errorf("getColumnCategories() = %v, want [0 1]", got)
+       }
+
+       if got := tablet.getDataTypes(); !reflect.DeepEqual(got, []int32{11, 
12}) {
+               t.Errorf("getDataTypes() = %v, want [11 12]", got)
+       }
+
+       objVal := []byte{0x01, 0x02, 0x03}
+       if err := tablet.SetValueAt(objVal, 1, 0); err == nil {
+               t.Fatal("SetValueAt([]byte) for OBJECT: want error, got nil")
+       }
+       if err := tablet.SetObjectValueAt(true, 0, objVal, 1, 0); err != nil {
+               t.Fatalf("SetObjectValueAt([]byte) error = %v", err)
+       }
+       tablet.SetTimestamp(1608268702780, 0)
+       tablet.RowSize++
+
+       if err := tablet.SetValueAt("hello", 1, 1); err == nil {
+               t.Fatal("SetValueAt(string) for OBJECT: want error, got nil")
+       }
+       if err := tablet.SetObjectValueAt(true, 0, []byte("hello"), 1, 1); err 
!= nil {
+               t.Fatalf("SetObjectValueAt(string bytes) error = %v", err)
+       }
+       tablet.SetTimestamp(1608268702781, 1)
+       tablet.RowSize++
+
+       wantObject1 := append([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0}, objVal...)
+       wantObject2 := append([]byte{1, 0, 0, 0, 0, 0, 0, 0, 0}, 
[]byte("hello")...)
+       if got, err := tablet.GetValueAt(1, 0); err != nil || 
!reflect.DeepEqual(got, wantObject1) {
+               t.Errorf("GetValueAt(1,0) = %v, %v; want %v, nil", got, err, 
wantObject1)
+       }
+       if got, err := tablet.GetValueAt(1, 1); err != nil || 
!reflect.DeepEqual(got, wantObject2) {
+               t.Errorf("GetValueAt(1,1) = %v, %v; want %v, nil", got, err, 
wantObject2)
+       }
+
+       valuesBytes, err := tablet.getValuesBytes()
+       if err != nil {
+               t.Fatal(err)
+       }
+       wantValues := []byte{
+               0, 0, 0, 0,
+               0, 0, 0, 0,
+               0, 0, 0, byte(len(wantObject1)),
+       }
+       wantValues = append(wantValues, wantObject1...)
+       wantValues = append(wantValues, 0, 0, 0, byte(len(wantObject2)))
+       wantValues = append(wantValues, wantObject2...)
+       if !reflect.DeepEqual(valuesBytes, wantValues) {
+               t.Errorf("getValuesBytes() = %v, want %v", valuesBytes, 
wantValues)
+       }
+}
+
+func TestTablet_SetObjectValueAt(t *testing.T) {
+       tablet, err := NewRelationalTablet("t1", []*MeasurementSchema{
+               {Measurement: "tag1", DataType: STRING},
+               {Measurement: "obj", DataType: OBJECT},
+       }, []ColumnCategory{TAG, FIELD}, 4)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       tablet.SetTimestamp(1, 0)
+       if err := tablet.SetObjectValueAt(false, 0, []byte{0x11, 0x22}, 1, 0); 
err != nil {
+               t.Fatalf("SetObjectValueAt(segment) error = %v", err)
+       }
+       tablet.RowSize++
+
+       tablet.SetTimestamp(1, 1)
+       if err := tablet.SetObjectValueAt(true, 512, []byte{0x33}, 1, 1); err 
!= nil {
+               t.Fatalf("SetObjectValueAt(last segment) error = %v", err)
+       }
+       tablet.RowSize++
+
+       wantSegment1 := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0x11, 0x22}
+       wantSegment2 := []byte{1, 0, 0, 0, 0, 0, 0, 2, 0, 0x33}
+       if got, err := tablet.GetValueAt(1, 0); err != nil || 
!reflect.DeepEqual(got, wantSegment1) {
+               t.Errorf("GetValueAt(1,0) = %v, %v; want %v, nil", got, err, 
wantSegment1)
+       }
+       if got, err := tablet.GetValueAt(1, 1); err != nil || 
!reflect.DeepEqual(got, wantSegment2) {
+               t.Errorf("GetValueAt(1,1) = %v, %v; want %v, nil", got, err, 
wantSegment2)
+       }
+
+       if err := tablet.SetValueAt(nil, 1, 2); err != nil {
+               t.Fatalf("SetValueAt(nil) error = %v", err)
+       }
+       if tablet.bitMaps == nil || tablet.bitMaps[1] == nil || 
!tablet.bitMaps[1].IsMarked(2) {
+               t.Fatal("SetValueAt(nil) did not mark the OBJECT cell as null")
+       }
+       if err := tablet.SetObjectValueAt(true, 0, []byte{0x44}, 1, 2); err != 
nil {
+               t.Fatalf("SetObjectValueAt() after null error = %v", err)
+       }
+       if tablet.bitMaps[1].IsMarked(2) {
+               t.Error("SetObjectValueAt() did not clear the previously marked 
null bit")
+       }
+
+       if err := tablet.SetObjectValueAt(false, 0, []byte{0x01}, 0, 0); err == 
nil {
+               t.Error("SetObjectValueAt() on non-OBJECT column: want error, 
got nil")
+       }
+       if err := tablet.SetObjectValueAt(false, 0, []byte{0x01}, 1, -1); err 
== nil {
+               t.Error("SetObjectValueAt() with invalid rowIndex: want error, 
got nil")
+       }
+       if err := tablet.SetObjectValueAt(false, 0, []byte{0x01}, -1, 0); err 
== nil {
+               t.Error("SetObjectValueAt() with invalid columnIndex: want 
error, got nil")
+       }
+}
diff --git a/client/utils.go b/client/utils.go
index 276cbe4..d6e8f93 100644
--- a/client/utils.go
+++ b/client/utils.go
@@ -206,6 +206,30 @@ func bytesToHexString(input []byte) string {
        return hexString
 }
 
+func objectBytesToString(input []byte) (string, error) {
+       const (
+               kilobyte = uint64(1024)
+               megabyte = kilobyte * 1024
+               gigabyte = megabyte * 1024
+       )
+
+       if len(input) < 8 {
+               return "", fmt.Errorf("invalid OBJECT value: expected at least 
8 bytes, got %d", len(input))
+       }
+
+       size := binary.BigEndian.Uint64(input[:8])
+       switch {
+       case size < kilobyte:
+               return fmt.Sprintf("(Object) %d B", size), nil
+       case size < megabyte:
+               return fmt.Sprintf("(Object) %.2f KB", 
float64(size)/float64(kilobyte)), nil
+       case size < gigabyte:
+               return fmt.Sprintf("(Object) %.2f MB", 
float64(size)/float64(megabyte)), nil
+       default:
+               return fmt.Sprintf("(Object) %.2f GB", 
float64(size)/float64(gigabyte)), nil
+       }
+}
+
 func DateToInt32(localDate time.Time) (int32, error) {
        if localDate.IsZero() {
                return 0, errors.New("date expression is null or empty")
diff --git a/client/utils_test.go b/client/utils_test.go
index 95d8d2a..7623da1 100644
--- a/client/utils_test.go
+++ b/client/utils_test.go
@@ -20,6 +20,7 @@
 package client
 
 import (
+       "encoding/binary"
        "testing"
 
        "github.com/apache/iotdb-client-go/v2/common"
@@ -207,6 +208,37 @@ func Test_bytesToHexString(t *testing.T) {
        }
 }
 
+func Test_objectBytesToString(t *testing.T) {
+       tests := []struct {
+               name string
+               size uint64
+               want string
+       }{
+               {name: "bytes", size: 1023, want: "(Object) 1023 B"},
+               {name: "kilobytes", size: 1024, want: "(Object) 1.00 KB"},
+               {name: "megabytes", size: 1024 * 1024, want: "(Object) 1.00 
MB"},
+               {name: "gigabytes", size: 1024 * 1024 * 1024, want: "(Object) 
1.00 GB"},
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       value := make([]byte, 8)
+                       binary.BigEndian.PutUint64(value, tt.size)
+                       got, err := objectBytesToString(value)
+                       if err != nil {
+                               t.Fatalf("objectBytesToString() error = %v", 
err)
+                       }
+                       if got != tt.want {
+                               t.Errorf("objectBytesToString() = %q, want %q", 
got, tt.want)
+                       }
+               })
+       }
+
+       if _, err := objectBytesToString(make([]byte, 7)); err == nil {
+               t.Fatal("objectBytesToString() with a short value: want error, 
got nil")
+       }
+}
+
 func Test_verifySuccess(t *testing.T) {
        type args struct {
                status *common.TSStatus
diff --git a/common/common.go b/common/common.go
index 74e03c6..18f4058 100644
--- a/common/common.go
+++ b/common/common.go
@@ -475,6 +475,10 @@ const (
        TAggregationType_SKEWNESS TAggregationType = 38
        TAggregationType_KURTOSIS TAggregationType = 39
        TAggregationType_PERCENTILE TAggregationType = 40
+       TAggregationType_RATE TAggregationType = 41
+       TAggregationType_INCREASE TAggregationType = 42
+       TAggregationType_IRATE TAggregationType = 43
+       TAggregationType_DELTA TAggregationType = 44
 )
 
 var knownTAggregationTypeValues = []TAggregationType{
@@ -519,6 +523,10 @@ var knownTAggregationTypeValues = []TAggregationType{
        TAggregationType_SKEWNESS,
        TAggregationType_KURTOSIS,
        TAggregationType_PERCENTILE,
+       TAggregationType_RATE,
+       TAggregationType_INCREASE,
+       TAggregationType_IRATE,
+       TAggregationType_DELTA,
 }
 
 func TAggregationTypeValues() iter.Seq[TAggregationType] {
@@ -574,6 +582,10 @@ func (p TAggregationType) String() string {
        case TAggregationType_SKEWNESS: return "SKEWNESS"
        case TAggregationType_KURTOSIS: return "KURTOSIS"
        case TAggregationType_PERCENTILE: return "PERCENTILE"
+       case TAggregationType_RATE: return "RATE"
+       case TAggregationType_INCREASE: return "INCREASE"
+       case TAggregationType_IRATE: return "IRATE"
+       case TAggregationType_DELTA: return "DELTA"
        }
        return "<UNSET>"
 }
@@ -621,6 +633,10 @@ func TAggregationTypeFromString(s string) 
(TAggregationType, error) {
        case "SKEWNESS": return TAggregationType_SKEWNESS, nil
        case "KURTOSIS": return TAggregationType_KURTOSIS, nil
        case "PERCENTILE": return TAggregationType_PERCENTILE, nil
+       case "RATE": return TAggregationType_RATE, nil
+       case "INCREASE": return TAggregationType_INCREASE, nil
+       case "IRATE": return TAggregationType_IRATE, nil
+       case "DELTA": return TAggregationType_DELTA, nil
        }
        return TAggregationType(0), fmt.Errorf("not a valid TAggregationType 
string")
 }
@@ -6310,6 +6326,7 @@ func (p *TSetThrottleQuotaReq) Validate() error {
 //  - PipeRemainingEventCountList
 //  - PipeRemainingTimeList
 //  - PipeDegradedStatusList
+//  - PipeRecentFailureList
 // 
 type TPipeHeartbeatResp struct {
        PipeMetaList [][]byte `thrift:"pipeMetaList,1,required" 
db:"pipeMetaList" json:"pipeMetaList"`
@@ -6317,6 +6334,7 @@ type TPipeHeartbeatResp struct {
        PipeRemainingEventCountList []int64 
`thrift:"pipeRemainingEventCountList,3" db:"pipeRemainingEventCountList" 
json:"pipeRemainingEventCountList,omitempty"`
        PipeRemainingTimeList []float64 `thrift:"pipeRemainingTimeList,4" 
db:"pipeRemainingTimeList" json:"pipeRemainingTimeList,omitempty"`
        PipeDegradedStatusList []int32 `thrift:"pipeDegradedStatusList,5" 
db:"pipeDegradedStatusList" json:"pipeDegradedStatusList,omitempty"`
+       PipeRecentFailureList []map[string]int64 
`thrift:"pipeRecentFailureList,6" db:"pipeRecentFailureList" 
json:"pipeRecentFailureList,omitempty"`
 }
 
 func NewTPipeHeartbeatResp() *TPipeHeartbeatResp {
@@ -6357,6 +6375,13 @@ func (p *TPipeHeartbeatResp) GetPipeDegradedStatusList() 
[]int32 {
        return p.PipeDegradedStatusList
 }
 
+var TPipeHeartbeatResp_PipeRecentFailureList_DEFAULT []map[string]int64
+
+
+func (p *TPipeHeartbeatResp) GetPipeRecentFailureList() []map[string]int64 {
+       return p.PipeRecentFailureList
+}
+
 func (p *TPipeHeartbeatResp) IsSetPipeCompletedList() bool {
        return p.PipeCompletedList != nil
 }
@@ -6373,6 +6398,10 @@ func (p *TPipeHeartbeatResp) 
IsSetPipeDegradedStatusList() bool {
        return p.PipeDegradedStatusList != nil
 }
 
+func (p *TPipeHeartbeatResp) IsSetPipeRecentFailureList() bool {
+       return p.PipeRecentFailureList != nil
+}
+
 func (p *TPipeHeartbeatResp) Read(ctx context.Context, iprot thrift.TProtocol) 
error {
        if _, err := iprot.ReadStructBegin(ctx); err != nil {
                return thrift.PrependError(fmt.Sprintf("%T read error: ", p), 
err)
@@ -6440,6 +6469,16 @@ func (p *TPipeHeartbeatResp) Read(ctx context.Context, 
iprot thrift.TProtocol) e
                                        return err
                                }
                        }
+               case 6:
+                       if fieldTypeId == thrift.LIST {
+                               if err := p.ReadField6(ctx, iprot); err != nil {
+                                       return err
+                               }
+                       } else {
+                               if err := iprot.Skip(ctx, fieldTypeId); err != 
nil {
+                                       return err
+                               }
+                       }
                default:
                        if err := iprot.Skip(ctx, fieldTypeId); err != nil {
                                return err
@@ -6568,6 +6607,46 @@ func (p *TPipeHeartbeatResp) ReadField5(ctx 
context.Context, iprot thrift.TProto
        return nil
 }
 
+func (p *TPipeHeartbeatResp) ReadField6(ctx context.Context, iprot 
thrift.TProtocol) error {
+       _, size, err := iprot.ReadListBegin(ctx)
+       if err != nil {
+               return thrift.PrependError("error reading list begin: ", err)
+       }
+       tSlice := make([]map[string]int64, 0, size)
+       p.PipeRecentFailureList = tSlice
+       for i := 0; i < size; i++ {
+               _, _, size, err := iprot.ReadMapBegin(ctx)
+               if err != nil {
+                       return thrift.PrependError("error reading map begin: ", 
err)
+               }
+               tMap := make(map[string]int64, size)
+               _elem29 := tMap
+               for i := 0; i < size; i++ {
+                       var _key30 string
+                       if v, err := iprot.ReadString(ctx); err != nil {
+                               return thrift.PrependError("error reading field 
0: ", err)
+                       } else {
+                               _key30 = v
+                       }
+                       var _val31 int64
+                       if v, err := iprot.ReadI64(ctx); err != nil {
+                               return thrift.PrependError("error reading field 
0: ", err)
+                       } else {
+                               _val31 = v
+                       }
+                       _elem29[_key30] = _val31
+               }
+               if err := iprot.ReadMapEnd(ctx); err != nil {
+                       return thrift.PrependError("error reading map end: ", 
err)
+               }
+               p.PipeRecentFailureList = append(p.PipeRecentFailureList, 
_elem29)
+       }
+       if err := iprot.ReadListEnd(ctx); err != nil {
+               return thrift.PrependError("error reading list end: ", err)
+       }
+       return nil
+}
+
 func (p *TPipeHeartbeatResp) Write(ctx context.Context, oprot 
thrift.TProtocol) error {
        if err := oprot.WriteStructBegin(ctx, "TPipeHeartbeatResp"); err != nil 
{
                return thrift.PrependError(fmt.Sprintf("%T write struct begin 
error: ", p), err)
@@ -6578,6 +6657,7 @@ func (p *TPipeHeartbeatResp) Write(ctx context.Context, 
oprot thrift.TProtocol)
                if err := p.writeField3(ctx, oprot); err != nil { return err }
                if err := p.writeField4(ctx, oprot); err != nil { return err }
                if err := p.writeField5(ctx, oprot); err != nil { return err }
+               if err := p.writeField6(ctx, oprot); err != nil { return err }
        }
        if err := oprot.WriteFieldStop(ctx); err != nil {
                return thrift.PrependError("write field stop error: ", err)
@@ -6701,6 +6781,40 @@ func (p *TPipeHeartbeatResp) writeField5(ctx 
context.Context, oprot thrift.TProt
        return err
 }
 
+func (p *TPipeHeartbeatResp) writeField6(ctx context.Context, oprot 
thrift.TProtocol) (err error) {
+       if p.IsSetPipeRecentFailureList() {
+               if err := oprot.WriteFieldBegin(ctx, "pipeRecentFailureList", 
thrift.LIST, 6); err != nil {
+                       return thrift.PrependError(fmt.Sprintf("%T write field 
begin error 6:pipeRecentFailureList: ", p), err)
+               }
+               if err := oprot.WriteListBegin(ctx, thrift.MAP, 
len(p.PipeRecentFailureList)); err != nil {
+                       return thrift.PrependError("error writing list begin: 
", err)
+               }
+               for _, v := range p.PipeRecentFailureList {
+                       if err := oprot.WriteMapBegin(ctx, thrift.STRING, 
thrift.I64, len(v)); err != nil {
+                               return thrift.PrependError("error writing map 
begin: ", err)
+                       }
+                       for k, v := range v {
+                               if err := oprot.WriteString(ctx, string(k)); 
err != nil {
+                                       return 
thrift.PrependError(fmt.Sprintf("%T. (0) field write error: ", p), err)
+                               }
+                               if err := oprot.WriteI64(ctx, int64(v)); err != 
nil {
+                                       return 
thrift.PrependError(fmt.Sprintf("%T. (0) field write error: ", p), err)
+                               }
+                       }
+                       if err := oprot.WriteMapEnd(ctx); err != nil {
+                               return thrift.PrependError("error writing map 
end: ", err)
+                       }
+               }
+               if err := oprot.WriteListEnd(ctx); err != nil {
+                       return thrift.PrependError("error writing list end: ", 
err)
+               }
+               if err := oprot.WriteFieldEnd(ctx); err != nil {
+                       return thrift.PrependError(fmt.Sprintf("%T write field 
end error 6:pipeRecentFailureList: ", p), err)
+               }
+       }
+       return err
+}
+
 func (p *TPipeHeartbeatResp) Equals(other *TPipeHeartbeatResp) bool {
        if p == other {
                return true
@@ -6709,28 +6823,37 @@ func (p *TPipeHeartbeatResp) Equals(other 
*TPipeHeartbeatResp) bool {
        }
        if len(p.PipeMetaList) != len(other.PipeMetaList) { return false }
        for i, _tgt := range p.PipeMetaList {
-               _src29 := other.PipeMetaList[i]
-               if bytes.Compare(_tgt, _src29) != 0 { return false }
+               _src32 := other.PipeMetaList[i]
+               if bytes.Compare(_tgt, _src32) != 0 { return false }
        }
        if len(p.PipeCompletedList) != len(other.PipeCompletedList) { return 
false }
        for i, _tgt := range p.PipeCompletedList {
-               _src30 := other.PipeCompletedList[i]
-               if _tgt != _src30 { return false }
+               _src33 := other.PipeCompletedList[i]
+               if _tgt != _src33 { return false }
        }
        if len(p.PipeRemainingEventCountList) != 
len(other.PipeRemainingEventCountList) { return false }
        for i, _tgt := range p.PipeRemainingEventCountList {
-               _src31 := other.PipeRemainingEventCountList[i]
-               if _tgt != _src31 { return false }
+               _src34 := other.PipeRemainingEventCountList[i]
+               if _tgt != _src34 { return false }
        }
        if len(p.PipeRemainingTimeList) != len(other.PipeRemainingTimeList) { 
return false }
        for i, _tgt := range p.PipeRemainingTimeList {
-               _src32 := other.PipeRemainingTimeList[i]
-               if _tgt != _src32 { return false }
+               _src35 := other.PipeRemainingTimeList[i]
+               if _tgt != _src35 { return false }
        }
        if len(p.PipeDegradedStatusList) != len(other.PipeDegradedStatusList) { 
return false }
        for i, _tgt := range p.PipeDegradedStatusList {
-               _src33 := other.PipeDegradedStatusList[i]
-               if _tgt != _src33 { return false }
+               _src36 := other.PipeDegradedStatusList[i]
+               if _tgt != _src36 { return false }
+       }
+       if len(p.PipeRecentFailureList) != len(other.PipeRecentFailureList) { 
return false }
+       for i, _tgt := range p.PipeRecentFailureList {
+               _src37 := other.PipeRecentFailureList[i]
+               if len(_tgt) != len(_src37) { return false }
+               for k, _tgt := range _tgt {
+                       _src38 := _src37[k]
+                       if _tgt != _src38 { return false }
+               }
        }
        return true
 }
@@ -8324,11 +8447,11 @@ func (p *TTestConnectionResp) ReadField2(ctx 
context.Context, iprot thrift.TProt
        tSlice := make([]*TTestConnectionResult_, 0, size)
        p.ResultList = tSlice
        for i := 0; i < size; i++ {
-               _elem34 := &TTestConnectionResult_{}
-               if err := _elem34.Read(ctx, iprot); err != nil {
-                       return thrift.PrependError(fmt.Sprintf("%T error 
reading struct: ", _elem34), err)
+               _elem39 := &TTestConnectionResult_{}
+               if err := _elem39.Read(ctx, iprot); err != nil {
+                       return thrift.PrependError(fmt.Sprintf("%T error 
reading struct: ", _elem39), err)
                }
-               p.ResultList = append(p.ResultList, _elem34)
+               p.ResultList = append(p.ResultList, _elem39)
        }
        if err := iprot.ReadListEnd(ctx); err != nil {
                return thrift.PrependError("error reading list end: ", err)
@@ -8396,8 +8519,8 @@ func (p *TTestConnectionResp) Equals(other 
*TTestConnectionResp) bool {
        if !p.Status.Equals(other.Status) { return false }
        if len(p.ResultList) != len(other.ResultList) { return false }
        for i, _tgt := range p.ResultList {
-               _src35 := other.ResultList[i]
-               if !_tgt.Equals(_src35) { return false }
+               _src40 := other.ResultList[i]
+               if !_tgt.Equals(_src40) { return false }
        }
        return true
 }
@@ -8519,11 +8642,11 @@ func (p *TNodeLocations) ReadField1(ctx 
context.Context, iprot thrift.TProtocol)
        tSlice := make([]*TConfigNodeLocation, 0, size)
        p.ConfigNodeLocations = tSlice
        for i := 0; i < size; i++ {
-               _elem36 := &TConfigNodeLocation{}
-               if err := _elem36.Read(ctx, iprot); err != nil {
-                       return thrift.PrependError(fmt.Sprintf("%T error 
reading struct: ", _elem36), err)
+               _elem41 := &TConfigNodeLocation{}
+               if err := _elem41.Read(ctx, iprot); err != nil {
+                       return thrift.PrependError(fmt.Sprintf("%T error 
reading struct: ", _elem41), err)
                }
-               p.ConfigNodeLocations = append(p.ConfigNodeLocations, _elem36)
+               p.ConfigNodeLocations = append(p.ConfigNodeLocations, _elem41)
        }
        if err := iprot.ReadListEnd(ctx); err != nil {
                return thrift.PrependError("error reading list end: ", err)
@@ -8539,11 +8662,11 @@ func (p *TNodeLocations) ReadField2(ctx 
context.Context, iprot thrift.TProtocol)
        tSlice := make([]*TDataNodeLocation, 0, size)
        p.DataNodeLocations = tSlice
        for i := 0; i < size; i++ {
-               _elem37 := &TDataNodeLocation{}
-               if err := _elem37.Read(ctx, iprot); err != nil {
-                       return thrift.PrependError(fmt.Sprintf("%T error 
reading struct: ", _elem37), err)
+               _elem42 := &TDataNodeLocation{}
+               if err := _elem42.Read(ctx, iprot); err != nil {
+                       return thrift.PrependError(fmt.Sprintf("%T error 
reading struct: ", _elem42), err)
                }
-               p.DataNodeLocations = append(p.DataNodeLocations, _elem37)
+               p.DataNodeLocations = append(p.DataNodeLocations, _elem42)
        }
        if err := iprot.ReadListEnd(ctx); err != nil {
                return thrift.PrependError("error reading list end: ", err)
@@ -8622,13 +8745,13 @@ func (p *TNodeLocations) Equals(other *TNodeLocations) 
bool {
        }
        if len(p.ConfigNodeLocations) != len(other.ConfigNodeLocations) { 
return false }
        for i, _tgt := range p.ConfigNodeLocations {
-               _src38 := other.ConfigNodeLocations[i]
-               if !_tgt.Equals(_src38) { return false }
+               _src43 := other.ConfigNodeLocations[i]
+               if !_tgt.Equals(_src43) { return false }
        }
        if len(p.DataNodeLocations) != len(other.DataNodeLocations) { return 
false }
        for i, _tgt := range p.DataNodeLocations {
-               _src39 := other.DataNodeLocations[i]
-               if !_tgt.Equals(_src39) { return false }
+               _src44 := other.DataNodeLocations[i]
+               if !_tgt.Equals(_src44) { return false }
        }
        return true
 }
@@ -8765,11 +8888,11 @@ func (p *TExternalServiceListResp) ReadField2(ctx 
context.Context, iprot thrift.
        tSlice := make([]*TExternalServiceEntry, 0, size)
        p.ExternalServiceInfos = tSlice
        for i := 0; i < size; i++ {
-               _elem40 := &TExternalServiceEntry{}
-               if err := _elem40.Read(ctx, iprot); err != nil {
-                       return thrift.PrependError(fmt.Sprintf("%T error 
reading struct: ", _elem40), err)
+               _elem45 := &TExternalServiceEntry{}
+               if err := _elem45.Read(ctx, iprot); err != nil {
+                       return thrift.PrependError(fmt.Sprintf("%T error 
reading struct: ", _elem45), err)
                }
-               p.ExternalServiceInfos = append(p.ExternalServiceInfos, _elem40)
+               p.ExternalServiceInfos = append(p.ExternalServiceInfos, _elem45)
        }
        if err := iprot.ReadListEnd(ctx); err != nil {
                return thrift.PrependError("error reading list end: ", err)
@@ -8837,8 +8960,8 @@ func (p *TExternalServiceListResp) Equals(other 
*TExternalServiceListResp) bool
        if !p.Status.Equals(other.Status) { return false }
        if len(p.ExternalServiceInfos) != len(other.ExternalServiceInfos) { 
return false }
        for i, _tgt := range p.ExternalServiceInfos {
-               _src41 := other.ExternalServiceInfos[i]
-               if !_tgt.Equals(_src41) { return false }
+               _src46 := other.ExternalServiceInfos[i]
+               if !_tgt.Equals(_src46) { return false }
        }
        return true
 }
@@ -9673,19 +9796,19 @@ func (p *TShowAppliedConfigurationsResp) ReadField2(ctx 
context.Context, iprot t
        tMap := make(map[string]string, size)
        p.Data = tMap
        for i := 0; i < size; i++ {
-               var _key42 string
+               var _key47 string
                if v, err := iprot.ReadString(ctx); err != nil {
                        return thrift.PrependError("error reading field 0: ", 
err)
                } else {
-                       _key42 = v
+                       _key47 = v
                }
-               var _val43 string
+               var _val48 string
                if v, err := iprot.ReadString(ctx); err != nil {
                        return thrift.PrependError("error reading field 0: ", 
err)
                } else {
-                       _val43 = v
+                       _val48 = v
                }
-               p.Data[_key42] = _val43
+               p.Data[_key47] = _val48
        }
        if err := iprot.ReadMapEnd(ctx); err != nil {
                return thrift.PrependError("error reading map end: ", err)
@@ -9758,8 +9881,8 @@ func (p *TShowAppliedConfigurationsResp) Equals(other 
*TShowAppliedConfiguration
        if !p.Status.Equals(other.Status) { return false }
        if len(p.Data) != len(other.Data) { return false }
        for k, _tgt := range p.Data {
-               _src44 := other.Data[k]
-               if _tgt != _src44 { return false }
+               _src49 := other.Data[k]
+               if _tgt != _src49 { return false }
        }
        return true
 }
diff --git a/database/column/column.go b/database/column/column.go
index 802246b..9095617 100644
--- a/database/column/column.go
+++ b/database/column/column.go
@@ -77,6 +77,10 @@ func GenColumn(dataType string, name string) Interface {
                return &String{
                        name: name,
                }
+       case "OBJECT":
+               return &Object{
+                       name: name,
+               }
        }
        return nil
 }
diff --git a/database/column/column.go b/database/column/object.go
similarity index 51%
copy from database/column/column.go
copy to database/column/object.go
index 802246b..ebe8232 100644
--- a/database/column/column.go
+++ b/database/column/object.go
@@ -19,64 +19,38 @@
 
 package column
 
-import (
-       "github.com/apache/iotdb-client-go/v2/client"
-)
+import "github.com/apache/iotdb-client-go/v2/client"
 
-type Type string
+type Object struct {
+       name string
+}
 
-type Interface interface {
-       Name() string
-       Type() Type
-       Row(stat *client.SessionDataSet, ptr bool) any
+func (o *Object) Name() string {
+       return o.name
 }
 
-func GenColumn(dataType string, name string) Interface {
-       switch dataType {
-       case "BOOLEAN":
-               return &Bool{
-                       name: name,
-               }
-       case "INT32":
-               return &Int32{
-                       name: name,
-               }
-       case "INT64":
-               {
-                       return &Int64{
-                               name: name,
-                       }
-               }
-       case "FLOAT":
-               return &Float{
-                       name: name,
-               }
-       case "DOUBLE":
-               return &Double{
-                       name: name,
-               }
-       case "TEXT":
-               {
-                       return &String{
-                               name: name,
-                       }
-               }
-       case "TIMESTAMP":
-               return &Timestamp{
-                       name: name,
-               }
-       case "DATE":
-               return &Date{
-                       name: name,
-               }
-       case "BLOB":
-               return &Blob{
-                       name: name,
+func (o *Object) Type() Type {
+       return "OBJECT"
+}
+
+func (o *Object) Row(stat *client.SessionDataSet, ptr bool) any {
+       if stat == nil {
+               if ptr {
+                       return nil
                }
-       case "STRING":
-               return &String{
-                       name: name,
+               return ""
+       }
+       value, err := stat.GetString(o.name)
+       if err != nil {
+               if ptr {
+                       return nil
                }
+               return ""
+       }
+       if ptr {
+               return &value
        }
-       return nil
+       return value
 }
+
+var _ Interface = (*Object)(nil)
diff --git a/database/column/column.go b/database/column/object_test.go
similarity index 50%
copy from database/column/column.go
copy to database/column/object_test.go
index 802246b..8069af4 100644
--- a/database/column/column.go
+++ b/database/column/object_test.go
@@ -19,64 +19,20 @@
 
 package column
 
-import (
-       "github.com/apache/iotdb-client-go/v2/client"
-)
+import "testing"
 
-type Type string
-
-type Interface interface {
-       Name() string
-       Type() Type
-       Row(stat *client.SessionDataSet, ptr bool) any
-}
-
-func GenColumn(dataType string, name string) Interface {
-       switch dataType {
-       case "BOOLEAN":
-               return &Bool{
-                       name: name,
-               }
-       case "INT32":
-               return &Int32{
-                       name: name,
-               }
-       case "INT64":
-               {
-                       return &Int64{
-                               name: name,
-                       }
-               }
-       case "FLOAT":
-               return &Float{
-                       name: name,
-               }
-       case "DOUBLE":
-               return &Double{
-                       name: name,
-               }
-       case "TEXT":
-               {
-                       return &String{
-                               name: name,
-                       }
-               }
-       case "TIMESTAMP":
-               return &Timestamp{
-                       name: name,
-               }
-       case "DATE":
-               return &Date{
-                       name: name,
-               }
-       case "BLOB":
-               return &Blob{
-                       name: name,
-               }
-       case "STRING":
-               return &String{
-                       name: name,
-               }
+func TestGenColumn_OBJECT(t *testing.T) {
+       column := GenColumn("OBJECT", "file")
+       if column == nil {
+               t.Fatal("GenColumn() returned nil for OBJECT")
+       }
+       if _, ok := column.(*Object); !ok {
+               t.Fatalf("GenColumn() returned %T for OBJECT, want *Object", 
column)
+       }
+       if column.Name() != "file" {
+               t.Errorf("Name() = %q, want %q", column.Name(), "file")
+       }
+       if column.Type() != "OBJECT" {
+               t.Errorf("Type() = %q, want %q", column.Type(), "OBJECT")
        }
-       return nil
 }
diff --git a/test/e2e/e2e_table_test.go b/test/e2e/e2e_table_test.go
index e56ce95..6355e48 100644
--- a/test/e2e/e2e_table_test.go
+++ b/test/e2e/e2e_table_test.go
@@ -20,6 +20,7 @@
 package e2e
 
 import (
+       "bytes"
        "log"
        "strconv"
        "strings"
@@ -424,6 +425,131 @@ func (s *e2eTableTestSuite) Test_InsertTabletAndQuery() {
        assert.Equal(int64(8), count)
 }
 
+func (s *e2eTableTestSuite) Test_InsertObjectTablet() {
+       s.T().Skip("OBJECT type is not supported in the e2e environment")
+       assert := s.Require()
+       s.checkError(s.session.ExecuteNonQueryStatement(
+               "create table object_table (region_id string tag, plant_id 
string tag, device_id string tag, temperature float field, file object field)"))
+
+       objectBytes := make([]byte, 1024)
+       for i := 0; i < len(objectBytes); i++ {
+               objectBytes[i] = byte(i % 251)
+       }
+       const segmentSize = 512
+       var objectSegments [][]byte
+       for i := 0; i < len(objectBytes); i += segmentSize {
+               end := i + segmentSize
+               if end > len(objectBytes) {
+                       end = len(objectBytes)
+               }
+               objectSegments = append(objectSegments, objectBytes[i:end])
+       }
+
+       tablet, err := client.NewRelationalTablet("object_table", 
[]*client.MeasurementSchema{
+               {Measurement: "region_id", DataType: client.STRING},
+               {Measurement: "plant_id", DataType: client.STRING},
+               {Measurement: "device_id", DataType: client.STRING},
+               {Measurement: "temperature", DataType: client.FLOAT},
+               {Measurement: "file", DataType: client.OBJECT},
+       }, []client.ColumnCategory{client.TAG, client.TAG, client.TAG, 
client.FIELD, client.FIELD}, 1)
+       assert.NoError(err)
+
+       // insert whole object at time 1
+       tablet.SetTimestamp(1, 0)
+       assert.NoError(tablet.SetValueAt("1", 0, 0))
+       assert.NoError(tablet.SetValueAt("5", 1, 0))
+       assert.NoError(tablet.SetValueAt("3", 2, 0))
+       assert.NoError(tablet.SetValueAt(float32(37.6), 3, 0))
+       assert.NoError(tablet.SetObjectValueAt(true, 0, objectBytes, 4, 0))
+       tablet.RowSize++
+       s.checkError(s.session.Insert(tablet))
+       tablet.Reset()
+
+       // insert object in segments at time 2
+       for i, segment := range objectSegments {
+               tablet.SetTimestamp(2, 0)
+               assert.NoError(tablet.SetValueAt("2", 0, 0))
+               assert.NoError(tablet.SetValueAt("6", 1, 0))
+               assert.NoError(tablet.SetValueAt("4", 2, 0))
+               assert.NoError(tablet.SetValueAt(float32(37.7), 3, 0))
+               isEOF := i == len(objectSegments)-1
+               assert.NoError(tablet.SetObjectValueAt(isEOF, 
int64(i*segmentSize), segment, 4, 0))
+               tablet.RowSize++
+               s.checkError(s.session.Insert(tablet))
+               tablet.Reset()
+       }
+
+       // insert a row without object value at time 3
+       tablet.SetTimestamp(3, 0)
+       assert.NoError(tablet.SetValueAt("3", 0, 0))
+       assert.NoError(tablet.SetValueAt("7", 1, 0))
+       assert.NoError(tablet.SetValueAt("5", 2, 0))
+       assert.NoError(tablet.SetValueAt(float32(37.8), 3, 0))
+       assert.NoError(tablet.SetValueAt(nil, 4, 0))
+       tablet.RowSize++
+       s.checkError(s.session.Insert(tablet))
+
+       timeoutInMs := int64(10000)
+
+       // count
+       dataSet, err := s.session.ExecuteQueryStatement("select count(*) from 
object_table", &timeoutInMs)
+       assert.NoError(err)
+       hasNext, err := dataSet.Next()
+       assert.NoError(err)
+       assert.True(hasNext)
+       count, err := dataSet.GetLongByIndex(1)
+       assert.NoError(err)
+       assert.Equal(int64(3), count)
+       dataSet.Close()
+
+       // read back whole object
+       dataSet, err = s.session.ExecuteQueryStatement("select 
READ_OBJECT(file) from object_table where time = 1", &timeoutInMs)
+       assert.NoError(err)
+       hasNext, err = dataSet.Next()
+       assert.NoError(err)
+       assert.True(hasNext)
+       blob, err := dataSet.GetBlobByIndex(1)
+       assert.NoError(err)
+       assert.True(bytes.Equal(objectBytes, blob.GetValues()))
+       dataSet.Close()
+
+       // read back segmented object
+       dataSet, err = s.session.ExecuteQueryStatement("select 
READ_OBJECT(file) from object_table where time = 2", &timeoutInMs)
+       assert.NoError(err)
+       hasNext, err = dataSet.Next()
+       assert.NoError(err)
+       assert.True(hasNext)
+       blob, err = dataSet.GetBlobByIndex(1)
+       assert.NoError(err)
+       assert.True(bytes.Equal(objectBytes, blob.GetValues()))
+       dataSet.Close()
+
+       // null object row
+       dataSet, err = s.session.ExecuteQueryStatement("select file from 
object_table where time = 3", &timeoutInMs)
+       assert.NoError(err)
+       hasNext, err = dataSet.Next()
+       assert.NoError(err)
+       assert.True(hasNext)
+       isNull, err := dataSet.IsNullByIndex(1)
+       assert.NoError(err)
+       assert.True(isNull)
+       dataSet.Close()
+
+       // non-object columns round-trip
+       dataSet, err = s.session.ExecuteQueryStatement("select region_id, 
plant_id, device_id, temperature from object_table where time = 1", 
&timeoutInMs)
+       assert.NoError(err)
+       hasNext, err = dataSet.Next()
+       assert.NoError(err)
+       assert.True(hasNext)
+       assert.Equal("1", getValueFromDataSet(dataSet, "region_id"))
+       assert.Equal("5", getValueFromDataSet(dataSet, "plant_id"))
+       assert.Equal("3", getValueFromDataSet(dataSet, "device_id"))
+       temp, err := dataSet.GetFloat("temperature")
+       assert.NoError(err)
+       assert.Equal(float32(37.6), temp)
+       dataSet.Close()
+}
+
 func getValueFromDataSet(dataSet *client.SessionDataSet, columnName string) 
interface{} {
        if isNull, err := dataSet.IsNull(columnName); err != nil {
                log.Fatal(err)

Reply via email to