Kinetica C# API  Version 6.2.0.1
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