Kinetica   C#   API  Version 7.2.3.1
RecordKeyBuilder.cs
Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Runtime.CompilerServices;
4 using System.Text;
5 using System.Text.RegularExpressions;
6 
7 namespace kinetica.Records;
8 
16  public sealed class RecordKeyBuilder
17  {
18  private readonly IReadOnlyList<int> _shardKeyIndices;
19  private readonly IReadOnlyList<ColumnType> _shardKeyTypes;
20  private readonly IReadOnlyDictionary<int, (int Precision, int Scale)> _decimalInfos;
21  private readonly IReadOnlyDictionary<int, int> _charLengths;
22  private readonly int _bufferSize;
23  private readonly IReadOnlyDictionary<string, int> _columnNameToShardIndex;
24 
28  public RecordKeyBuilder(Type type)
29  {
30  if (type == null)
31  throw new ArgumentNullException(nameof(type));
32 
33  var shardKeyIndices = new List<int>();
34  var shardKeyTypes = new List<ColumnType>();
35  var decimalInfos = new Dictionary<int, (int Precision, int Scale)>();
36  var charLengths = new Dictionary<int, int>();
37  var columnNameToShardIndex = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
38  int bufferSize = 0;
39 
40  for (int i = 0; i < type.ShardKeyIndices.Count; i++)
41  {
42  var colIdx = type.ShardKeyIndices[i];
43  var col = type.GetColumn(colIdx)!;
44 
45  shardKeyIndices.Add(colIdx);
46  shardKeyTypes.Add(col.ColumnType);
47  columnNameToShardIndex[col.Name] = i;
48 
49  // Calculate buffer size contribution
50  bufferSize += GetColumnBufferSize(col.ColumnType, col.Properties, i, decimalInfos, charLengths);
51  }
52 
53  _shardKeyIndices = shardKeyIndices;
54  _shardKeyTypes = shardKeyTypes;
55  _decimalInfos = decimalInfos;
56  _charLengths = charLengths;
57  _bufferSize = bufferSize;
58  _columnNameToShardIndex = columnNameToShardIndex;
59  }
60 
64  public RecordKeyBuilder(kinetica.KineticaType ktype, bool isPrimaryKey = false)
65  {
66  if (ktype == null)
67  throw new ArgumentNullException(nameof(ktype));
68 
69  var shardKeyIndices = new List<int>();
70  var shardKeyTypes = new List<ColumnType>();
71  var decimalInfos = new Dictionary<int, (int Precision, int Scale)>();
72  var charLengths = new Dictionary<int, int>();
73  var columnNameToShardIndex = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
74  int bufferSize = 0;
75 
76  var columns = ktype.getColumns();
77  for (int i = 0; i < columns.Count; i++)
78  {
79  var col = columns[i];
80  var props = col.getProperties();
81 
82  bool isKey = isPrimaryKey
83  ? props.Contains(kinetica.ColumnProperty.PRIMARY_KEY)
84  : props.Contains(kinetica.ColumnProperty.SHARD_KEY);
85 
86  if (isKey)
87  {
88  var colType = DetermineColumnType(col);
89  shardKeyIndices.Add(i);
90  shardKeyTypes.Add(colType);
91  columnNameToShardIndex[col.getName()] = shardKeyIndices.Count - 1;
92 
93  bufferSize += GetColumnBufferSizeFromKineticaColumn(col, shardKeyIndices.Count - 1, decimalInfos, charLengths);
94  }
95  }
96 
97  _shardKeyIndices = shardKeyIndices;
98  _shardKeyTypes = shardKeyTypes;
99  _decimalInfos = decimalInfos;
100  _charLengths = charLengths;
101  _bufferSize = bufferSize;
102  _columnNameToShardIndex = columnNameToShardIndex;
103  }
104 
108  public bool HasKey => _shardKeyIndices.Count > 0;
109 
113  public int KeyColumnCount => _shardKeyIndices.Count;
114 
119  {
120  if (!HasKey)
121  return null;
122 
123  if (values.Count != _shardKeyIndices.Count)
124  return null;
125 
126  var key = new RecordKey(_bufferSize);
127 
128  for (int i = 0; i < values.Count; i++)
129  {
130  var (name, value) = values[i];
131 
132  // Find the shard key index for this column name
133  if (!_columnNameToShardIndex.TryGetValue(name, out var shardIdx))
134  {
135  // Column name doesn't match - try positional
136  shardIdx = i;
137  if (shardIdx >= _shardKeyTypes.Count)
138  return null;
139  }
140 
141  var colType = _shardKeyTypes[shardIdx];
142 
143  if (!AddValueToKey(key, value, colType, shardIdx))
144  return null;
145  }
146 
147  if (!key.IsValid)
148  return null;
149 
150  key.ComputeHash();
151  return key;
152  }
153 
154  private bool AddValueToKey(RecordKey key, ShardKeyValue value, ColumnType colType, int shardIdx)
155  {
156  if (value.IsNull)
157  return false; // Null shard key values make the key invalid
158 
159  switch (colType)
160  {
161  case ColumnType.Integer:
162  case ColumnType.Int8:
163  case ColumnType.Int16:
164  case ColumnType.Boolean:
165  if (value.TryGetInt(out var intVal))
166  {
167  key.AddInt(intVal);
168  return true;
169  }
170  return false;
171 
172  case ColumnType.Long:
173  case ColumnType.Timestamp:
174  if (value.TryGetLong(out var longVal))
175  {
176  key.AddLong(longVal);
177  return true;
178  }
179  return false;
180 
181  case ColumnType.Float:
182  key.AddFloat(value.AsFloat());
183  return true;
184 
185  case ColumnType.Double:
186  key.AddDouble(value.AsDouble());
187  return true;
188 
189  case ColumnType.String:
190  case ColumnType.Char1:
191  case ColumnType.Char2:
192  case ColumnType.Char4:
193  case ColumnType.Char8:
194  case ColumnType.Char16:
195  case ColumnType.Char32:
196  case ColumnType.Char64:
197  case ColumnType.Char128:
198  case ColumnType.Char256:
199  if (value.TryGetString(out var strVal) && strVal != null)
200  {
201  // For char types, get the expected length
202  int charLen = _charLengths.TryGetValue(shardIdx, out var len) ? len : 0;
203  if (charLen > 0)
204  key.AddCharN(strVal, charLen);
205  else
206  key.AddString(strVal);
207  return true;
208  }
209  return false;
210 
211  case ColumnType.Date:
212  if (value.TryGetString(out var dateVal) && dateVal != null)
213  {
214  key.AddDate(dateVal);
215  return true;
216  }
217  return false;
218 
219  case ColumnType.DateTime:
220  if (value.TryGetString(out var dtVal) && dtVal != null)
221  {
222  key.AddDateTime(dtVal);
223  return true;
224  }
225  return false;
226 
227  case ColumnType.Time:
228  if (value.TryGetString(out var timeVal) && timeVal != null)
229  {
230  key.AddTime(timeVal);
231  return true;
232  }
233  return false;
234 
235  case ColumnType.Ipv4:
236  if (value.TryGetString(out var ipVal) && ipVal != null)
237  {
238  key.AddIpv4(ipVal);
239  return true;
240  }
241  return false;
242 
243  case ColumnType.Decimal:
244  if (value.TryGetString(out var decVal) && decVal != null)
245  {
246  var (precision, scale) = _decimalInfos.TryGetValue(shardIdx, out var info) ? info : (18, 4);
247  key.AddDecimal(decVal, precision, scale);
248  return true;
249  }
250  return false;
251 
252  case ColumnType.Uuid:
253  if (value.TryGetString(out var uuidVal) && uuidVal != null)
254  {
255  key.AddUuid(uuidVal);
256  return true;
257  }
258  return false;
259 
260  default:
261  return false;
262  }
263  }
264 
265  #region Buffer Size Calculation
266 
267  private static int GetColumnBufferSize(
268  ColumnType colType,
269  IReadOnlyList<string> properties,
270  int shardIdx,
271  Dictionary<int, (int Precision, int Scale)> decimalInfos,
272  Dictionary<int, int> charLengths)
273  {
274  switch (colType)
275  {
276  case ColumnType.Int8:
277  return 1;
278  case ColumnType.Int16:
279  return 2;
280  case ColumnType.Integer:
281  case ColumnType.Boolean:
282  case ColumnType.Ipv4:
283  case ColumnType.Time:
284  case ColumnType.Float:
285  return 4;
286  case ColumnType.Long:
287  case ColumnType.Timestamp:
288  case ColumnType.Date:
289  case ColumnType.DateTime:
290  case ColumnType.Double:
291  case ColumnType.String:
292  case ColumnType.Uuid:
293  return 8;
294  case ColumnType.Char1:
295  charLengths[shardIdx] = 1;
296  return 1;
297  case ColumnType.Char2:
298  charLengths[shardIdx] = 2;
299  return 2;
300  case ColumnType.Char4:
301  charLengths[shardIdx] = 4;
302  return 4;
303  case ColumnType.Char8:
304  charLengths[shardIdx] = 8;
305  return 8;
306  case ColumnType.Char16:
307  charLengths[shardIdx] = 16;
308  return 16;
309  case ColumnType.Char32:
310  charLengths[shardIdx] = 32;
311  return 32;
312  case ColumnType.Char64:
313  charLengths[shardIdx] = 64;
314  return 64;
315  case ColumnType.Char128:
316  charLengths[shardIdx] = 128;
317  return 128;
318  case ColumnType.Char256:
319  charLengths[shardIdx] = 256;
320  return 256;
321  case ColumnType.Decimal:
322  // Parse decimal precision from properties
323  foreach (var prop in properties)
324  {
325  var match = Regex.Match(prop, @"decimal\s*\(\s*(\d+)\s*,\s*(\d+)\s*\)", RegexOptions.IgnoreCase);
326  if (match.Success)
327  {
328  int precision = int.Parse(match.Groups[1].Value);
329  int scale = int.Parse(match.Groups[2].Value);
330  decimalInfos[shardIdx] = (precision, scale);
331  return precision > 18 ? 12 : 8;
332  }
333  }
334  decimalInfos[shardIdx] = (18, 4);
335  return 8;
336  default:
337  return 8; // Default to 8 bytes (Murmur hash)
338  }
339  }
340 
341  private static int GetColumnBufferSizeFromKineticaColumn(
343  int shardIdx,
344  Dictionary<int, (int Precision, int Scale)> decimalInfos,
345  Dictionary<int, int> charLengths)
346  {
347  var props = col.getProperties();
348 
349  if (props.Contains("int8")) return 1;
350  if (props.Contains("int16")) return 2;
351  if (props.Contains("ipv4") || props.Contains("time")) return 4;
352  if (props.Contains("char1")) { charLengths[shardIdx] = 1; return 1; }
353  if (props.Contains("char2")) { charLengths[shardIdx] = 2; return 2; }
354  if (props.Contains("char4")) { charLengths[shardIdx] = 4; return 4; }
355  if (props.Contains("char8")) { charLengths[shardIdx] = 8; return 8; }
356  if (props.Contains("char16")) { charLengths[shardIdx] = 16; return 16; }
357  if (props.Contains("char32")) { charLengths[shardIdx] = 32; return 32; }
358  if (props.Contains("char64")) { charLengths[shardIdx] = 64; return 64; }
359  if (props.Contains("char128")) { charLengths[shardIdx] = 128; return 128; }
360  if (props.Contains("char256")) { charLengths[shardIdx] = 256; return 256; }
361 
362  foreach (var prop in props)
363  {
364  var match = Regex.Match(prop, @"decimal\s*\(\s*(\d+)\s*,\s*(\d+)\s*\)", RegexOptions.IgnoreCase);
365  if (match.Success)
366  {
367  int precision = int.Parse(match.Groups[1].Value);
368  int scale = int.Parse(match.Groups[2].Value);
369  decimalInfos[shardIdx] = (precision, scale);
370  return precision > 18 ? 12 : 8;
371  }
372  }
373 
374  // Default sizes based on base type
375  return col.getType() switch
376  {
383  _ => 8
384  };
385  }
386 
387  private static ColumnType DetermineColumnType(kinetica.KineticaType.Column col)
388  {
389  var props = col.getProperties();
390 
391  if (props.Contains("boolean")) return ColumnType.Boolean;
392  if (props.Contains("int8")) return ColumnType.Int8;
393  if (props.Contains("int16")) return ColumnType.Int16;
394  if (props.Contains("timestamp")) return ColumnType.Timestamp;
395  if (props.Contains("date")) return ColumnType.Date;
396  if (props.Contains("datetime")) return ColumnType.DateTime;
397  if (props.Contains("time")) return ColumnType.Time;
398  if (props.Contains("decimal")) return ColumnType.Decimal;
399  if (props.Contains("ipv4")) return ColumnType.Ipv4;
400  if (props.Contains("uuid")) return ColumnType.Uuid;
401  if (props.Contains("char1")) return ColumnType.Char1;
402  if (props.Contains("char2")) return ColumnType.Char2;
403  if (props.Contains("char4")) return ColumnType.Char4;
404  if (props.Contains("char8")) return ColumnType.Char8;
405  if (props.Contains("char16")) return ColumnType.Char16;
406  if (props.Contains("char32")) return ColumnType.Char32;
407  if (props.Contains("char64")) return ColumnType.Char64;
408  if (props.Contains("char128")) return ColumnType.Char128;
409  if (props.Contains("char256")) return ColumnType.Char256;
410 
411  return col.getType() switch
412  {
413  kinetica.KineticaType.Column.ColumnType.INT => ColumnType.Integer,
414  kinetica.KineticaType.Column.ColumnType.LONG => ColumnType.Long,
415  kinetica.KineticaType.Column.ColumnType.FLOAT => ColumnType.Float,
416  kinetica.KineticaType.Column.ColumnType.DOUBLE => ColumnType.Double,
417  kinetica.KineticaType.Column.ColumnType.STRING => ColumnType.String,
418  _ => ColumnType.String
419  };
420  }
421 
422  #endregion
423  }
RecordKeyBuilder(kinetica.KineticaType ktype, bool isPrimaryKey=false)
Creates a RecordKeyBuilder from a KineticaType (for backwards compatibility).
void AddUuid(string value)
Adds a UUID string to the key.
Definition: RecordKey.cs:291
bool TryGetLong(out long value)
Tries to get the value as a long.
const string PRIMARY_KEY
This property indicates that this column will be part of (or the entire) primary key.
A binary key used for shard routing.
Definition: RecordKey.cs:16
int Count
Gets the number of shard key values.
Column? GetColumn(int index)
Gets a column by index.
Definition: Type.cs:312
RecordKeyBuilder(Type type)
Creates a RecordKeyBuilder from a Type definition.
bool HasKey
Returns true if this builder has shard key columns.
void AddTime(string value)
Adds a time string (HH:MM:SS.mmm) to the key.
Definition: RecordKey.cs:237
void AddIpv4(string value)
Adds an IPv4 address string (dotted-quad) to the key.
Definition: RecordKey.cs:258
void AddDecimal(string value, int precision, int scale)
Adds a decimal string to the key.
Definition: RecordKey.cs:277
Column properties used for Kinetica types.
void AddDateTime(string value)
Adds a datetime string (YYYY-MM-DD HH:MM:SS.mmm) to the key.
Definition: RecordKey.cs:206
IReadOnlyList< int > ShardKeyIndices
Gets the shard key column indices.
Definition: Type.cs:289
float AsFloat()
Gets the value as a 32-bit float.
void AddCharN(string value, int length)
Adds a fixed-length char to the key.
Definition: RecordKey.cs:159
void AddString(string value)
Adds a string to the key (via MurmurHash3).
Definition: RecordKey.cs:146
if(args.Length > 0)
Definition: Program.cs:5
IList< Column > getColumns()
Collection of shard key column names and values.
double AsDouble()
Gets the value as a 64-bit double.
Builds or creates RecordKey objects based on a given record.
const string SHARD_KEY
This property indicates that this column will be part of (or the entire) shard key.
Immutable collection of metadata about a Kinetica type.
Definition: Type.cs:36
void AddLong(long value)
Adds a 64-bit integer to the key (little-endian).
Definition: RecordKey.cs:116
void AddDate(string value)
Adds a date string (YYYY-MM-DD) to the key.
Definition: RecordKey.cs:184
A typed value for shard key computation.
RecordKey? Build(ShardKeyValues values)
Builds a RecordKey from the given shard key values.
void AddDouble(double value)
Adds a 64-bit double to the key.
Definition: RecordKey.cs:139
void AddInt(int value)
Adds a 32-bit integer to the key (little-endian).
Definition: RecordKey.cs:105
void AddFloat(float value)
Adds a 32-bit float to the key.
Definition: RecordKey.cs:131
bool TryGetString(out string? value)
Tries to get the value as a string.
bool IsNull
Returns true if this value is null.
int KeyColumnCount
Gets the number of shard key columns.
bool TryGetInt(out int value)
Tries to get the value as an integer.