Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Apache.IoTDB/Client.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,6 +50,7 @@ static public TSDataType GetDataTypeByStr(string typeStr)
"TEXT" => TSDataType.TEXT,
"STRING" => TSDataType.STRING,
"BLOB" => TSDataType.BLOB,
"OBJECT" => TSDataType.OBJECT,
"TIMESTAMP" => TSDataType.TIMESTAMP,
"DATE" => TSDataType.DATE,
_ => TSDataType.NONE
Expand Down
5 changes: 4 additions & 1 deletion src/Apache.IoTDB/DataStructure/ColumnDecoder.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -188,7 +188,10 @@ public class BinaryArrayColumnDecoder : ColumnDecoder
{
public Column ReadColumn(ByteBuffer reader, TSDataType dataType, int positionCount)
{
if (dataType != TSDataType.TEXT)
if (dataType != TSDataType.TEXT
&& dataType != TSDataType.STRING
&& dataType != TSDataType.BLOB
&& dataType != TSDataType.OBJECT)
throw new ArgumentException($"Invalid data type: {dataType}");

bool[] nullIndicators = ColumnDeserializer.DeserializeNullIndicators(reader, positionCount);
Expand Down
13 changes: 13 additions & 0 deletions src/Apache.IoTDB/DataStructure/RpcDataSet.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,8 +60,8 @@
public int _tsBlockSize;
private int _tsBlockIndex;
private TimeZoneInfo _zoneId;
private int _timeFactor;

Check warning on line 63 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (ubuntu-latest)

Field 'RpcDataSet._timeFactor' is never assigned to, and will always have its default value 0

Check warning on line 63 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (ubuntu-latest)

Field 'RpcDataSet._timeFactor' is never assigned to, and will always have its default value 0

Check warning on line 63 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (ubuntu-latest)

Field 'RpcDataSet._timeFactor' is never assigned to, and will always have its default value 0

Check warning on line 63 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (ubuntu-latest)

Field 'RpcDataSet._timeFactor' is never assigned to, and will always have its default value 0

Check warning on line 63 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (ubuntu-latest)

Field 'RpcDataSet._timeFactor' is never assigned to, and will always have its default value 0

Check warning on line 63 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (macos-latest)

Field 'RpcDataSet._timeFactor' is never assigned to, and will always have its default value 0

Check warning on line 63 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (macos-latest)

Field 'RpcDataSet._timeFactor' is never assigned to, and will always have its default value 0

Check warning on line 63 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (macos-latest)

Field 'RpcDataSet._timeFactor' is never assigned to, and will always have its default value 0

Check warning on line 63 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (macos-latest)

Field 'RpcDataSet._timeFactor' is never assigned to, and will always have its default value 0

Check warning on line 63 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (macos-latest)

Field 'RpcDataSet._timeFactor' is never assigned to, and will always have its default value 0

Check warning on line 63 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (windows-latest)

Field 'RpcDataSet._timeFactor' is never assigned to, and will always have its default value 0

Check warning on line 63 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (windows-latest)

Field 'RpcDataSet._timeFactor' is never assigned to, and will always have its default value 0

Check warning on line 63 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (windows-latest)

Field 'RpcDataSet._timeFactor' is never assigned to, and will always have its default value 0

Check warning on line 63 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (windows-latest)

Field 'RpcDataSet._timeFactor' is never assigned to, and will always have its default value 0

Check warning on line 63 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (windows-latest)

Field 'RpcDataSet._timeFactor' is never assigned to, and will always have its default value 0
private string _timePrecision;

Check warning on line 64 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (ubuntu-latest)

Field 'RpcDataSet._timePrecision' is never assigned to, and will always have its default value null

Check warning on line 64 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (ubuntu-latest)

Field 'RpcDataSet._timePrecision' is never assigned to, and will always have its default value null

Check warning on line 64 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (ubuntu-latest)

Field 'RpcDataSet._timePrecision' is never assigned to, and will always have its default value null

Check warning on line 64 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (ubuntu-latest)

Field 'RpcDataSet._timePrecision' is never assigned to, and will always have its default value null

Check warning on line 64 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (ubuntu-latest)

Field 'RpcDataSet._timePrecision' is never assigned to, and will always have its default value null

Check warning on line 64 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (macos-latest)

Field 'RpcDataSet._timePrecision' is never assigned to, and will always have its default value null

Check warning on line 64 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (macos-latest)

Field 'RpcDataSet._timePrecision' is never assigned to, and will always have its default value null

Check warning on line 64 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (macos-latest)

Field 'RpcDataSet._timePrecision' is never assigned to, and will always have its default value null

Check warning on line 64 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (macos-latest)

Field 'RpcDataSet._timePrecision' is never assigned to, and will always have its default value null

Check warning on line 64 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (macos-latest)

Field 'RpcDataSet._timePrecision' is never assigned to, and will always have its default value null

Check warning on line 64 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (windows-latest)

Field 'RpcDataSet._timePrecision' is never assigned to, and will always have its default value null

Check warning on line 64 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (windows-latest)

Field 'RpcDataSet._timePrecision' is never assigned to, and will always have its default value null

Check warning on line 64 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (windows-latest)

Field 'RpcDataSet._timePrecision' is never assigned to, and will always have its default value null

Check warning on line 64 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (windows-latest)

Field 'RpcDataSet._timePrecision' is never assigned to, and will always have its default value null

Check warning on line 64 in src/Apache.IoTDB/DataStructure/RpcDataSet.cs

View workflow job for this annotation

GitHub Actions/ build (windows-latest)

Field 'RpcDataSet._timePrecision' is never assigned to, and will always have its default value null
private bool disposedValue;

public RpcDataSet(string sql, List<string> columnNameList, List<string> columnTypeList,
Expand DownExpand Up@@ -495,6 +495,8 @@
private Binary GetBinaryByTsBlockColumnIndex(int tsBlockColumnIndex)
{
CheckRecord();
if (GetDataTypeByTsBlockColumnIndex(tsBlockColumnIndex) == TSDataType.OBJECT)
throw new InvalidOperationException("OBJECT type does not support GetBlob");
if (!IsNull(tsBlockColumnIndex, _tsBlockIndex))
{
_lastReadWasNull = false;
Expand DownExpand Up@@ -554,6 +556,10 @@
case TSDataType.BLOB:
return _curTsBlock.GetColumn(tsBlockColumnIndex).GetBinary(_tsBlockIndex);

case TSDataType.OBJECT:
Binary objectBytes = _curTsBlock.GetColumn(tsBlockColumnIndex).GetBinary(_tsBlockIndex);
return Utils.ObjectBytesToString(objectBytes.Data);

case TSDataType.DATE:
int value = _curTsBlock.GetColumn(tsBlockColumnIndex).GetInt(_tsBlockIndex);
return Int32ToDate(value);
Expand DownExpand Up@@ -635,6 +641,10 @@
Binary blobBytes = _curTsBlock.GetColumn(index).GetBinary(_tsBlockIndex);
return blobBytes.ToString().Replace("-", "");

case TSDataType.OBJECT:
Binary objectBytes = _curTsBlock.GetColumn(index).GetBinary(_tsBlockIndex);
return Utils.ObjectBytesToString(objectBytes.Data);

case TSDataType.DATE:
int dateValue = _curTsBlock.GetColumn(index).GetInt(_tsBlockIndex);
DateTime date = Int32ToDate(dateValue);
Expand DownExpand Up@@ -701,6 +711,9 @@
var binary = GetBinary(columnName);
localfield = binary?.Data;
break;
case TSDataType.OBJECT:
localfield = GetString(columnName);
break;
case TSDataType.DATE:
localfield = GetDate(columnName);
break;
Expand Down
54 changes: 54 additions & 0 deletions src/Apache.IoTDB/DataStructure/Tablet.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -297,6 +297,57 @@ public List<sbyte> GetColumnColumnCategories()
return columnCategories;
}

/// <summary>
/// Builds the wire representation of one OBJECT segment: a 1-byte isEOF
/// flag, an 8-byte big-endian offset, then the raw segment content.
/// This matches Java's
/// Tablet.addValue(rowIndex, columnIndex, isEOF, offset, content).
/// </summary>
public static byte[] BuildObjectValue(bool isEOF, long offset, byte[] content)
{
if (content == null)
throw new ArgumentNullException(nameof(content));
if (offset < 0)
throw new ArgumentOutOfRangeException(nameof(offset), offset, "OBJECT segment offset must be non-negative.");

var value = new byte[9 + content.Length];
value[0] = isEOF ? (byte)1 : (byte)0;
value[1] = (byte)(offset >> 56);
value[2] = (byte)(offset >> 48);
value[3] = (byte)(offset >> 40);
value[4] = (byte)(offset >> 32);
value[5] = (byte)(offset >> 24);
value[6] = (byte)(offset >> 16);
value[7] = (byte)(offset >> 8);
value[8] = (byte)offset;
Array.Copy(content, 0, value, 9, content.Length);
return value;
}

/// <summary>
/// Writes one segment of an OBJECT column value at an existing row.
///
/// An OBJECT value can be written in multiple segments so that a large
/// object does not need to be fully loaded into memory. Segments must be
/// written with ascending offsets and the last segment must set isEOF
/// to true.
/// </summary>
public void SetObjectValueAt(bool isEOF, long offset, byte[] content, int columnIndex, int rowIndex)
{
if (columnIndex < 0 || columnIndex >= ColNumber)
throw new ArgumentOutOfRangeException(nameof(columnIndex), columnIndex, "Column index is out of range.");
if (rowIndex < 0 || rowIndex >= RowNumber)
throw new ArgumentOutOfRangeException(nameof(rowIndex), rowIndex, "Row index is out of range.");
if (DataTypes[columnIndex] != TSDataType.OBJECT)
throw new ArgumentException($"Column {columnIndex} must be of type OBJECT.", nameof(columnIndex));

_values[rowIndex][columnIndex] = BuildObjectValue(isEOF, offset, content);
if (BitMaps != null && BitMaps[columnIndex] != null)
{
BitMaps[columnIndex].unmark(rowIndex);
}
}

private int EstimateBufferSize()
{
var estimateSize = 0;
Expand DownExpand Up@@ -326,6 +377,7 @@ private int EstimateBufferSize()
case TSDataType.TEXT:
case TSDataType.BLOB:
case TSDataType.STRING:
case TSDataType.OBJECT:
estimateSize += 8;
break;
default:
Expand DownExpand Up@@ -441,7 +493,9 @@ public byte[] GetBinaryValues()
break;
}
case TSDataType.BLOB:
case TSDataType.OBJECT:
{
// OBJECT uses the same length-prefixed binary encoding as BLOB.
for (int j = 0; j < RowNumber; j++)
{
var value = _values[j][i];
Expand Down
1 change: 1 addition & 0 deletions src/Apache.IoTDB/IoTDBConstants.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,6 +33,7 @@ public enum TSDataType
DATE = 9,
BLOB = 10,
STRING = 11,
OBJECT = 12,
}

public enum TSEncoding
Expand Down
35 changes: 35 additions & 0 deletions src/Apache.IoTDB/Utils.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;

Expand DownExpand Up@@ -121,5 +122,39 @@ public static string ByteArrayToHexString(byte[] bytes)
{
return "0x" + BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
}

/// <summary>
/// Formats the wire representation of a stored OBJECT value for display.
/// The server stores OBJECT cells as an 8-byte big-endian file size
/// followed by the internal object path; this renders the size in
/// human-readable units (mirrors the Go client's objectBytesToString).
/// </summary>
public static string ObjectBytesToString(byte[] input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
if (input.Length < 8)
throw new ArgumentException(
$"Invalid OBJECT value: expected at least 8 bytes, got {input.Length}.",
nameof(input));

ulong size = 0;
for (int i = 0; i < 8; i++)
{
size = (size << 8) | input[i];
}

const ulong kilobyte = 1024;
const ulong megabyte = kilobyte * 1024;
const ulong gigabyte = megabyte * 1024;

if (size < kilobyte)
return $"(Object) {size} B";
if (size < megabyte)
return string.Format(CultureInfo.InvariantCulture, "(Object) {0:F2} KB", (double)size / kilobyte);
if (size < gigabyte)
return string.Format(CultureInfo.InvariantCulture, "(Object) {0:F2} MB", (double)size / megabyte);
return string.Format(CultureInfo.InvariantCulture, "(Object) {0:F2} GB", (double)size / gigabyte);
}
}
}
96 changes: 96 additions & 0 deletions tests/Apache.IoTDB.Tests/RpcDataSetTests.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -184,5 +184,101 @@ public void GetRow_DataTypesMatchMeasurements()
Assert.That(row0.DataTypes.Count, Is.EqualTo(row0.Values.Count),
"DataTypes count should match Values count.");
}

private static byte[] BuildObjectTsBlockBytes()
{
var payload = new List<byte>();
payload.AddRange(new byte[] { 0, 0, 0, 0, 0, 0, 0x04, 0x00 }); // size 1024 BE
payload.AddRange(System.Text.Encoding.UTF8.GetBytes("internal/path/1.bin"));

var buf = new ByteBuffer(256);
buf.AddInt(1); // value column count
buf.AddByte((byte)TSDataType.OBJECT); // value column type
buf.AddInt(1); // position count
buf.AddByte((byte)ColumnEncoding.Int64Array); // time encoding
buf.AddByte((byte)ColumnEncoding.BinaryArray); // value encoding

buf.AddByte(0); // time mayHaveNull
buf.AddLong(1000L); // timestamp

buf.AddByte(0); // object mayHaveNull
buf.AddInt(payload.Count);
foreach (var b in payload)
{
buf.AddByte(b);
}

return buf.GetBuffer();
}

private RpcDataSet CreateObjectDataSet()
{
var columnNames = new List<string> { "file" };
var columnTypes = new List<string> { "OBJECT" };
var columnNameIndex = new Dictionary<string, int> { { "file", 0 } };
var columnIndex2TsBlockColumnIndexList = new List<int> { 0 };

return new RpcDataSet(
sql: "select file from object_table",
columnNameList: columnNames,
columnTypeList: columnTypes,
columnNameIndex: columnNameIndex,
ignoreTimestamp: false,
moreData: false,
queryId: 1,
statementId: 1,
client: null,
sessionId: 1,
queryResult: new List<byte[]> { BuildObjectTsBlockBytes() },
fetchSize: 1024,
timeout: 10000,
zoneId: "UTC",
columnIndex2TsBlockColumnIndexList: columnIndex2TsBlockColumnIndexList
);
}

[Test]
public void GetObject_ObjectColumn_ReturnsFormattedSizeString()
{
var dataSet = CreateObjectDataSet();
dataSet.Next();

Assert.That(dataSet.GetObject("file"), Is.EqualTo("(Object) 1.00 KB"));
// By-index APIs are 1-based with the implicit Time column at index 1,
// so the OBJECT column is index 2.
Assert.That(dataSet.GetObjectByIndex(2), Is.EqualTo("(Object) 1.00 KB"));
}

[Test]
public void GetString_ObjectColumn_ReturnsFormattedSizeString()
{
var dataSet = CreateObjectDataSet();
dataSet.Next();

Assert.That(dataSet.GetString("file"), Is.EqualTo("(Object) 1.00 KB"));
Assert.That(dataSet.GetStringByIndex(2), Is.EqualTo("(Object) 1.00 KB"));
}

[Test]
public void GetRow_ObjectColumn_KeepsObjectTypeAndValue()
{
var dataSet = CreateObjectDataSet();
dataSet.Next();
var row = dataSet.GetRow();

Assert.That(row.Measurements, Does.Contain("file"));
Assert.That(row.DataTypes, Does.Contain(TSDataType.OBJECT));
Assert.That(row.Values, Does.Contain("(Object) 1.00 KB"));
}

[Test]
public void GetBinary_ObjectColumn_Throws()
{
var dataSet = CreateObjectDataSet();
dataSet.Next();

Assert.Throws<InvalidOperationException>(() => dataSet.GetBinary("file"));
Assert.Throws<InvalidOperationException>(() => dataSet.GetBinaryByIndex(2));
}
}
}
Loading
Loading