2 using System.Collections.Generic;
3 using System.Globalization;
4 using System.Runtime.CompilerServices;
7 namespace kinetica.Records;
18 private readonly
byte[] _buffer;
19 private int _position;
20 private long _routingHash;
21 private bool _isValid;
28 _buffer =
new byte[bufferSize];
37 public bool IsValid => _isValid && _position > 0;
47 public long HashCode() => _routingHash;
52 public int Route(IList<int> routingTable)
54 if (routingTable ==
null || routingTable.Count == 0)
58 int slot = (int)(Math.Abs(_routingHash) % routingTable.Count);
59 int rank = routingTable[slot];
62 return rank > 0 ? rank - 1 : 0;
87 [MethodImpl(MethodImplOptions.AggressiveInlining)]
91 _buffer[_position++] = (byte)value;
95 [MethodImpl(MethodImplOptions.AggressiveInlining)]
99 _buffer[_position++] = (byte)value;
100 _buffer[_position++] = (byte)(value >> 8);
104 [MethodImpl(MethodImplOptions.AggressiveInlining)]
108 _buffer[_position++] = (byte)value;
109 _buffer[_position++] = (byte)(value >> 8);
110 _buffer[_position++] = (byte)(value >> 16);
111 _buffer[_position++] = (byte)(value >> 24);
115 [MethodImpl(MethodImplOptions.AggressiveInlining)]
119 _buffer[_position++] = (byte)value;
120 _buffer[_position++] = (byte)(value >> 8);
121 _buffer[_position++] = (byte)(value >> 16);
122 _buffer[_position++] = (byte)(value >> 24);
123 _buffer[_position++] = (byte)(value >> 32);
124 _buffer[_position++] = (byte)(value >> 40);
125 _buffer[_position++] = (byte)(value >> 48);
126 _buffer[_position++] = (byte)(value >> 56);
130 [MethodImpl(MethodImplOptions.AggressiveInlining)]
133 var bits = BitConverter.SingleToInt32Bits(value);
138 [MethodImpl(MethodImplOptions.AggressiveInlining)]
141 var bits = BitConverter.DoubleToInt64Bits(value);
167 EnsureCapacity(length);
169 var bytes = Encoding.UTF8.GetBytes(value);
170 int copyLen = Math.Min(bytes.Length, length);
172 Array.Copy(bytes, 0, _buffer, _position, copyLen);
175 for (
int i = copyLen; i < length; i++)
177 _buffer[_position + i] = 0;
192 if (!TryParseDate(value, out
int year, out
int month, out
int day))
201 int packed = year * 10000 + month * 100 + day;
214 if (!TryParseDateTime(value, out
int year, out
int month, out
int day,
215 out
int hour, out
int minute, out
int second, out
int millis))
225 packed |= ((long)(year - 1900) & 0x3FF) << 54;
226 packed |= ((long)month & 0xF) << 50;
227 packed |= ((long)day & 0x1F) << 45;
228 packed |= ((long)hour & 0x1F) << 40;
229 packed |= ((long)minute & 0x3F) << 34;
230 packed |= ((long)second & 0x3F) << 28;
231 packed |= ((long)millis & 0x3FF) << 18;
245 if (!TryParseTime(value, out
int hour, out
int minute, out
int second, out
int millis))
253 int packed = (hour * 3600000) + (minute * 60000) + (second * 1000) + millis;
266 if (!TryParseIpv4(value, out uint ip))
277 public void AddDecimal(
string value,
int precision,
int scale)
305 #region Parsing Helpers 307 private static bool TryParseDate(
string value, out
int year, out
int month, out
int day)
309 year = month = day = 0;
311 if (value.Length < 10)
315 if (
int.TryParse(value.AsSpan(0, 4), out year) &&
316 int.TryParse(value.AsSpan(5, 2), out month) &&
317 int.TryParse(value.AsSpan(8, 2), out day))
325 private static bool TryParseDateTime(
string value,
326 out
int year, out
int month, out
int day,
327 out
int hour, out
int minute, out
int second, out
int millis)
329 year = month = day = hour = minute = second = millis = 0;
331 if (value.Length < 19)
335 if (!TryParseDate(value, out year, out month, out day))
338 if (
int.TryParse(value.AsSpan(11, 2), out hour) &&
339 int.TryParse(value.AsSpan(14, 2), out minute) &&
340 int.TryParse(value.AsSpan(17, 2), out second))
343 if (value.Length > 20 && value[19] ==
'.')
345 var msSpan = value.AsSpan(20);
346 int msLen = Math.Min(msSpan.Length, 3);
347 if (
int.TryParse(msSpan.Slice(0, msLen), out millis))
350 while (msLen < 3) { millis *= 10; msLen++; }
359 private static bool TryParseTime(
string value,
360 out
int hour, out
int minute, out
int second, out
int millis)
362 hour = minute = second = millis = 0;
364 if (value.Length < 8)
368 if (
int.TryParse(value.AsSpan(0, 2), out hour) &&
369 int.TryParse(value.AsSpan(3, 2), out minute) &&
370 int.TryParse(value.AsSpan(6, 2), out second))
373 if (value.Length > 9 && value[8] ==
'.')
375 var msSpan = value.AsSpan(9);
376 int msLen = Math.Min(msSpan.Length, 3);
377 if (
int.TryParse(msSpan.Slice(0, msLen), out millis))
379 while (msLen < 3) { millis *= 10; msLen++; }
388 private static bool TryParseIpv4(
string value, out uint ip)
392 var parts = value.Split(
'.');
393 if (parts.Length != 4)
396 for (
int i = 0; i < 4; i++)
398 if (!
byte.TryParse(parts[i], out
byte b))
408 [MethodImpl(MethodImplOptions.AggressiveInlining)]
409 private void EnsureCapacity(
int additional)
411 if (_position + additional > _buffer.Length)
413 throw new InvalidOperationException(
414 $
"RecordKey buffer overflow: need {_position + additional} bytes, have {_buffer.Length}");
424 private const ulong C1 = 0x87c37b91114253d5UL;
425 private const ulong C2 = 0x4cf5ad432745937fUL;
432 return Hash128(data, 0, data.Length);
438 public static long Hash128(
byte[] data,
int offset,
int length, uint seed = 0)
443 int nblocks = length / 16;
446 for (
int i = 0; i < nblocks; i++)
448 ulong k1 = GetBlock64(data, offset + i * 16);
449 ulong k2 = GetBlock64(data, offset + i * 16 + 8);
452 k1 = RotateLeft(k1, 31);
456 h1 = RotateLeft(h1, 27);
458 h1 = h1 * 5 + 0x52dce729;
461 k2 = RotateLeft(k2, 33);
465 h2 = RotateLeft(h2, 31);
467 h2 = h2 * 5 + 0x38495ab5;
471 int tail = offset + nblocks * 16;
472 int remaining = length - nblocks * 16;
479 case 15: k2_tail ^= (ulong)data[tail + 14] << 48;
goto case 14;
480 case 14: k2_tail ^= (ulong)data[tail + 13] << 40;
goto case 13;
481 case 13: k2_tail ^= (ulong)data[tail + 12] << 32;
goto case 12;
482 case 12: k2_tail ^= (ulong)data[tail + 11] << 24;
goto case 11;
483 case 11: k2_tail ^= (ulong)data[tail + 10] << 16;
goto case 10;
484 case 10: k2_tail ^= (ulong)data[tail + 9] << 8;
goto case 9;
486 k2_tail ^= data[tail + 8];
488 k2_tail = RotateLeft(k2_tail, 33);
492 case 8: k1_tail ^= (ulong)data[tail + 7] << 56;
goto case 7;
493 case 7: k1_tail ^= (ulong)data[tail + 6] << 48;
goto case 6;
494 case 6: k1_tail ^= (ulong)data[tail + 5] << 40;
goto case 5;
495 case 5: k1_tail ^= (ulong)data[tail + 4] << 32;
goto case 4;
496 case 4: k1_tail ^= (ulong)data[tail + 3] << 24;
goto case 3;
497 case 3: k1_tail ^= (ulong)data[tail + 2] << 16;
goto case 2;
498 case 2: k1_tail ^= (ulong)data[tail + 1] << 8;
goto case 1;
500 k1_tail ^= data[tail];
502 k1_tail = RotateLeft(k1_tail, 31);
524 [MethodImpl(MethodImplOptions.AggressiveInlining)]
525 private static ulong GetBlock64(
byte[] data,
int offset)
527 return BitConverter.ToUInt64(data, offset);
530 [MethodImpl(MethodImplOptions.AggressiveInlining)]
531 private static ulong RotateLeft(ulong x,
int r)
533 return (x << r) | (x >> (64 - r));
536 [MethodImpl(MethodImplOptions.AggressiveInlining)]
537 private static ulong FMix64(ulong k)
540 k *= 0xff51afd7ed558ccdUL;
542 k *= 0xc4ceb9fe1a85ec53UL;
static long Hash128(byte[] data)
Computes a 128-bit MurmurHash3 and returns the lower 64 bits.
long RoutingHash
Gets the routing hash (must call ComputeHash first).
void AddUuid(string value)
Adds a UUID string to the key.
static long Hash128(byte[] data, int offset, int length, uint seed=0)
Computes a 128-bit MurmurHash3 and returns the lower 64 bits.
void Invalidate()
Invalidates this key (e.g., when a null value is encountered).
A binary key used for shard routing.
void ComputeHash()
Computes the routing hash from the buffer contents.
void AddInt16(short value)
Adds a 16-bit integer to the key (little-endian).
void AddTime(string value)
Adds a time string (HH:MM:SS.mmm) to the key.
void AddInt8(sbyte value)
Adds an 8-bit integer to the key.
RecordKey(int bufferSize)
Creates a new RecordKey with the specified buffer size.
void AddIpv4(string value)
Adds an IPv4 address string (dotted-quad) to the key.
void AddDecimal(string value, int precision, int scale)
Adds a decimal string to the key.
void AddDateTime(string value)
Adds a datetime string (YYYY-MM-DD HH:MM:SS.mmm) to the key.
long HashCode()
Gets the hash code for stripe distribution.
void AddCharN(string value, int length)
Adds a fixed-length char to the key.
void AddString(string value)
Adds a string to the key (via MurmurHash3).
bool IsValid
Returns true if this key is valid (no null values were added).
void AddLong(long value)
Adds a 64-bit integer to the key (little-endian).
void AddDate(string value)
Adds a date string (YYYY-MM-DD) to the key.
void AddDouble(double value)
Adds a 64-bit double to the key.
void AddInt(int value)
Adds a 32-bit integer to the key (little-endian).
int Route(IList< int > routingTable)
Routes this key to a worker index using the routing table.
void AddFloat(float value)
Adds a 32-bit float to the key.
MurmurHash3 implementation for 128-bit hashing.