Kinetica C# API  Version 7.0.19.0
 All Classes Namespaces Files Functions Variables Enumerations Enumerator Properties Pages
RecordKeyBuilder.cs
Go to the documentation of this file.
1 using System.Collections.Generic;
2 
3 
4 namespace kinetica.Utils
5 {
6 
11  internal sealed class RecordKeyBuilder<T>
12  {
16  private enum ColumnType
17  {
18  CHAR1,
19  CHAR2,
20  CHAR4,
21  CHAR8,
22  CHAR16,
23  CHAR32,
24  CHAR64,
25  CHAR128,
26  CHAR256,
27  DATE,
28  DATETIME,
29  DECIMAL,
30  DOUBLE,
31  FLOAT,
32  INT,
33  INT8,
34  INT16,
35  IPV4,
36  LONG,
37  STRING,
38  TIME,
39  TIMESTAMP
40  } // end enum ColumnType
41 
42 
43  // Class members
44  private KineticaType ktype;
45  private IList<int> routing_column_indices;
46  private IList<ColumnType> column_types;
47  private int buffer_size;
48 
49  public RecordKeyBuilder(bool is_primary_key, KineticaType ktype)
50  {
51  this.ktype = ktype;
52 
53  this.buffer_size = 0;
54  routing_column_indices = new List<int>();
55  column_types = new List<ColumnType>();
56 
57  // We need to check if the type has all of the following: x, y, timestamp, track ID
58  // (this will tell us if it's a track type table, and if so, the track ID
59  // column would be a routing column)
60  bool has_timestamp = false;
61  bool has_x = false;
62  bool has_y = false;
63  int track_id_column_idx = -1; // not found yet
64 
65  // Add indices of any primary or shard key (based on is_primary_key)
66  // to the list of routing columns
67  IList<KineticaType.Column> columns = ktype.getColumns();
68  for (int i = 0; i < columns.Count; ++i)
69  {
70  // Get the column
71  KineticaType.Column column = columns[i];
72 
73  // Check if it is one of: x, y, timestamp, track ID
74  switch (column.getName())
75  {
76  case "TRACKID":
77  track_id_column_idx = i;
78  break;
79 
80  case "TIMESTAMP":
81  has_timestamp = true;
82  break;
83 
84  case "x":
85  has_x = true;
86  break;
87 
88  case "y":
89  has_y = true;
90  break;
91  } // end switch on column name
92 
93  // Check if this column has been declared as a primary/shard key
94  // And if so, and if appropriate, add it to the routing key column list
95  if (is_primary_key && column.getProperties().Contains(ColumnProperty.PRIMARY_KEY))
96  {
97  routing_column_indices.Add(i);
98  }
99  else if (!is_primary_key && column.getProperties().Contains(ColumnProperty.SHARD_KEY))
100  {
101  routing_column_indices.Add(i);
102  }
103  } // end for loop
104 
105  // Check if this is a track-type table; if so, add the track ID column's index to the list
106  if (!is_primary_key
107  && has_timestamp && has_x && has_y && (track_id_column_idx != -1))
108  {
109  if (routing_column_indices.Count == 0)
110  {
111  routing_column_indices.Add(track_id_column_idx);
112  }
113  else if ((routing_column_indices.Count != 1)
114  || (routing_column_indices[0] != track_id_column_idx))
115  {
116  // Track type tables can't have any other routing key
117  throw new KineticaException("Cannot have a shard key other than 'TRACKID' for track tables.");
118  }
119  } // end if a track type table
120 
121 
122  // For each index of routing columns, save the column type, and increase
123  // the buffer size appropriately
124  foreach (int i in routing_column_indices)
125  {
126  // Get the column information
127  KineticaType.Column column = columns[i];
128 
129  switch (column.getType())
130  {
131  // Float and double are the simplest
132  case KineticaType.Column.ColumnType.FLOAT:
133  {
134  column_types.Add(ColumnType.FLOAT);
135  this.buffer_size += 4;
136  break;
137  }
138  case KineticaType.Column.ColumnType.DOUBLE:
139  {
140  column_types.Add(ColumnType.DOUBLE);
141  this.buffer_size += 8;
142  break;
143  }
144 
145  case KineticaType.Column.ColumnType.INT:
146  {
147  // Integer has byte, short and int
148  if (column.getProperties().Contains(ColumnProperty.INT8))
149  { // byte
150  column_types.Add(ColumnType.INT8);
151  this.buffer_size += 1;
152  }
153  else if (column.getProperties().Contains(ColumnProperty.INT16))
154  { // short
155  column_types.Add(ColumnType.INT16);
156  this.buffer_size += 2;
157  }
158  else // regular 4-byte integer
159  {
160  column_types.Add(ColumnType.INT);
161  this.buffer_size += 4;
162  }
163  break;
164  } // end case integer
165 
166  case KineticaType.Column.ColumnType.LONG:
167  {
168  // Long has the regular long and timestamp
169  if (column.getProperties().Contains(ColumnProperty.TIMESTAMP))
170  { // it's a timestamp
171  column_types.Add(ColumnType.TIMESTAMP);
172  }
173  else // regular long
174  {
175  column_types.Add(ColumnType.LONG);
176  }
177  this.buffer_size += 8;
178  break;
179  } // end case long
180 
181  case KineticaType.Column.ColumnType.STRING:
182  {
183  if (column.getProperties().Contains(ColumnProperty.CHAR1))
184  {
185  column_types.Add(ColumnType.CHAR1);
186  this.buffer_size += 1;
187  }
188  else if (column.getProperties().Contains(ColumnProperty.CHAR2))
189  {
190  column_types.Add(ColumnType.CHAR2);
191  this.buffer_size += 2;
192  }
193  else if (column.getProperties().Contains(ColumnProperty.CHAR4))
194  {
195  column_types.Add(ColumnType.CHAR4);
196  this.buffer_size += 4;
197  }
198  else if (column.getProperties().Contains(ColumnProperty.CHAR8))
199  {
200  column_types.Add(ColumnType.CHAR8);
201  this.buffer_size += 8;
202  }
203  else if (column.getProperties().Contains(ColumnProperty.CHAR16))
204  {
205  column_types.Add(ColumnType.CHAR16);
206  this.buffer_size += 16;
207  }
208  else if (column.getProperties().Contains(ColumnProperty.CHAR32))
209  {
210  column_types.Add(ColumnType.CHAR32);
211  this.buffer_size += 32;
212  }
213  else if (column.getProperties().Contains(ColumnProperty.CHAR64))
214  {
215  column_types.Add(ColumnType.CHAR64);
216  this.buffer_size += 64;
217  }
218  else if (column.getProperties().Contains(ColumnProperty.CHAR128))
219  {
220  column_types.Add(ColumnType.CHAR128);
221  this.buffer_size += 128;
222  }
223  else if (column.getProperties().Contains(ColumnProperty.CHAR256))
224  {
225  column_types.Add(ColumnType.CHAR256);
226  this.buffer_size += 256;
227  }
228  else if (column.getProperties().Contains(ColumnProperty.DATE))
229  {
230  column_types.Add(ColumnType.DATE);
231  this.buffer_size += 4;
232  }
233  else if (column.getProperties().Contains(ColumnProperty.DATETIME))
234  {
235  column_types.Add(ColumnType.DATETIME);
236  this.buffer_size += 8;
237  }
238  else if (column.getProperties().Contains(ColumnProperty.DECIMAL))
239  {
240  column_types.Add(ColumnType.DECIMAL);
241  this.buffer_size += 8;
242  }
243  else if (column.getProperties().Contains(ColumnProperty.IPV4))
244  {
245  column_types.Add(ColumnType.IPV4);
246  this.buffer_size += 4;
247  }
248  else if (column.getProperties().Contains(ColumnProperty.TIME))
249  {
250  column_types.Add(ColumnType.TIME);
251  this.buffer_size += 4;
252  }
253  else // regular string
254  {
255  column_types.Add(ColumnType.STRING);
256  this.buffer_size += 8;
257  }
258  break;
259  } // end case string
260 
261  // Other types are not allowed for routing columns
262  case KineticaType.Column.ColumnType.BYTES:
263  case KineticaType.Column.ColumnType.DEFAULT:
264  throw new KineticaException($"Cannot use column '{column.getName()}' as a key.");
265  } // end switch on the column's primitive data type
266  } // end foreach
267  } // end constructor RecordKeyBuilder
268 
269 
277  public RecordKey build(T record)
278  {
279  // Can't build a key if the buffer size is zero!
280  if (this.buffer_size == 0)
281  return null;
282 
283  // Create the empty key
284  RecordKey key = new RecordKey(this.buffer_size);
285 
286  // Add each routing column's value to the key
287  for (int i = 0; i < this.routing_column_indices.Count; ++i)
288  {
289  // Get the column (with type and name)
290  KineticaType.Column column = this.ktype.getColumns()[this.routing_column_indices[i]];
291 
292  // Get the value out of the record using the column's name and reflection
293  var value = record.GetType().GetProperty(column.getName()).GetValue(record, null);
294 
295  switch (this.column_types[i])
296  {
297  case ColumnType.CHAR1:
298  key.addCharN((string)value, 1);
299  break;
300 
301  case ColumnType.CHAR2:
302  key.addCharN((string)value, 2);
303  break;
304 
305  case ColumnType.CHAR4:
306  key.addCharN((string)value, 4);
307  break;
308 
309  case ColumnType.CHAR8:
310  key.addCharN((string)value, 8);
311  break;
312 
313  case ColumnType.CHAR16:
314  key.addCharN((string)value, 16);
315  break;
316 
317  case ColumnType.CHAR32:
318  key.addCharN((string)value, 32);
319  break;
320 
321  case ColumnType.CHAR64:
322  key.addCharN((string)value, 64);
323  break;
324 
325  case ColumnType.CHAR128:
326  key.addCharN((string)value, 128);
327  break;
328 
329  case ColumnType.CHAR256:
330  key.addCharN((string)value, 256);
331  break;
332 
333  case ColumnType.DATE:
334  key.addDate((string)value);
335  break;
336 
337  case ColumnType.DATETIME:
338  key.addDateTime((string)value);
339  break;
340 
341  case ColumnType.DECIMAL:
342  key.addDecimal((string)value);
343  break;
344 
345  case ColumnType.DOUBLE:
346  key.addDouble((double?)value);
347  break;
348 
349  case ColumnType.FLOAT:
350  key.addFloat((float?)value);
351  break;
352 
353  case ColumnType.INT:
354  key.addInt((int?)value);
355  break;
356 
357  case ColumnType.INT8:
358  key.addInt8((int?)value);
359  break;
360 
361  case ColumnType.INT16:
362  key.addInt16((int?)value);
363  break;
364 
365  case ColumnType.IPV4:
366  key.addIPv4((string)value);
367  break;
368 
369  case ColumnType.LONG:
370  key.addLong((long?)value);
371  break;
372 
373  case ColumnType.STRING:
374  key.addString((string)value);
375  break;
376 
377  case ColumnType.TIME:
378  key.addTime((string)value);
379  break;
380 
381  case ColumnType.TIMESTAMP:
382  key.addTimeStamp((long?)value);
383  break;
384  } // end switch
385  } // end for loop
386 
387  // Compute the hash for the key and return it
388  key.computHashes();
389  return key;
390  } // end build()
391 
392 
393 
403  public string buildExpression(T record)
404  {
405  // Can't build a key if the buffer size is zero!
406  if (this.buffer_size == 0)
407  return null;
408 
409  // Create the empty expression
410  System.Text.StringBuilder expression = new System.Text.StringBuilder( "(" );
411 
412  // Add each routing column's value to the key
413  for (int i = 0; i < this.routing_column_indices.Count; ++i)
414  {
415  if ( i > 0 ) // need a conjunction
416  expression.Append( " and " );
417 
418  // Get the column (with type and name)
419  KineticaType.Column column = this.ktype.getColumns()[this.routing_column_indices[i]];
420  string column_name = column.getName();
421 
422  // Get the value out of the record using the column's name and reflection
423  var value = record.GetType().GetProperty( column_name ).GetValue( record, null );
424 
425  // Handle null values
426  if ( value == null )
427  {
428  expression.Append( "is_null(" );
429  expression.Append( column_name );
430  expression.Append( ")" );
431  continue; // nothing more to do for this column
432  }
433 
434  // Add this column to the expression
435  expression.Append( "(" );
436  expression.Append( column_name );
437  expression.Append( " = " );
438 
439  // Add the value to the expression
440  switch ( this.column_types[i] )
441  {
442  // Need to quote string values
443  case ColumnType.CHAR1:
444  case ColumnType.CHAR2:
445  case ColumnType.CHAR4:
446  case ColumnType.CHAR8:
447  case ColumnType.CHAR16:
448  case ColumnType.CHAR32:
449  case ColumnType.CHAR64:
450  case ColumnType.CHAR128:
451  case ColumnType.CHAR256:
452  case ColumnType.DATE:
453  case ColumnType.DATETIME:
454  case ColumnType.DECIMAL:
455  case ColumnType.IPV4:
456  case ColumnType.STRING:
457  case ColumnType.TIME:
458  expression.Append( "\"" );
459  expression.Append( value );
460  expression.Append( "\"" );
461  break;
462 
463  case ColumnType.DOUBLE:
464  case ColumnType.FLOAT:
465  case ColumnType.INT:
466  case ColumnType.INT8:
467  case ColumnType.INT16:
468  case ColumnType.LONG:
469  expression.Append( value );
470  break;
471  } // end switch
472 
473  // Closing parenthesis for the column
474  expression.Append( ")" );
475  } // end for loop
476 
477  // Final closing parenthesis
478  expression.Append( ")" );
479 
480  return expression.ToString();
481  } // end buildExpression()
482 
483 
484 
490  public bool hasKey()
491  {
492  // Does it have any routing columns?
493  return !(this.routing_column_indices.Count == 0);
494  }
495 
496 
502  public bool hasSameKey(RecordKeyBuilder<T> other)
503  {
504  return this.column_types.Equals(other.column_types);
505  }
506 
507  } // end class RecordKeyBuilder
508 
509 } // end namespace kinetica.Utils
const string CHAR1
This property provides optimized memory, disk and query performance for string columns.
const string DATETIME
Valid only for &#39;string&#39; columns.
const string INT16
This property provides optimized memory and query performance for int columns.
const string PRIMARY_KEY
This property indicates that this column will be part of (or the entire) primary key.
const string CHAR128
This property provides optimized memory, disk and query performance for string columns.
const string TIMESTAMP
Valid only for &#39;long&#39; columns.
const string CHAR16
This property provides optimized memory, disk and query performance for string columns.
const string CHAR64
This property provides optimized memory, disk and query performance for string columns.
const string CHAR4
This property provides optimized memory, disk and query performance for string columns.
const string CHAR2
This property provides optimized memory, disk and query performance for string columns.
const string DATE
Valid only for &#39;string&#39; columns.
const string CHAR8
This property provides optimized memory, disk and query performance for string columns.
const string DECIMAL
Valid only for &#39;string&#39; columns.
const string CHAR32
This property provides optimized memory, disk and query performance for string columns.
const string IPV4
This property provides optimized memory, disk and query performance for string columns representing I...
const string CHAR256
This property provides optimized memory, disk and query performance for string columns.
const string SHARD_KEY
This property indicates that this column will be part of (or the entire) shard key.
const string INT8
This property provides optimized memory and query performance for int columns.
const string TIME
Valid only for &#39;string&#39; columns.