betterlmy opened a new pull request, #155:
URL: https://github.com/apache/iotdb-client-go/pull/155

   # Bug 复现与修复报告
   
   ## 一、问题描述
   
   在使用 IoTDB Go Client 查询包含空字符串(`""`)的 TEXT 列时,客户端报 `EOF` 错误,导致查询失败。同一条 SQL 在 
IoTDB 命令行中执行正常。
   
   ---
   
   ## 二、复现步骤
   
   **环境:** IoTDB 2.0.5
   
   **第一步:写入一条 TEXT 列值为空字符串的数据**
   
   ```sql
   INSERT INTO 
root.dev.property.`09354fae7b31c824d5888bcd184af887`.Ew7dSmO5m0PdZt7dU9sytGEyPgUlHH
     (timestamp, mouldNo_value)
   VALUES (now(), '');
   ```
   
   **第二步:用 Go Client 查询该列**
   
   ```go
   sql := "SELECT mouldNo_value FROM 
root.dev.property.09354fae7b31c824d5888bcd184af887.Ew7dSmO5m0PdZt7dU9sytGEyPgUlHH
 ORDER BY time DESC LIMIT 2"
   dataset, err := session.ExecuteQueryStatement(sql, nil)
   // err == nil
   
   for {
       hasNext, err := dataset.Next()
       // err = EOF  <-- 此处报错
   }
   ```
   
   **复现条件:**
   - 查询列为单列 TEXT 类型
   - 结果集中最后一行的该列值为空字符串 `""`(非 null)
   
   **报错信息:**
   ```
   EOF
   ```
   
   ---
   
   ## 三、根本原因分析
   
   问题出在 `client/column_decoder.go` 的 `BinaryArrayColumnDecoder.ReadColumn`。
   
   IoTDB 服务端对 TEXT 类型的每个值序列化格式为:
   
   ```
   +---------------+-------+
   | value length  | value |
   +---------------+-------+
   | int32         | bytes |
   +---------------+-------+
   ```
   
   当值为空字符串时,服务端发送 `length = 0`,后跟 0 字节内容。
   
   旧代码:
   
   ```go
   var length int32
   binary.Read(reader, binary.BigEndian, &length)
   
   value := make([]byte, length)  // length=0,创建空 slice
   _, err = reader.Read(value)    // 对空 slice 调用 Read,reader 恰好处于 EOF 边界时返回 
io.EOF
   ```
   
   Go 标准库 `bytes.Reader.Read` 在 reader 已无剩余字节时,即使传入空 slice(读取 0 字节),也会返回 
`io.EOF`。当空字符串恰好是 TsBlock 字节流中最后一个值时,`reader.Read([]byte{})` 触发 EOF,导致解码失败。
   
   **同时发现的第二个 bug:** 当服务端返回 `positionCount = 0` 的列时,所有类型的 ColumnDecoder 都会调用 
`deserializeNullIndicators`,后者对空 reader 执行 `ReadByte()`,同样返回 `io.EOF`。
   
   ---
   
   ## 四、修复方案
   
   **Bug 1(空字符串 EOF):** 在 `BinaryArrayColumnDecoder` 中,当 `length == 0` 时直接创建空 
Binary,跳过 `reader.Read`:
   
   ```go
   if length == 0 {
       values[i] = NewBinary([]byte{})
   } else {
       value := make([]byte, length)
       _, err = reader.Read(value)
       if err != nil {
           return nil, err
       }
       values[i] = NewBinary(value)
   }
   ```
   
   **Bug 2(positionCount=0 EOF):** 在四种 ColumnDecoder 的 `ReadColumn` 入口处,对 
`positionCount == 0` 提前返回空 Column:
   
   ```go
   if positionCount == 0 {
       return NewBinaryColumn(0, 0, nil, []*Binary{})
   }
   ```
   
   ---
   
   ## 五、验证结果
   
   **修复前:**
   ```
   迭代结果集失败(第 1 行): EOF
   --- FAIL: TestQueryControlSignal
   ```
   
   **修复后:**
   ```
   --- 第 1 行 --- mouldNo_value (TEXT) = ""
   --- 第 2 行 --- mouldNo_value (TEXT) = ""
   共返回 2 行
   --- PASS: TestQueryControlSignal
   ```
   
   ---
   
   ## 六、新增 Feature:SessionDataSet.GetCurrentRowTime()
   
   `SessionDataSet` 新增 `GetCurrentRowTime()` 方法,返回当前行的原始时间戳(int64,毫秒级 Unix 时间戳)。
   
   **背景:** 原有的 `GetTimestamp(columnName)` 方法返回 
`time.Time`,需要指定列名,且经过时区转换。对于需要直接操作原始时间戳数值的场景(如比较、存储、序列化),需要再做一次转换,不够便捷。
   
   **用法:**
   
   ```go
   for {
       hasNext, _ := dataset.Next()
       if !hasNext {
           break
       }
       ts := dataset.GetCurrentRowTime() // 直接获取行时间戳,单位毫秒
       fmt.Println(ts)
   }
   ```
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to