in modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleReader.cs [333:633]
public LocalTime? GetTimeNullable(int index) => Seek(index) switch
{
{ IsEmpty: true } => null,
{ Length: >= 4 and <= 6 } s => ReadTime(s),
var s => throw GetInvalidLengthException(index, 6, s.Length)
};
/// <summary>
/// Gets a local date and time value.
/// </summary>
/// <param name="index">Index.</param>
/// <returns>Value.</returns>
public LocalDateTime GetDateTime(int index) => GetDateTimeNullable(index) ?? ThrowNullElementException<LocalDateTime>(index);
/// <summary>
/// Gets a local date and time value.
/// </summary>
/// <param name="index">Index.</param>
/// <returns>Value.</returns>
public LocalDateTime? GetDateTimeNullable(int index) => Seek(index) switch
{
{ IsEmpty: true } => null,
{ Length: >= 7 and <= 9 } s => ReadDate(s) + ReadTime(s[3..]),
var s => throw GetInvalidLengthException(index, 9, s.Length)
};
/// <summary>
/// Gets a timestamp (instant) value.
/// </summary>
/// <param name="index">Index.</param>
/// <returns>Value.</returns>
public Instant GetTimestamp(int index) => GetTimestampNullable(index) ?? ThrowNullElementException<Instant>(index);
/// <summary>
/// Gets a timestamp (instant) value.
/// </summary>
/// <param name="index">Index.</param>
/// <returns>Value.</returns>
public Instant? GetTimestampNullable(int index) => Seek(index) switch
{
{ IsEmpty: true } => null,
{ Length: 8 } s => Instant.FromUnixTimeSeconds(BinaryPrimitives.ReadInt64LittleEndian(s)),
{ Length: 12 } s => Instant.FromUnixTimeSeconds(BinaryPrimitives.ReadInt64LittleEndian(s))
.PlusNanoseconds(BinaryPrimitives.ReadInt32LittleEndian(s[8..])),
var s => throw GetInvalidLengthException(index, 12, s.Length)
};
/// <summary>
/// Gets a duration value.
/// </summary>
/// <param name="index">Index.</param>
/// <returns>Value.</returns>
public Duration GetDuration(int index) => GetDurationNullable(index) ?? ThrowNullElementException<Duration>(index);
/// <summary>
/// Gets a duration value.
/// </summary>
/// <param name="index">Index.</param>
/// <returns>Value.</returns>
public Duration? GetDurationNullable(int index) => Seek(index) switch
{
{ IsEmpty: true } => null,
{ Length: 8 } s => Duration.FromSeconds(BinaryPrimitives.ReadInt64LittleEndian(s)),
{ Length: 12 } s => Duration.FromSeconds(BinaryPrimitives.ReadInt64LittleEndian(s))
.Plus(Duration.FromNanoseconds(BinaryPrimitives.ReadInt32LittleEndian(s[8..]))),
var s => throw GetInvalidLengthException(index, 12, s.Length)
};
/// <summary>
/// Gets a period value.
/// </summary>
/// <param name="index">Index.</param>
/// <returns>Value.</returns>
public Period GetPeriod(int index) => GetPeriodNullable(index) ?? ThrowNullElementException<Period>(index);
/// <summary>
/// Gets a period value.
/// </summary>
/// <param name="index">Index.</param>
/// <returns>Value.</returns>
public Period? GetPeriodNullable(int index) => Seek(index) switch
{
{ IsEmpty: true } => null,
{ Length: 3 } s => Period.FromYears(unchecked((sbyte)s[0])) +
Period.FromMonths(unchecked((sbyte)s[1])) +
Period.FromDays(unchecked((sbyte)s[2])),
{ Length: 6 } s => Period.FromYears(BinaryPrimitives.ReadInt16LittleEndian(s)) +
Period.FromMonths(BinaryPrimitives.ReadInt16LittleEndian(s[2..])) +
Period.FromDays(BinaryPrimitives.ReadInt16LittleEndian(s[4..])),
{ Length: 12 } s => Period.FromYears(BinaryPrimitives.ReadInt32LittleEndian(s)) +
Period.FromMonths(BinaryPrimitives.ReadInt32LittleEndian(s[4..])) +
Period.FromDays(BinaryPrimitives.ReadInt32LittleEndian(s[8..])),
var s => throw GetInvalidLengthException(index, 12, s.Length)
};
/// <summary>
/// Gets bytes.
/// </summary>
/// <param name="index">Index.</param>
/// <returns>Value.</returns>
public byte[] GetBytes(int index) => GetBytesNullable(index) ?? throw GetNullElementException(index);
/// <summary>
/// Gets bytes.
/// </summary>
/// <param name="index">Index.</param>
/// <returns>Value.</returns>
public byte[]? GetBytesNullable(int index) => Seek(index) switch
{
{ IsEmpty: true } => null,
var s when s[0] == BinaryTupleCommon.VarlenEmptyByte => s[1..].ToArray(),
var s => s.ToArray()
};
/// <summary>
/// Gets bytes.
/// </summary>
/// <param name="index">Index.</param>
/// <returns>Value.</returns>
public ReadOnlySpan<byte> GetBytesSpan(int index) => Seek(index) switch
{
{ IsEmpty: true } => throw GetNullElementException(index),
var s when s[0] == BinaryTupleCommon.VarlenEmptyByte => s[1..],
var s => s
};
/// <summary>
/// Gets an object value according to the specified type.
/// </summary>
/// <param name="index">Index.</param>
/// <param name="columnType">Column type.</param>
/// <param name="scale">Column decimal scale.</param>
/// <returns>Value.</returns>
public object? GetObject(int index, ColumnType columnType, int scale = 0) =>
columnType switch
{
ColumnType.Int8 => GetByteNullable(index),
ColumnType.Int16 => GetShortNullable(index),
ColumnType.Int32 => GetIntNullable(index),
ColumnType.Int64 => GetLongNullable(index),
ColumnType.Float => GetFloatNullable(index),
ColumnType.Double => GetDoubleNullable(index),
ColumnType.Uuid => GetGuidNullable(index),
ColumnType.String => GetStringNullable(index),
ColumnType.Decimal => GetDecimalNullable(index, scale),
ColumnType.ByteArray => GetBytesNullable(index),
ColumnType.Bitmask => GetBitmaskNullable(index),
ColumnType.Date => GetDateNullable(index),
ColumnType.Time => GetTimeNullable(index),
ColumnType.Datetime => GetDateTimeNullable(index),
ColumnType.Timestamp => GetTimestampNullable(index),
ColumnType.Number => GetNumberNullable(index),
ColumnType.Boolean => GetByteAsBoolNullable(index),
ColumnType.Period => GetPeriodNullable(index),
ColumnType.Duration => GetDurationNullable(index),
_ => throw new IgniteClientException(ErrorGroups.Client.Protocol, "Unsupported type: " + columnType)
};
/// <summary>
/// Gets an object value according to the type code at the specified index.
/// </summary>
/// <param name="index">Index.</param>
/// <returns>Value.</returns>
public object? GetObject(int index)
{
if (IsNull(index))
{
return null;
}
var type = (ColumnType)GetInt(index);
var scale = GetInt(index + 1);
return GetObject(index + 2, type, scale);
}
private static LocalDate ReadDate(ReadOnlySpan<byte> span)
{
// Read int32 from 3 bytes, preserving sign.
Span<byte> buf = stackalloc byte[4];
span[..3].CopyTo(buf[1..]);
int date = BinaryPrimitives.ReadInt32LittleEndian(buf) >> 8;
int day = date & 31;
int month = (date >> 5) & 15;
int year = (date >> 9); // Sign matters.
return new LocalDate(year, month, day);
}
private static LocalTime ReadTime(ReadOnlySpan<byte> span)
{
long time = BinaryPrimitives.ReadUInt32LittleEndian(span);
var length = span.Length;
int nanos;
if (length == 4)
{
nanos = ((int) time & ((1 << 10) - 1)) * 1000 * 1000;
time >>= 10;
}
else if (length == 5)
{
time |= (long)span[4] << 32;
nanos = ((int) time & ((1 << 20) - 1)) * 1000;
time >>= 20;
}
else
{
time |= (long)BinaryPrimitives.ReadUInt16LittleEndian(span[4..]) << 32;
nanos = ((int)time & ((1 << 30) - 1));
time >>= 30;
}
int second = ((int) time) & 63;
int minute = ((int) time >> 6) & 63;
int hour = ((int) time >> 12) & 31;
return LocalTime.FromHourMinuteSecondNanosecond(hour, minute, second, nanos);
}
private static decimal? ReadDecimal(ReadOnlySpan<byte> span, int scale)
{
if (span.IsEmpty)
{
return null;
}
var unscaled = new BigInteger(span, isBigEndian: true);
var res = (decimal)unscaled;
if (scale > 0)
{
res /= (decimal)BigInteger.Pow(10, scale);
}
return res;
}
private static T ThrowNullElementException<T>(int index) => throw GetNullElementException(index);
private static InvalidOperationException GetNullElementException(int index) =>
new($"Binary tuple element with index {index} is null.");
private static InvalidOperationException GetInvalidLengthException(int index, int expectedLength, int actualLength) =>
new($"Binary tuple element with index {index} has invalid length (expected {expectedLength}, actual {actualLength}).");
private int GetOffset(int position)
{
var span = _buffer[position..];
switch (_entrySize)
{
case 1:
return span[0];
case 2:
return BinaryPrimitives.ReadUInt16LittleEndian(span);
case 4:
{
var offset = BinaryPrimitives.ReadInt32LittleEndian(span);
if (offset < 0)
{
throw new InvalidOperationException("Unsupported offset table size");
}
return offset;
}
default:
throw new InvalidOperationException("Invalid offset table size");
}
}
private ReadOnlySpan<byte> Seek(int index)
{
Debug.Assert(index >= 0, "index >= 0");
Debug.Assert(index < _numElements, "index < numElements");
int entry = _entryBase + index * _entrySize;
int offset = _valueBase;
if (index > 0)
{
offset += GetOffset(entry - _entrySize);
}
int nextOffset = _valueBase + GetOffset(entry);
if (nextOffset < offset)
{
throw new InvalidOperationException("Corrupted offset table");
}
return _buffer.Slice(offset, nextOffset - offset);
}
}
}