modules/platforms/dotnet/Apache.Ignite/Internal/Proto/BinaryTuple/BinaryTupleBuilder.cs (859 lines of code) (raw):
/*
* 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.
*/
namespace Apache.Ignite.Internal.Proto.BinaryTuple
{
using System;
using System.Buffers.Binary;
using System.Collections;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.InteropServices;
using Buffers;
using Ignite.Sql;
using NodaTime;
using Table;
/// <summary>
/// Binary tuple builder.
/// </summary>
internal ref struct BinaryTupleBuilder
{
/** Number of elements in the tuple. */
private readonly int _numElements;
/** Size of an offset table entry. */
private readonly int _entrySize;
/** Position of the varlen offset table. */
private readonly int _entryBase;
/** Starting position of variable-length values. */
private readonly int _valueBase;
/** Colocation column index provider. When not null, used to compute colocation hash on the fly. */
private readonly IHashedColumnIndexProvider? _hashedColumnsPredicate;
/** Buffer for tuple content. */
private readonly PooledArrayBuffer _buffer;
/** Current element. */
private int _elementIndex;
/// <summary>
/// Initializes a new instance of the <see cref="BinaryTupleBuilder"/> struct.
/// </summary>
/// <param name="numElements">Capacity.</param>
/// <param name="totalValueSize">Total value size, -1 when unknown.</param>
/// <param name="hashedColumnsPredicate">A predicate that returns true for colocation column indexes.
/// Pass null when colocation hash is not needed.</param>
public BinaryTupleBuilder(
int numElements,
int totalValueSize = -1,
IHashedColumnIndexProvider? hashedColumnsPredicate = null)
{
Debug.Assert(numElements >= 0, "numElements >= 0");
_numElements = numElements;
_hashedColumnsPredicate = hashedColumnsPredicate;
_buffer = new();
_elementIndex = 0;
// Reserve buffer for individual hash codes.
_entryBase = _hashedColumnsPredicate != null
? BinaryTupleCommon.HeaderSize + _hashedColumnsPredicate.HashedColumnCount * 4
: BinaryTupleCommon.HeaderSize;
_entrySize = totalValueSize < 0
? 4
: BinaryTupleCommon.FlagsToEntrySize(BinaryTupleCommon.ValueSizeToFlags(totalValueSize));
_valueBase = _entryBase + _entrySize * numElements;
_buffer.GetSpan(size: _valueBase)[.._valueBase].Clear();
_buffer.Advance(_valueBase);
}
/// <summary>
/// Gets the current element index.
/// </summary>
public int ElementIndex => _elementIndex;
/// <summary>
/// Gets the hash from column values according to specified <see cref="IHashedColumnIndexProvider"/>.
/// </summary>
/// <returns>Column hash according to specified <see cref="IHashedColumnIndexProvider"/>.</returns>
public int GetHash()
{
if (_hashedColumnsPredicate == null)
{
return 0;
}
var hash = 0;
var hashes = GetHashSpan();
for (var i = 0; i < _hashedColumnsPredicate.HashedColumnCount; i++)
{
var colHash = hashes[i];
hash = HashUtils.Combine(hash, colHash);
}
return hash;
}
/// <summary>
/// Appends a null value.
/// </summary>
public void AppendNull()
{
if (GetHashOrder() is { } hashOrder)
{
PutHash(hashOrder, HashUtils.Hash32((sbyte)0));
}
OnWrite();
}
/// <summary>
/// Appends a byte.
/// </summary>
/// <param name="value">Value.</param>
public void AppendByte(sbyte value)
{
if (GetHashOrder() is { } hashOrder)
{
PutHash(hashOrder, HashUtils.Hash32(value));
}
PutByte(value);
OnWrite();
}
/// <summary>
/// Appends a byte.
/// </summary>
/// <param name="value">Value.</param>
public void AppendByteNullable(sbyte? value)
{
if (value == null)
{
AppendNull();
}
else
{
AppendByte(value.Value);
}
}
/// <summary>
/// Appends a short.
/// </summary>
/// <param name="value">Value.</param>
public void AppendShort(short value)
{
if (GetHashOrder() is { } hashOrder)
{
PutHash(hashOrder, HashUtils.Hash32(value));
}
if (value >= sbyte.MinValue && value <= sbyte.MaxValue)
{
PutByte((sbyte)value);
}
else
{
PutShort(value);
}
OnWrite();
}
/// <summary>
/// Appends a short.
/// </summary>
/// <param name="value">Value.</param>
public void AppendShortNullable(short? value)
{
if (value == null)
{
AppendNull();
}
else
{
AppendShort(value.Value);
}
}
/// <summary>
/// Appends an int.
/// </summary>
/// <param name="value">Value.</param>
public void AppendInt(int value)
{
if (GetHashOrder() is { } hashOrder)
{
PutHash(hashOrder, HashUtils.Hash32(value));
}
if (value >= sbyte.MinValue && value <= sbyte.MaxValue)
{
PutByte((sbyte)value);
}
else if (value >= short.MinValue && value <= short.MaxValue)
{
PutShort((short)value);
}
else
{
PutInt(value);
}
OnWrite();
}
/// <summary>
/// Appends an int.
/// </summary>
/// <param name="value">Value.</param>
public void AppendIntNullable(int? value)
{
if (value == null)
{
AppendNull();
}
else
{
AppendInt(value.Value);
}
}
/// <summary>
/// Appends a long.
/// </summary>
/// <param name="value">Value.</param>
public void AppendLong(long value)
{
if (GetHashOrder() is { } hashOrder)
{
PutHash(hashOrder, HashUtils.Hash32(value));
}
if (value >= sbyte.MinValue && value <= sbyte.MaxValue)
{
PutByte((sbyte)value);
}
else if (value >= short.MinValue && value <= short.MaxValue)
{
PutShort((short)value);
}
else if (value >= int.MinValue && value <= int.MaxValue)
{
PutInt((int)value);
}
else
{
PutLong(value);
}
OnWrite();
}
/// <summary>
/// Appends a long.
/// </summary>
/// <param name="value">Value.</param>
public void AppendLongNullable(long? value)
{
if (value == null)
{
AppendNull();
}
else
{
AppendLong(value.Value);
}
}
/// <summary>
/// Appends a gloat.
/// </summary>
/// <param name="value">Value.</param>
public void AppendFloat(float value)
{
if (GetHashOrder() is { } hashOrder)
{
PutHash(hashOrder, HashUtils.Hash32(value));
}
PutFloat(value);
OnWrite();
}
/// <summary>
/// Appends a gloat.
/// </summary>
/// <param name="value">Value.</param>
public void AppendFloatNullable(float? value)
{
if (value == null)
{
AppendNull();
}
else
{
AppendFloat(value.Value);
}
}
/// <summary>
/// Appends a double.
/// </summary>
/// <param name="value">Value.</param>
public void AppendDouble(double value)
{
if (GetHashOrder() is { } hashOrder)
{
PutHash(hashOrder, HashUtils.Hash32(value));
}
// ReSharper disable once CompareOfFloatsByEqualityOperator
if (value == (float)value)
{
PutFloat((float)value);
}
else
{
PutDouble(value);
}
OnWrite();
}
/// <summary>
/// Appends a double.
/// </summary>
/// <param name="value">Value.</param>
public void AppendDoubleNullable(double? value)
{
if (value == null)
{
AppendNull();
}
else
{
AppendDouble(value.Value);
}
}
/// <summary>
/// Appends a string.
/// </summary>
/// <param name="value">Value.</param>
public void AppendString(string value)
{
PutString(value);
OnWrite();
}
/// <summary>
/// Appends a string.
/// </summary>
/// <param name="value">Value.</param>
public void AppendStringNullable(string? value)
{
if (value == null)
{
AppendNull();
}
else
{
AppendString(value);
}
}
/// <summary>
/// Appends bytes.
/// </summary>
/// <param name="value">Value.</param>
public void AppendBytes(Span<byte> value)
{
if (GetHashOrder() is { } hashOrder)
{
PutHash(hashOrder, HashUtils.Hash32(value));
}
PutBytes(value);
OnWrite();
}
/// <summary>
/// Appends bytes.
/// </summary>
/// <param name="value">Value.</param>
public void AppendBytes(byte[] value) => AppendBytes(value.AsSpan());
/// <summary>
/// Appends bytes.
/// </summary>
/// <param name="value">Value.</param>
public void AppendBytesNullable(byte[]? value)
{
if (value == null)
{
AppendNull();
}
else
{
AppendBytes(value);
}
}
/// <summary>
/// Appends a guid.
/// </summary>
/// <param name="value">Value.</param>
public void AppendGuid(Guid value)
{
var span = GetSpan(16);
UuidSerializer.Write(value, span);
if (GetHashOrder() is { } hashOrder)
{
var lo = BinaryPrimitives.ReadInt64LittleEndian(span[..8]);
var hi = BinaryPrimitives.ReadInt64LittleEndian(span[8..]);
var hash = HashUtils.Hash32(hi, HashUtils.Hash32(lo));
PutHash(hashOrder, hash);
}
OnWrite();
}
/// <summary>
/// Appends a guid.
/// </summary>
/// <param name="value">Value.</param>
public void AppendGuidNullable(Guid? value)
{
if (value == null)
{
AppendNull();
}
else
{
AppendGuid(value.Value);
}
}
/// <summary>
/// Appends a bitmask.
/// </summary>
/// <param name="value">Value.</param>
public void AppendBitmask(BitArray value)
{
var size = (value.Length + 7) / 8; // Ceiling division.
var arr = ByteArrayPool.Rent(size);
try
{
value.CopyTo(arr, 0);
// Trim zero bytes.
while (size > 0 && arr[size - 1] == 0)
{
size--;
}
var resBytes = arr.AsSpan()[..size];
if (GetHashOrder() is { } hashOrder)
{
PutHash(hashOrder, HashUtils.Hash32(resBytes));
}
PutBytes(resBytes);
OnWrite();
}
finally
{
ByteArrayPool.Return(arr);
}
}
/// <summary>
/// Appends a bitmask.
/// </summary>
/// <param name="value">Value.</param>
public void AppendBitmaskNullable(BitArray? value)
{
if (value == null)
{
AppendNull();
}
else
{
AppendBitmask(value);
}
}
/// <summary>
/// Appends a decimal.
/// </summary>
/// <param name="value">Value.</param>
/// <param name="scale">Decimal scale from schema.</param>
public void AppendDecimal(decimal value, int scale) =>
AppendNumber(BinaryTupleCommon.DecimalToUnscaledBigInteger(value, scale));
/// <summary>
/// Appends a decimal.
/// </summary>
/// <param name="value">Value.</param>
/// <param name="scale">Decimal scale from schema.</param>
public void AppendDecimalNullable(decimal? value, int scale)
{
if (value == null)
{
AppendNull();
}
else
{
AppendDecimal(value.Value, scale);
}
}
/// <summary>
/// Appends a number.
/// </summary>
/// <param name="value">Value.</param>
public void AppendNumber(BigInteger value)
{
var size = value.GetByteCount();
var destination = GetSpan(size);
var success = value.TryWriteBytes(destination, out int written, isBigEndian: true);
if (GetHashOrder() is { } hashOrder)
{
PutHash(hashOrder, HashUtils.Hash32(destination[..written]));
}
Debug.Assert(success, "success");
Debug.Assert(written == size, "written == size");
OnWrite();
}
/// <summary>
/// Appends a number.
/// </summary>
/// <param name="value">Value.</param>
public void AppendNumberNullable(BigInteger? value)
{
if (value == null)
{
AppendNull();
}
else
{
AppendNumber(value.Value);
}
}
/// <summary>
/// Appends a date.
/// </summary>
/// <param name="value">Value.</param>
public void AppendDate(LocalDate value)
{
if (GetHashOrder() is { } hashOrder)
{
PutHash(hashOrder, HashUtils.Hash32(value));
}
PutDate(value);
OnWrite();
}
/// <summary>
/// Appends a date.
/// </summary>
/// <param name="value">Value.</param>
public void AppendDateNullable(LocalDate? value)
{
if (value == null)
{
AppendNull();
}
else
{
AppendDate(value.Value);
}
}
/// <summary>
/// Appends a time.
/// </summary>
/// <param name="value">Value.</param>
/// <param name="precision">Precision.</param>
public void AppendTime(LocalTime value, int precision)
{
if (GetHashOrder() is { } hashOrder)
{
PutHash(hashOrder, HashUtils.Hash32(value, precision));
}
PutTime(value, precision);
OnWrite();
}
/// <summary>
/// Appends a time.
/// </summary>
/// <param name="value">Value.</param>
/// <param name="precision">Precision.</param>
public void AppendTimeNullable(LocalTime? value, int precision)
{
if (value == null)
{
AppendNull();
}
else
{
AppendTime(value.Value, precision);
}
}
/// <summary>
/// Appends a date and time.
/// </summary>
/// <param name="value">Value.</param>
/// <param name="precision">Precision.</param>
public void AppendDateTime(LocalDateTime value, int precision)
{
if (GetHashOrder() is { } hashOrder)
{
PutHash(hashOrder, HashUtils.Hash32(value, precision));
}
PutDate(value.Date);
PutTime(value.TimeOfDay, precision);
OnWrite();
}
/// <summary>
/// Appends a date and time.
/// </summary>
/// <param name="value">Value.</param>
/// <param name="precision">Precision.</param>
public void AppendDateTimeNullable(LocalDateTime? value, int precision)
{
if (value == null)
{
AppendNull();
}
else
{
AppendDateTime(value.Value, precision);
}
}
/// <summary>
/// Appends a timestamp (instant).
/// </summary>
/// <param name="value">Value.</param>
/// <param name="precision">Precision.</param>
public void AppendTimestamp(Instant value, int precision)
{
var (seconds, nanos) = PutTimestamp(value, precision);
if (GetHashOrder() is { } hashOrder)
{
var hash = HashUtils.Hash32(nanos, HashUtils.Hash32(seconds));
PutHash(hashOrder, hash);
}
OnWrite();
}
/// <summary>
/// Appends a timestamp (instant).
/// </summary>
/// <param name="value">Value.</param>
/// <param name="precision">Precision.</param>
public void AppendTimestampNullable(Instant? value, int precision)
{
if (value == null)
{
AppendNull();
}
else
{
AppendTimestamp(value.Value, precision);
}
}
/// <summary>
/// Appends a duration.
/// </summary>
/// <param name="value">Value.</param>
public void AppendDuration(Duration value)
{
if (GetHashOrder() is not null)
{
// Colocation keys can't include Duration.
throw new NotSupportedException("Duration hashing is not supported.");
}
PutDuration(value);
OnWrite();
}
/// <summary>
/// Appends a duration.
/// </summary>
/// <param name="value">Value.</param>
public void AppendDurationNullable(Duration? value)
{
if (value == null)
{
AppendNull();
}
else
{
AppendDuration(value.Value);
}
}
/// <summary>
/// Appends a period.
/// </summary>
/// <param name="value">Value.</param>
public void AppendPeriod(Period value)
{
if (GetHashOrder() is not null)
{
// Colocation keys can't include Period.
throw new NotSupportedException("Period hashing is not supported.");
}
PutPeriod(value);
OnWrite();
}
/// <summary>
/// Appends a period.
/// </summary>
/// <param name="value">Value.</param>
public void AppendPeriodNullable(Period? value)
{
if (value == null)
{
AppendNull();
}
else
{
AppendPeriod(value);
}
}
/// <summary>
/// Appends an object.
/// </summary>
/// <param name="value">Value.</param>
/// <param name="colType">Column type.</param>
/// <param name="scale">Decimal scale.</param>
/// <param name="precision">Precision.</param>
public void AppendObject(object? value, ColumnType colType, int scale = 0, int precision = TemporalTypes.MaxTimePrecision)
{
if (value == null)
{
AppendNull();
return;
}
switch (colType)
{
case ColumnType.Int8:
AppendByte((sbyte)value);
break;
case ColumnType.Int16:
AppendShort((short)value);
break;
case ColumnType.Int32:
AppendInt((int)value);
break;
case ColumnType.Int64:
AppendLong((long)value);
break;
case ColumnType.Float:
AppendFloat((float)value);
break;
case ColumnType.Double:
AppendDouble((double)value);
break;
case ColumnType.Uuid:
AppendGuid((Guid)value);
break;
case ColumnType.String:
AppendString((string)value);
break;
case ColumnType.ByteArray:
AppendBytes((byte[])value);
break;
case ColumnType.Bitmask:
AppendBitmask((BitArray)value);
break;
case ColumnType.Decimal:
AppendDecimal((decimal)value, scale);
break;
case ColumnType.Number:
AppendNumber((BigInteger)value);
break;
case ColumnType.Date:
AppendDate((LocalDate)value);
break;
case ColumnType.Time:
AppendTime((LocalTime)value, precision);
break;
case ColumnType.Datetime:
AppendDateTime((LocalDateTime)value, precision);
break;
case ColumnType.Timestamp:
AppendTimestamp((Instant)value, precision);
break;
default:
throw new IgniteClientException(ErrorGroups.Client.Protocol, "Unsupported type: " + colType);
}
}
/// <summary>
/// Appends an object.
/// </summary>
/// <param name="value">Value.</param>
/// <param name="timePrecision">Time precision.</param>
/// <param name="timestampPrecision">Timestamp precision.</param>
public void AppendObjectWithType(
object? value,
int timePrecision = TemporalTypes.MaxTimePrecision,
int timestampPrecision = TemporalTypes.MaxTimePrecision)
{
switch (value)
{
case null:
AppendNull(); // Type.
AppendNull(); // Scale.
AppendNull(); // Value.
break;
case int i32:
AppendTypeAndScale(ColumnType.Int32);
AppendInt(i32);
break;
case long i64:
AppendTypeAndScale(ColumnType.Int64);
AppendLong(i64);
break;
case string str:
AppendTypeAndScale(ColumnType.String);
AppendString(str);
break;
case Guid uuid:
AppendTypeAndScale(ColumnType.Uuid);
AppendGuid(uuid);
break;
case sbyte i8:
AppendTypeAndScale(ColumnType.Int8);
AppendByte(i8);
break;
case short i16:
AppendTypeAndScale(ColumnType.Int16);
AppendShort(i16);
break;
case float f32:
AppendTypeAndScale(ColumnType.Float);
AppendFloat(f32);
break;
case double f64:
AppendTypeAndScale(ColumnType.Double);
AppendDouble(f64);
break;
case byte[] bytes:
AppendTypeAndScale(ColumnType.ByteArray);
AppendBytes(bytes);
break;
case decimal dec:
var scale = GetDecimalScale(dec);
AppendTypeAndScale(ColumnType.Decimal, scale);
AppendDecimal(dec, scale);
break;
case BigInteger bigInt:
AppendTypeAndScale(ColumnType.Number);
AppendNumber(bigInt);
break;
case LocalDate localDate:
AppendTypeAndScale(ColumnType.Date);
AppendDate(localDate);
break;
case LocalTime localTime:
AppendTypeAndScale(ColumnType.Time);
AppendTime(localTime, timePrecision);
break;
case LocalDateTime localDateTime:
AppendTypeAndScale(ColumnType.Datetime);
AppendDateTime(localDateTime, timePrecision);
break;
case Instant instant:
AppendTypeAndScale(ColumnType.Timestamp);
AppendTimestamp(instant, timestampPrecision);
break;
case BitArray bitArray:
AppendTypeAndScale(ColumnType.Bitmask);
AppendBitmask(bitArray);
break;
default:
throw new IgniteClientException(ErrorGroups.Client.Protocol, "Unsupported type: " + value.GetType());
}
}
/// <summary>
/// Builds the tuple.
/// <para />
/// NOTE: This should be called only once as it messes up with accumulated internal data.
/// </summary>
/// <returns>Resulting memory.</returns>
public Memory<byte> Build()
{
int baseOffset = _entryBase - BinaryTupleCommon.HeaderSize;
int offset = baseOffset;
int valueSize = _buffer.Position - _valueBase;
byte flags = BinaryTupleCommon.ValueSizeToFlags(valueSize);
int desiredEntrySize = BinaryTupleCommon.FlagsToEntrySize(flags);
// Shrink the offset table if needed.
if (desiredEntrySize != _entrySize)
{
if (desiredEntrySize > _entrySize)
{
throw new InvalidOperationException("Offset entry overflow in binary tuple builder");
}
Debug.Assert(_entrySize == 4 || _entrySize == 2, "_entrySize == 4 || _entrySize == 2");
Debug.Assert(desiredEntrySize == 2 || desiredEntrySize == 1, "desiredEntrySize == 2 || desiredEntrySize == 1");
int getIndex = _valueBase;
int putIndex = _valueBase;
while (getIndex > _entryBase)
{
getIndex -= _entrySize;
putIndex -= desiredEntrySize;
var value = _entrySize == 4
? _buffer.ReadInt(getIndex)
: _buffer.ReadShort(getIndex);
if (desiredEntrySize == 1)
{
_buffer.WriteByte((byte)value, putIndex);
}
else
{
_buffer.WriteShort((short)value, putIndex);
}
}
offset = baseOffset + (_entrySize - desiredEntrySize) * _numElements;
}
_buffer.WriteByte(flags, offset);
return _buffer.GetWrittenMemory().Slice(offset);
}
/// <summary>
/// Disposes this instance.
/// </summary>
public void Dispose()
{
_buffer.Dispose();
}
private static int GetDecimalScale(decimal value)
{
Span<int> bits = stackalloc int[4];
decimal.GetBits(value, bits);
return (bits[3] & 0x00FF0000) >> 16;
}
private void PutByte(sbyte value) => _buffer.WriteByte(unchecked((byte)value));
private void PutShort(short value) => _buffer.WriteShort(value);
private void PutInt(int value) => _buffer.WriteInt(value);
private void PutLong(long value) => _buffer.WriteLong(value);
private void PutFloat(float value) => PutInt(BitConverter.SingleToInt32Bits(value));
private void PutDouble(double value) => PutLong(BitConverter.DoubleToInt64Bits(value));
private void PutBytes(Span<byte> bytes)
{
if (bytes.Length == 0)
{
GetSpan(1)[0] = BinaryTupleCommon.VarlenEmptyByte;
}
else if (bytes[0] == BinaryTupleCommon.VarlenEmptyByte)
{
var span = GetSpan(bytes.Length + 1);
span[0] = BinaryTupleCommon.VarlenEmptyByte;
bytes.CopyTo(span[1..]);
}
else
{
bytes.CopyTo(GetSpan(bytes.Length));
}
}
private void PutString(string value)
{
if (value.Length == 0)
{
if (GetHashOrder() is { } hashOrder)
{
PutHash(hashOrder, HashUtils.Hash32(Span<byte>.Empty));
}
_buffer.GetSpan(1)[0] = BinaryTupleCommon.VarlenEmptyByte;
_buffer.Advance(1);
return;
}
var maxByteCount = ProtoCommon.StringEncoding.GetMaxByteCount(value.Length);
var span = _buffer.GetSpan(maxByteCount);
var actualBytes = ProtoCommon.StringEncoding.GetBytes(value, span);
span = span[..actualBytes];
if (GetHashOrder() is { } hashOrder2)
{
PutHash(hashOrder2, HashUtils.Hash32(span));
}
_buffer.Advance(actualBytes);
// UTF-8 encoded strings should not start with 0x80 (character codes larger than 127 have a multi-byte encoding).
// We trust this but verify.
if (span[0] == BinaryTupleCommon.VarlenEmptyByte)
{
throw new InvalidOperationException(
$"Failed to encode a string element: resulting payload starts with invalid {BinaryTupleCommon.VarlenEmptyByte} byte");
}
}
private (long Seconds, int Nanos) PutTimestamp(Instant value, int precision)
{
var (seconds, nanos) = value.ToSecondsAndNanos(precision);
PutLong(seconds);
if (nanos != 0)
{
PutInt(nanos);
}
return (seconds, nanos);
}
private void PutDuration(Duration value)
{
// Logic taken from
// https://github.com/nodatime/nodatime.serialization/blob/main/src/NodaTime.Serialization.Protobuf/NodaExtensions.cs#L42
// (Apache License).
long days = value.Days;
long nanoOfDay = value.NanosecondOfDay;
long secondOfDay = nanoOfDay / NodaConstants.NanosecondsPerSecond;
long seconds = days * NodaConstants.SecondsPerDay + secondOfDay;
int nanos = value.SubsecondNanoseconds;
PutLong(seconds);
if (nanos != 0)
{
PutInt(nanos);
}
}
private void PutPeriod(Period value)
{
if (value.HasTimeComponent)
{
throw new NotSupportedException("Period with time component is not supported.");
}
if (value.Weeks != 0)
{
throw new NotSupportedException("Period with weeks component is not supported.");
}
int years = value.Years;
int months = value.Months;
int days = value.Days;
if (years is >= sbyte.MinValue and <= sbyte.MaxValue &&
months is >= sbyte.MinValue and <= sbyte.MaxValue &&
days is >= sbyte.MinValue and <= sbyte.MaxValue)
{
PutByte((sbyte) years);
PutByte((sbyte) months);
PutByte((sbyte) days);
}
else if (years is >= short.MinValue and <= short.MaxValue &&
months is >= short.MinValue and <= short.MaxValue &&
days is >= short.MinValue and <= short.MaxValue)
{
PutShort((short) years);
PutShort((short) months);
PutShort((short) days);
}
else
{
PutInt(years);
PutInt(months);
PutInt(days);
}
}
private void PutTime(LocalTime value, int precision)
{
long hour = value.Hour;
long minute = value.Minute;
long second = value.Second;
long nanos = TemporalTypes.NormalizeNanos(value.NanosecondOfSecond, precision);
if ((nanos % 1000) != 0)
{
long time = (hour << 42) | (minute << 36) | (second << 30) | nanos;
PutInt((int)time);
PutShort((short)(time >> 32));
}
else if ((nanos % 1000000) != 0)
{
long time = (hour << 32) | (minute << 26) | (second << 20) | (nanos / 1000);
PutInt((int)time);
PutByte((sbyte)(time >> 32));
}
else
{
long time = (hour << 22) | (minute << 16) | (second << 10) | (nanos / 1000000);
PutInt((int)time);
}
}
private void PutDate(LocalDate value)
{
int year = value.Year;
int month = value.Month;
int day = value.Day;
int date = (year << 9) | (month << 5) | day;
// Write int32 as 3 bytes, preserving sign.
Span<byte> buf = stackalloc byte[4];
BinaryPrimitives.WriteInt32LittleEndian(buf, date << 8);
buf[1..].CopyTo(GetSpan(3));
}
private void AppendTypeAndScale(ColumnType type, int scale = 0)
{
AppendInt((int)type);
AppendInt(scale);
}
private void OnWrite()
{
Debug.Assert(_elementIndex < _numElements, "_elementIndex < _numElements");
int offset = _buffer.Position - _valueBase;
switch (_entrySize)
{
case 1:
_buffer.WriteByte((byte)offset, _entryBase + _elementIndex);
break;
case 2:
_buffer.WriteShort((short)offset, _entryBase + _elementIndex * 2);
break;
case 4:
_buffer.WriteInt(offset, _entryBase + _elementIndex * 4);
break;
default:
throw new InvalidOperationException("Tuple entry size is invalid.");
}
_elementIndex++;
}
private Span<byte> GetSpan(int size)
{
var span = _buffer.GetSpan(size);
_buffer.Advance(size);
return span;
}
private int? GetHashOrder() => _hashedColumnsPredicate?.HashedColumnOrder(_elementIndex) switch
{
null or < 0 => null,
{ } order => order
};
private void PutHash(int index, int hash)
{
Debug.Assert(_hashedColumnsPredicate != null, "_hashedColumnsPredicate != null");
Debug.Assert(index >= 0, "index >= 0");
Debug.Assert(index < _hashedColumnsPredicate.HashedColumnCount, "index < _hashedColumnsPredicate.HashedColumnCount");
GetHashSpan()[index] = hash;
}
private Span<int> GetHashSpan() => MemoryMarshal.Cast<byte, int>(_buffer.GetWrittenMemory().Span);
}
}