Kinetica   C#   API  Version 7.2.3.1
RecordKey.cs
Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Numerics;
4 using System.Text.RegularExpressions;
5 
6 
7 namespace kinetica.Utils;
8 
14  internal sealed class RecordKey
15  {
19  private static readonly Regex DATE_REGEX = new Regex("\\A(\\d{4})-(\\d{2})-(\\d{2})$");
20 
24  private static readonly Regex DATETIME_REGEX = new Regex("\\A(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})(?<time>\\s+(?<hour>\\d{1,2}):(?<min>\\d{2}):(?<sec>\\d{2})(?:\\.(?<ms>\\d{1,6}))?)?$");
25 
26 
30  private static readonly Regex IPV4_REGEX = new Regex("\\A(?<a>\\d{1,3})\\.(?<b>\\d{1,3})\\.(?<c>\\d{1,3})\\.(?<d>\\d{1,3})$");
31 
35  private static readonly Regex TIME_REGEX = new Regex("\\A(?<hour>\\d{1,2}):(?<minute>\\d{2}):(?<seconds>\\d{2})(\\.(?<milliseconds>\\d{1,3}))?$");
36 
40  private static readonly DateTime EPOCH_DATE = new DateTime(1970, 1, 1);
41 
45  private static readonly int MIN_SUPPORTED_YEAR = 1000;
46 
50  private static readonly int MAX_SUPPORTED_YEAR = 2900;
51 
55  private static readonly int YEAR_1900 = 1900;
56 
60  private static readonly TimeZoneInfo UTC = TimeZoneInfo.Utc;
61 
62  private readonly byte[] buffer;
63  private readonly int buffer_size;
64  private int current_size;
65  private int hash_code;
66  private bool is_valid;
67  private long routingHash;
68 
74  public RecordKey(int size)
75  {
76  if (size < 1)
77  throw new KineticaException("Buffer size must be greater than or equal to 1. "
78  + "Size given: " + size);
79  buffer_size = size;
80  current_size = 0;
81  buffer = new byte[size];
82  this.is_valid = true;
83  }
84 
89  public bool isValid()
90  {
91  return this.is_valid;
92  }
93 
98  public int hashCode()
99  {
100  return this.hash_code;
101  }
102 
103 
104 
111  private bool isBufferFull(bool throw_if_full = true)
112  {
113  if (this.current_size == this.buffer_size)
114  {
115  if (throw_if_full)
116  throw new KineticaException("The buffer is already full!");
117  return true; // yes, the buffer is full, and we haven't thrown
118  }
119  return false; // buffer is NOT full
120  } // end isBufferFull
121 
133  private bool willBufferOverflow(int n, bool throw_if_overflow = true)
134  {
135  // Note: We're not checking for a negative value for n here
136  if ((this.current_size + n) > this.buffer_size)
137  {
138  if (throw_if_overflow)
139  throw new KineticaException($"The buffer (of size {buffer_size}) does not have sufficient room in it to put {n} more byte(s) (current size is {this.current_size}).");
140  return true; // yes, the buffer WILL overflow, but we haven't thrown
141  }
142  return false; // buffer will NOT overflow
143  } // end willBufferOverflow
144 
145 
152  private void add(byte b)
153  {
154  // Add the byte to the buffer and increment the size
155  buffer.SetValue(b, current_size++);
156  } // end add()
157 
158 
159 
164  public void addInt(int? value)
165  {
166  // Check if the given number of characters will fit in the buffer
167  this.willBufferOverflow(4); // int is four bytes long
168 
169  // Handle nulls
170  if (value == null)
171  {
172  // Add four zero bytes for the null value
173  this.add((byte)0); // 1st 0
174  this.add((byte)0); // 2nd 0
175  this.add((byte)0); // 3rd 0
176  this.add((byte)0); // 4th 0
177  return;
178  }
179 
180  // Put the integer into the array, but first convert to bytes
181  byte[] int_bytes = BitConverter.GetBytes((int)value);
182 
183  // Add the four bytes
184  foreach (byte b in int_bytes)
185  this.add(b);
186  } // end addInt
187 
188 
193  public void addInt8(int? value)
194  {
195  // Check if the given number of characters will fit in the buffer
196  this.willBufferOverflow(1); // int8 is one byte long
197 
198  // Handle nulls
199  if (value == null)
200  {
201  // Add one zero byte for the null value
202  this.add((byte)0);
203  return;
204  }
205 
206  // Put the integer into the array, but first convert to byte
207  this.add((byte)value);
208  } // end addInt8
209 
210 
215  public void addInt16(int? value)
216  {
217  // Check if the given number of characters will fit in the buffer
218  this.willBufferOverflow(2); // int16 is two bytes long
219 
220  // Handle nulls
221  if (value == null)
222  {
223  // Add two zero bytes for the null value
224  this.add((byte)0); // 1st 0
225  this.add((byte)0); // 2nd 0
226  return;
227  }
228 
229  // Put the short into the array, but first convert to bytes
230  byte[] short_bytes = BitConverter.GetBytes((short)value);
231 
232  // Add the two bytes
233  foreach (byte b in short_bytes)
234  this.add(b);
235  } // end addInt16
236 
237 
238 
243  public void addLong(long? value)
244  {
245  // Check if the given number of characters will fit in the buffer
246  this.willBufferOverflow(8); // int is eight bytes long
247 
248  // Handle nulls
249  if (value == null)
250  {
251  // Add four zero bytes for the null value
252  this.add((byte)0); // 1st 0
253  this.add((byte)0); // 2nd 0
254  this.add((byte)0); // 3rd 0
255  this.add((byte)0); // 4th 0
256  this.add((byte)0); // 5th 0
257  this.add((byte)0); // 6th 0
258  this.add((byte)0); // 7th 0
259  this.add((byte)0); // 8th 0
260  return;
261  }
262 
263  // Put the long into the array, but first convert to bytes
264  byte[] long_bytes = BitConverter.GetBytes((long)value);
265 
266  // Add the eight bytes
267  foreach (byte b in long_bytes)
268  this.add(b);
269  } // end addLong
270 
271 
276  public void addFloat(float? value)
277  {
278  // Check if the given number of characters will fit in the buffer
279  this.willBufferOverflow(4); // int is four bytes long
280 
281  // Handle nulls
282  if (value == null)
283  {
284  // Add four zero bytes for the null value
285  this.add((byte)0.0f); // 1st 0
286  this.add((byte)0.0f); // 2nd 0
287  this.add((byte)0.0f); // 3rd 0
288  this.add((byte)0.0f); // 4th 0
289  return;
290  }
291 
292  // Put the integer into the array, but first convert to bytes
293  byte[] float_bytes = BitConverter.GetBytes((float)value);
294 
295  // Add the four bytes
296  foreach (byte b in float_bytes)
297  this.add(b);
298  } // end addFloat
299 
300 
301 
306  public void addDouble(double? value)
307  {
308  // Check if the given number of characters will fit in the buffer
309  this.willBufferOverflow(8); // int is eight bytes long
310 
311  // Handle nulls
312  if (value == null)
313  {
314  // Add four zero bytes for the null value
315  this.add((byte)0.0); // 1st 0
316  this.add((byte)0.0); // 2nd 0
317  this.add((byte)0.0); // 3rd 0
318  this.add((byte)0.0); // 4th 0
319  this.add((byte)0.0); // 5th 0
320  this.add((byte)0.0); // 6th 0
321  this.add((byte)0.0); // 7th 0
322  this.add((byte)0.0); // 8th 0
323  return;
324  }
325 
326  // Put the integer into the array, but first convert to bytes
327  byte[] double_bytes = BitConverter.GetBytes((double)value);
328 
329  // Add the eight bytes
330  foreach (byte b in double_bytes)
331  this.add(b);
332  } // end addDouble
333 
334 
335 
341  public void addString(string value)
342  {
343  // Handle nulls
344  if (value == null)
345  {
346  this.addLong(0L);
347  return;
348  }
349 
350  // Hash the value
351  MurMurHash3.LongPair murmur = new MurMurHash3.LongPair();
352  System.Text.Encoding encoding = new System.Text.UTF8Encoding();
353  byte[] input = encoding.GetBytes(value);
354  MurMurHash3.murmurhash3_x64_128(input, 0, (uint)input.Length, 10, out murmur);
355 
356  // Add the hashed value to the buffer
357  this.addLong(murmur.val1);
358  } // end addString
359 
360 
361 
370  public void addCharN(string value, int N)
371  {
372  // Check if the given number of characters will fit in the buffer
373  this.willBufferOverflow(N);
375  //if ( ( this.current_size + N ) > buffer_size )
376  // throw new KineticaException( $"The given {N} character(s) will not fit in the buffer (of size {buffer_size}) which has {this.current_size} bytes in it already." );
377 
378  // Handle nulls
379  if (value == null)
380  {
381  for (int i = 0; i < N; ++i)
382  {
383  this.add((byte)0);
384  }
385  return;
386  }
387 
388  // Encode the string into bytes (using the UTF-8 encoding)
389  byte[] bytes = System.Text.Encoding.UTF8.GetBytes(value);
390  int byte_count = bytes.GetLength(0);
391 
392  // Truncate longer strings to the given length
393  if (byte_count > N)
394  byte_count = N;
395 
396  // Put the characters in the byte buffer in the little endian
397  // order (which means it will be right to left)
398  // ----------------------------------------------------------
399  // First, pad with any zeroes "at the end"
400  for (int i = N; i > byte_count; --i)
401  {
402  this.add((byte)0);
403  }
404 
405  // Then, put all the characters (in reverse order)
406  for (int i = (byte_count - 1); i >= 0; --i)
407  {
408  this.add(bytes[i]);
409  }
410  } // end addCharN()
411 
412 
419  public void addDate(string value)
420  {
421  // Check and throw if the buffer is already full
422  this.isBufferFull(true);
423 
424  // Handle nulls
425  if (value == null)
426  {
427  this.addInt(0);
428  return;
429  }
430 
431  // Check that the given value matches the YYYY-MM-DD pattern
432  Match match = DATE_REGEX.Match(value);
433  if (!match.Success)
434  {
435  // No match, so the key is invalid
436  this.is_valid = false;
437  this.addInt(0);
438  return;
439  }
440 
441  // We'll need to parse the string into year, month, and day
442  int year, month, day;
443  DateTime date;
444  System.Globalization.GregorianCalendar calendar = new System.Globalization.GregorianCalendar();
445 
446  // Parse the string value
447  try
448  {
449  year = int.Parse(match.Groups[1].ToString());
450  month = int.Parse(match.Groups[2].ToString());
451  day = int.Parse(match.Groups[3].ToString());
452  date = new DateTime(year, month, day, calendar);
453  }
454  catch (Exception)
455  {
456  // Upon any error, set this key to be invalid
457  this.addInt(0);
458  this.is_valid = false;
459  return;
460  }
461 
462  // Kinetica does not support years outside the range [1000, 2900]
463  if ((year < MIN_SUPPORTED_YEAR) || (year > MAX_SUPPORTED_YEAR))
464  {
465  this.addInt(0);
466  this.is_valid = false;
467  return;
468  }
469 
470  int fixed_day_of_week = ((int)calendar.GetDayOfWeek(date) + 1);
471 
472  // Deduce the integer representing the date
473  int date_integer = (((year - YEAR_1900) << 21)
474  | (month << 17)
475  | (day << 12)
476  | (calendar.GetDayOfYear(date) << 3)
477  | fixed_day_of_week);
478  this.addInt(date_integer);
479  } // end addDate()
480 
481 
488  public void addDateTime(string value)
489  {
490  // Check and throw if the buffer is already full
491  this.isBufferFull(true);
492 
493  // Handle nulls
494  if (value == null)
495  {
496  this.addLong(0);
497  return;
498  }
499 
500  // Check that the given value matches the YYYY-MM-DD HH:MM:SS.mmm pattern
501  Match match = DATETIME_REGEX.Match(value);
502  if (!match.Success)
503  {
504  // No match, so the key is invalid
505  this.is_valid = false;
506  this.addLong(0);
507  return;
508  }
509 
510  // We'll need to parse the string into year, month, day, hour,
511  // minute, second, and millisecond
512  int year, month, day;
513  int hour = 0;
514  int minute = 0;
515  int second = 0;
516  int msecond = 0;
517  DateTime date;
518  System.Globalization.GregorianCalendar calendar = new System.Globalization.GregorianCalendar();
519 
520  // Parse the string value
521  try
522  {
523  year = int.Parse(match.Groups["year"].Value);
524  month = int.Parse(match.Groups["month"].Value);
525  day = int.Parse(match.Groups["day"].Value);
526 
527  // Handle the optional time part
528  Group time_group = match.Groups["time"];
529  if (time_group.Success)
530  {
531  hour = int.Parse(match.Groups["hour"].Value);
532  minute = int.Parse(match.Groups["min"].Value);
533  second = int.Parse(match.Groups["sec"].Value);
534 
535  // Handle the further optional milliseconds
536  Group ms_group = match.Groups["ms"];
537  if (ms_group.Success)
538  {
539  msecond = int.Parse(match.Groups["ms"].Value);
540  // Need to have the milliseconds be milliseconds (three digits)
541  switch (ms_group.Value.Length)
542  {
543  case 1:
544  msecond *= 100; break;
545  case 2:
546  msecond *= 10; break;
547  // No need for case 3
548  case 4:
549  msecond /= 10; break;
550  case 5:
551  msecond /= 100; break;
552  case 6:
553  msecond /= 1000; break;
554  }
555  }
556  } // end parsing the time component
557 
558  // Now put it all together
559  date = new DateTime(year, month, day, hour, minute, second, msecond, calendar);
560  }
561  catch (Exception)
562  {
563  // Upon any error, set this key to be invalid
564  this.addLong(0);
565  this.is_valid = false;
566  return;
567  }
568 
569  // Kinetica does not support years outside the range [1000, 2900]
570  if ((year < MIN_SUPPORTED_YEAR) || (year > MAX_SUPPORTED_YEAR))
571  {
572  this.addLong(0);
573  this.is_valid = false;
574  return;
575  }
576 
577  int fixed_day_of_week = ((int)calendar.GetDayOfWeek(date) + 1);
578 
579  // Deduce the integer representing the date
580  long datetime_long = (long)((((long)(year - YEAR_1900)) << 53)
581  | (((long)month) << 49)
582  | (((long)day) << 44)
583  | (((long)hour) << 39)
584  | (((long)minute) << 33)
585  | (((long)second) << 27)
586  | (((long)msecond) << 17)
587  | (((long)calendar.GetDayOfYear(date)) << 8)
588  | (((long)fixed_day_of_week) << 5));
589  this.addLong(datetime_long);
590  } // end addDateTime()
591 
592 
601  public void addDecimal(string value, int precision, int scale)
602  {
603  // Check and throw if the buffer is already full
604  this.isBufferFull(true);
605 
606  // Determine byte size based on precision
607  int byteSize = (precision > 18) ? 12 : 8;
608 
609  // Handle nulls or empty strings
610  if (string.IsNullOrEmpty(value) || value == "null")
611  {
612  for (int i = 0; i < byteSize; i++)
613  this.add((byte)0);
614  return;
615  }
616 
617  try
618  {
619  // Parse the decimal value
620  if (!decimal.TryParse(value, System.Globalization.NumberStyles.Any,
621  System.Globalization.CultureInfo.InvariantCulture, out decimal parsedValue))
622  {
623  // Invalid format - write zeros and mark invalid
624  for (int i = 0; i < byteSize; i++)
625  this.add((byte)0);
626  this.is_valid = false;
627  return;
628  }
629 
630  // Calculate the scale multiplier
631  decimal multiplier = (decimal)Math.Pow(10, scale);
632 
633  // Scale the value and round to get unscaled integer
634  decimal scaledValue = Math.Round(parsedValue * multiplier);
635 
636  if (precision <= 18)
637  {
638  // 8-byte decimal: store as long
639  long longValue = (long)scaledValue;
640  this.addLong(longValue);
641  }
642  else
643  {
644  // 12-byte decimal: store as BigInteger in little-endian
645  this.willBufferOverflow(12);
646 
647  // Convert to BigInteger
648  BigInteger bigValue = new BigInteger(scaledValue);
649 
650  // Get little-endian bytes, pad or truncate to 12 bytes
651  byte[] bigBytes = bigValue.ToByteArray();
652  byte[] result = new byte[12];
653 
654  // Copy bytes, handling both positive and negative numbers
655  int copyLen = Math.Min(bigBytes.Length, 12);
656  Array.Copy(bigBytes, result, copyLen);
657 
658  // Sign-extend if negative and bytes are fewer than 12
659  if (bigValue < 0 && bigBytes.Length < 12)
660  {
661  for (int i = bigBytes.Length; i < 12; i++)
662  result[i] = 0xFF;
663  }
664 
665  // Add all 12 bytes
666  foreach (byte b in result)
667  this.add(b);
668  }
669  }
670  catch (Exception)
671  {
672  // Upon any error, set this key to be invalid
673  for (int i = 0; i < byteSize; i++)
674  this.add((byte)0);
675  this.is_valid = false;
676  }
677  } // end addDecimal(value, precision, scale)
678 
679 
686  public void addIPv4(string value)
687  {
688  // Check and throw if the buffer is already full
689  this.isBufferFull(true);
690 
691  // Handle nulls
692  if (value == null)
693  {
694  this.addInt(0);
695  return;
696  }
697 
698  // Check that the given value matches the XXX.XXX.XXX.XXX pattern
699  Match match = IPV4_REGEX.Match(value);
700  if (!match.Success)
701  {
702  // No match, so the key is invalid
703  this.is_valid = false;
704  this.addInt(0);
705  return;
706  }
707 
708  // We'll need to parse the string into four integers
709  int a, b, c, d;
710 
711  // Parse the string value
712  try
713  {
714  a = int.Parse(match.Groups["a"].Value);
715  b = int.Parse(match.Groups["b"].Value);
716  c = int.Parse(match.Groups["c"].Value);
717  d = int.Parse(match.Groups["d"].Value);
718  }
719  catch (Exception)
720  {
721  // Upon any error, set this key to be invalid
722  this.addInt(0);
723  this.is_valid = false;
724  return;
725  }
726 
727  // Each byte has to be within the range [0, 255] (the regex does
728  // not support negative numbers, so no worries about those)
729  if ((a > 255) || (b > 255) || (c > 255) || (d > 255))
730  {
731  this.addInt(0);
732  this.is_valid = false;
733  return;
734  }
735 
736  // Deduce the integer representing the date
737  int ipv4_integer = ((a << 24) | (b << 16) | (c << 8) | d);
738  this.addInt(ipv4_integer);
739  } // end addIPv4()
740 
741 
749  public void addTime(string value)
750  {
751  // Check and throw if the buffer is already full
752  this.isBufferFull(true);
753 
754  // Handle nulls
755  if (value == null)
756  {
757  this.addInt(0);
758  return;
759  }
760 
761  // Check that the given value matches the HH:MM:SS[.mmm] pattern
762  Match match = TIME_REGEX.Match(value);
763  if (!match.Success)
764  {
765  // No match, so the key is invalid
766  this.is_valid = false;
767  this.addInt(0);
768  return;
769  }
770 
771  // We'll need to parse the string into four integers
772  uint hour, minute, second, milliseconds;
773 
774  // Parse the string value
775  try
776  {
777  hour = uint.Parse(match.Groups["hour"].Value);
778  minute = uint.Parse(match.Groups["minute"].Value);
779  second = uint.Parse(match.Groups["seconds"].Value);
780  Group msec_group = match.Groups["milliseconds"];
781 
782  // Milliseconds are optional
783  milliseconds = 0;
784  if (msec_group.Success)
785  {
786  milliseconds = uint.Parse(msec_group.Value);
787 
788  // Handle single and double digits for milliseconds
789  switch (msec_group.Value.Length)
790  {
791  case 1:
792  milliseconds *= 100; break;
793  case 2:
794  milliseconds *= 10; break;
795  }
796  }
797  }
798  catch (Exception)
799  {
800  // Upon any error, set this key to be invalid
801  this.addInt(0);
802  this.is_valid = false;
803  return;
804  }
805 
806  // Validate the hour, minute, second values
807  if ((hour > 23) || (minute > 59) || (second > 59))
808  {
809  this.addInt(0);
810  this.is_valid = false;
811  return;
812  }
813 
814  // Deduce the integer representing the time
815  int time_integer = (int)((hour << 26) | (minute << 20) | (second << 14) | (milliseconds << 4));
816  this.addInt(time_integer);
817  } // end addTime()
818 
819 
824  public void addTimeStamp(long? value)
825  {
826  // Handle nulls
827  if (value == null)
828  {
829  this.addLong(0);
830  return;
831  }
832 
833  // Encode the timestamp the way the database server does it
834  DateTime time = EPOCH_DATE.AddMilliseconds((double)value);
835  long fixed_day_of_week = ((long)time.DayOfWeek + 1);
836 
837  long timestamp = (long)((((long)(time.Year - YEAR_1900)) << 53)
838  | (((long)(time.Month)) << 49)
839  | (((long)time.Day) << 44)
840  | (((long)time.Hour) << 39)
841  | (((long)time.Minute) << 33)
842  | (((long)time.Second) << 27)
843  | (((long)time.Millisecond) << 17)
844  | (((long)time.DayOfYear) << 8)
845  | (fixed_day_of_week << 5));
846  this.addLong(timestamp);
847  } // end addTimeStamp()
848 
849 
850 
857  public void computeHashes()
858  {
859  // Check all the values for the key have been added
860  if (this.current_size != this.buffer_size)
861  throw new KineticaException("The RecordKey buffer is not full; check that all the relevant values have been added.");
862 
863  // Hash the value
864  MurMurHash3.LongPair murmur = new MurMurHash3.LongPair();
865  MurMurHash3.murmurhash3_x64_128(this.buffer, 0, (uint)this.buffer_size, 10, out murmur);
866 
867  // Save the hash value
868  this.routingHash = murmur.val1;
869  this.hash_code = (int)(this.routingHash ^ ((this.routingHash >> 32) & 0x0000ffffL));
870  } // end computeHashes
871 
872 
873 
880  public int route(IList<int> routingTable)
881  {
882  // Return 1 less than the value of the nth element of routingTable where
883  // n == (record key hash) % (number of elements in routingTable)
884  // (because the 1st worker rank is the 0th element in the worker list)
885  return (routingTable[Math.Abs((int)(this.routingHash % routingTable.Count))] - 1);
886  } // end route
887 
888  } // end class RecordKey
void addCharN(string value, int N)
Appends a charN value to the buffer.
Definition: RecordKey.cs:370
void addInt8(int? value)
Add an 8-bit integer to the buffer.
Definition: RecordKey.cs:193
void addInt16(int? value)
Add a short (two bytes) to the buffer.
Definition: RecordKey.cs:215
void addDecimal(string value, int precision, int scale)
Adds a decimal value to the buffer with specified precision and scale.
Definition: RecordKey.cs:601
int route(IList< int > routingTable)
Given a routing table consisting of worker rank indices, choose a worker rank based on the hash of th...
Definition: RecordKey.cs:880
void addDate(string value)
Adds a string to the buffer that has the 'date' property.
Definition: RecordKey.cs:419
void addInt(int? value)
Add an integer to the buffer.
Definition: RecordKey.cs:164
A binary key used for shard routing.
Definition: RecordKey.cs:16
bool isValid()
Returns whether the key is valid or not.
Definition: RecordKey.cs:89
RecordKey(int size)
Allocate the buffer for the record key with the given size.
Definition: RecordKey.cs:74
void computeHashes()
Compute the hash of the key in the buffer.
Definition: RecordKey.cs:857
void addTime(string value)
Adds a string to the buffer that has the 'time' property.
Definition: RecordKey.cs:749
void addString(string value)
Add a string to the buffer.
Definition: RecordKey.cs:341
void addIPv4(string value)
Adds a string to the buffer that has the 'ipv4' property.
Definition: RecordKey.cs:686
void addTimeStamp(long? value)
Adds a long to the buffer that has the 'timestamp' property.
Definition: RecordKey.cs:824
void addLong(long? value)
Add a long to the buffer.
Definition: RecordKey.cs:243
void addDouble(double? value)
Add a double to the buffer.
Definition: RecordKey.cs:306
void addDateTime(string value)
Adds a string to the buffer that has the 'datetime' property.
Definition: RecordKey.cs:488
void addFloat(float? value)
Add a float to the buffer.
Definition: RecordKey.cs:276
DateTime in YYYY-MM-DD HH:MM:SS.mmm format
static void murmurhash3_x64_128(byte[] key, uint offset, uint len, int seed, out LongPair output)
Returns the MurmurHash3_x64_128 hash, placing the result in output
Definition: MurMurHash3.cs:58
128 bits of state
Definition: MurMurHash3.cs:25
int hashCode()
Returns key's hash code.
Definition: RecordKey.cs:98