Kinetica   C#   API  Version 7.2.3.1
Type.cs
Go to the documentation of this file.
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Text.Json;
6 using System.Threading.Tasks;
7 
8 namespace kinetica.Records;
9 
36  public sealed class Type
37  {
38  private readonly string _label;
39  private readonly IReadOnlyList<Column> _columns;
40  private readonly IReadOnlyDictionary<string, int> _columnIndices;
41  private readonly IReadOnlyList<int> _shardKeyIndices;
42  private readonly IReadOnlyList<int> _primaryKeyIndices;
43  private readonly string _schemaString;
44  private readonly string? _typeId;
45 
53  public Type(string label, IEnumerable<Column> columns)
54  {
55  _label = label ?? throw new ArgumentNullException(nameof(label));
56  var columnList = columns?.ToList() ?? throw new ArgumentNullException(nameof(columns));
57 
58  if (columnList.Count == 0)
59  throw new ArgumentException("At least one column is required", nameof(columns));
60 
61  _columns = columnList.AsReadOnly();
62 
63  // Build column index map
64  var columnIndices = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
65  var shardKeyIndices = new List<int>();
66  var primaryKeyIndices = new List<int>();
67 
68  for (int i = 0; i < columnList.Count; i++)
69  {
70  var col = columnList[i];
71  if (columnIndices.ContainsKey(col.Name))
72  throw new ArgumentException($"Duplicate column name: {col.Name}", nameof(columns));
73 
74  columnIndices[col.Name] = i;
75 
76  if (col.IsShardKey)
77  shardKeyIndices.Add(i);
78 
79  if (col.IsPrimaryKey)
80  primaryKeyIndices.Add(i);
81  }
82 
83  _columnIndices = columnIndices;
84  _shardKeyIndices = shardKeyIndices.AsReadOnly();
85  _primaryKeyIndices = primaryKeyIndices.AsReadOnly();
86 
87  // Build Avro schema
88  _schemaString = BuildAvroSchema(label, columnList);
89  _typeId = null;
90  }
91 
92  // Private constructor for FromTableResponse
93  private Type(
94  string label,
95  IReadOnlyList<Column> columns,
96  IReadOnlyDictionary<string, int> columnIndices,
97  IReadOnlyList<int> shardKeyIndices,
98  IReadOnlyList<int> primaryKeyIndices,
99  string schemaString,
100  string? typeId)
101  {
102  _label = label;
103  _columns = columns;
104  _columnIndices = columnIndices;
105  _shardKeyIndices = shardKeyIndices;
106  _primaryKeyIndices = primaryKeyIndices;
107  _schemaString = schemaString;
108  _typeId = typeId;
109  }
110 
111  #region Factory Methods
112 
119  public static TypeBuilder Builder(string label) => new TypeBuilder(label);
120 
128  public static async Task<Type> FromTableAsync(kinetica.Kinetica kinetica, string tableName)
129  {
130  if (kinetica == null)
131  throw new ArgumentNullException(nameof(kinetica));
132  if (string.IsNullOrEmpty(tableName))
133  throw new ArgumentException("Table name is required", nameof(tableName));
134 
135  var response = await Task.Run(() => kinetica.showTable(tableName, new Dictionary<string, string>
136  {
137  ["get_sizes"] = "false"
138  }));
139 
140  return FromShowTableResponse(tableName, response);
141  }
142 
149  public static Type FromTable(kinetica.Kinetica kinetica, string tableName)
150  {
151  if (kinetica == null)
152  throw new ArgumentNullException(nameof(kinetica));
153  if (string.IsNullOrEmpty(tableName))
154  throw new ArgumentException("Table name is required", nameof(tableName));
155 
156  var response = kinetica.showTable(tableName, new Dictionary<string, string>
157  {
158  ["get_sizes"] = "false"
159  });
160 
161  return FromShowTableResponse(tableName, response);
162  }
163 
168  public static Type FromShowTableResponse(string tableName, kinetica.ShowTableResponse response)
169  {
170  if (response.type_schemas == null || response.type_schemas.Count == 0)
171  throw new InvalidOperationException($"No type schema for table '{tableName}'");
172 
173  var schemaStr = response.type_schemas[0];
174 
175  // Parse the Avro schema JSON
176  using var doc = JsonDocument.Parse(schemaStr);
177  var root = doc.RootElement;
178 
179  var label = root.TryGetProperty("name", out var nameProp)
180  ? nameProp.GetString() ?? tableName
181  : tableName;
182 
183  if (!root.TryGetProperty("fields", out var fieldsProp) || fieldsProp.ValueKind != JsonValueKind.Array)
184  throw new InvalidOperationException("Schema missing fields array");
185 
186  // Get properties
187  if (response.properties == null || response.properties.Count == 0)
188  throw new InvalidOperationException("No properties in response");
189 
190  var properties = response.properties[0];
191 
192  // Build columns
193  var columns = new List<Column>();
194  var columnIndices = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
195  var shardKeyIndices = new List<int>();
196  var primaryKeyIndices = new List<int>();
197 
198  int idx = 0;
199  foreach (var field in fieldsProp.EnumerateArray())
200  {
201  if (!field.TryGetProperty("name", out var fieldNameProp))
202  throw new InvalidOperationException("Field missing name");
203 
204  var fieldName = fieldNameProp.GetString()
205  ?? throw new InvalidOperationException("Field name is null");
206 
207  var avroType = ExtractAvroType(field);
208  var colProps = properties.TryGetValue(fieldName, out var propList)
209  ? propList
210  : new List<string>();
211 
212  var columnType = ColumnTypeExtensions.FromAvroAndProperties(avroType, colProps);
213  var column = Column.WithProperties(fieldName, columnType, colProps);
214 
215  columns.Add(column);
216  columnIndices[fieldName] = idx;
217 
218  if (column.IsShardKey)
219  shardKeyIndices.Add(idx);
220  if (column.IsPrimaryKey)
221  primaryKeyIndices.Add(idx);
222 
223  idx++;
224  }
225 
226  // Get type ID if available
227  string? typeId = null;
228  if (response.type_ids != null && response.type_ids.Count > 0)
229  typeId = response.type_ids[0];
230 
231  return new Type(
232  label,
233  columns.AsReadOnly(),
234  columnIndices,
235  shardKeyIndices.AsReadOnly(),
236  primaryKeyIndices.AsReadOnly(),
237  schemaStr, // Use original schema string from Kinetica
238  typeId);
239  }
240 
241  private static string ExtractAvroType(JsonElement field)
242  {
243  if (!field.TryGetProperty("type", out var typeProp))
244  return "string";
245 
246  return typeProp.ValueKind switch
247  {
248  JsonValueKind.String => typeProp.GetString() ?? "string",
249  JsonValueKind.Array => ExtractTypeFromUnion(typeProp),
250  JsonValueKind.Object => typeProp.TryGetProperty("type", out var innerType)
251  ? innerType.GetString() ?? "string"
252  : "string",
253  _ => "string"
254  };
255  }
256 
257  private static string ExtractTypeFromUnion(JsonElement unionArray)
258  {
259  // Union type like ["null", "string"] - return the non-null type
260  foreach (var element in unionArray.EnumerateArray())
261  {
262  if (element.ValueKind == JsonValueKind.String)
263  {
264  var typeName = element.GetString();
265  if (typeName != null && typeName != "null")
266  return typeName;
267  }
268  }
269  return "string";
270  }
271 
272  #endregion
273 
274  #region Properties
275 
277  public string Label => _label;
278 
280  public string SchemaString => _schemaString;
281 
283  public string? TypeId => _typeId;
284 
286  public int ColumnCount => _columns.Count;
287 
289  public IReadOnlyList<int> ShardKeyIndices => _shardKeyIndices;
290 
292  public IReadOnlyList<int> PrimaryKeyIndices => _primaryKeyIndices;
293 
295  public bool HasShardKey => _shardKeyIndices.Count > 0;
296 
298  public bool HasPrimaryKey => _primaryKeyIndices.Count > 0;
299 
301  public IReadOnlyList<Column> Columns => _columns;
302 
303  #endregion
304 
305  #region Column Access
306 
312  public Column? GetColumn(int index)
313  {
314  if (index < 0 || index >= _columns.Count)
315  return null;
316  return _columns[index];
317  }
318 
324  public Column? GetColumnByName(string name)
325  {
326  if (_columnIndices.TryGetValue(name, out var index))
327  return _columns[index];
328  return null;
329  }
330 
336  public int? GetColumnIndex(string name)
337  {
338  if (_columnIndices.TryGetValue(name, out var index))
339  return index;
340  return null;
341  }
342 
349  public int GetColumnIndexOrThrow(string name)
350  {
351  if (_columnIndices.TryGetValue(name, out var index))
352  return index;
353  throw new KeyNotFoundException($"Column not found: {name}");
354  }
355 
356  #endregion
357 
358  #region Record Creation
359 
365  public GenericRecord NewInstance() => new GenericRecord(this);
366 
367  #endregion
368 
369  #region Schema Building
370 
371  private static string BuildAvroSchema(string name, IReadOnlyList<Column> columns)
372  {
373  var sb = new StringBuilder();
374  sb.Append("{\"type\":\"record\",\"name\":\"");
375  sb.Append(EscapeJsonString(name));
376  sb.Append("\",\"fields\":[");
377 
378  for (int i = 0; i < columns.Count; i++)
379  {
380  if (i > 0) sb.Append(',');
381 
382  var col = columns[i];
383  var avroType = col.AvroTypeName;
384 
385  sb.Append("{\"name\":\"");
386  sb.Append(EscapeJsonString(col.Name));
387  sb.Append("\",\"type\":");
388 
389  if (col.IsNullable)
390  {
391  sb.Append("[\"null\",\"");
392  sb.Append(avroType);
393  sb.Append("\"]");
394  }
395  else
396  {
397  sb.Append('"');
398  sb.Append(avroType);
399  sb.Append('"');
400  }
401 
402  sb.Append('}');
403  }
404 
405  sb.Append("]}");
406  return sb.ToString();
407  }
408 
409  private static string EscapeJsonString(string s)
410  {
411  // Simple JSON escaping for common cases
412  if (s.IndexOfAny(new[] { '"', '\\', '\n', '\r', '\t' }) < 0)
413  return s;
414 
415  var sb = new StringBuilder(s.Length + 10);
416  foreach (var c in s)
417  {
418  switch (c)
419  {
420  case '"': sb.Append("\\\""); break;
421  case '\\': sb.Append("\\\\"); break;
422  case '\n': sb.Append("\\n"); break;
423  case '\r': sb.Append("\\r"); break;
424  case '\t': sb.Append("\\t"); break;
425  default: sb.Append(c); break;
426  }
427  }
428  return sb.ToString();
429  }
430 
431  #endregion
432 
433  public override string ToString()
434  {
435  return $"Type({_label}, {_columns.Count} columns)";
436  }
437  }
bool HasShardKey
Returns true if this type has a shard key.
Definition: Type.cs:295
string SchemaString
Gets the Avro schema string.
Definition: Type.cs:280
int ColumnCount
Gets the number of columns.
Definition: Type.cs:286
static Type FromShowTableResponse(string tableName, kinetica.ShowTableResponse response)
Build Type from ShowTableResponse.
Definition: Type.cs:168
Column? GetColumnByName(string name)
Gets a column by name.
Definition: Type.cs:324
static TypeBuilder Builder(string label)
Creates a TypeBuilder for fluent construction.
Column? GetColumn(int index)
Gets a column by index.
Definition: Type.cs:312
int? GetColumnIndex(string name)
Gets a column index by name.
Definition: Type.cs:336
bool HasPrimaryKey
Returns true if this type has a primary key.
Definition: Type.cs:298
string? TypeId
Gets the type ID (if fetched from Kinetica).
Definition: Type.cs:283
string Label
Gets the type label/name.
Definition: Type.cs:277
GenericRecord NewInstance()
Creates a new GenericRecord instance of this type.
IReadOnlyList< int > ShardKeyIndices
Gets the shard key column indices.
Definition: Type.cs:289
IReadOnlyList< int > PrimaryKeyIndices
Gets the primary key column indices.
Definition: Type.cs:292
A set of results returned by Kinetica.showTable.
Definition: ShowTable.cs:638
Type(string label, IEnumerable< Column > columns)
Creates a Type from column definitions.
Definition: Type.cs:53
override string ToString()
Definition: Type.cs:433
Extension methods for ColumnType.
Definition: ColumnType.cs:103
static ColumnType FromAvroAndProperties(string avroType, IEnumerable< string >? properties)
Parse column type from Avro type and properties.
Definition: ColumnType.cs:169
Immutable metadata about a column in a Kinetica type.
Definition: Column.cs:11
static async Task< Type > FromTableAsync(kinetica.Kinetica kinetica, string tableName)
Fetch type from an existing Kinetica table.
Definition: Type.cs:128
int GetColumnIndexOrThrow(string name)
Gets a column index by name, throwing if not found.
Definition: Type.cs:349
Builder for constructing a Type with a fluent API.
Definition: TypeBuilder.cs:21
Immutable collection of metadata about a Kinetica type.
Definition: Type.cs:36
A generic record that can hold values for any Kinetica type.
static Column WithProperties(string name, ColumnType columnType, IEnumerable< string > properties)
Creates a column with properties from a list.
IReadOnlyList< Column > Columns
Gets all columns.
Definition: Type.cs:301
static Type FromTable(kinetica.Kinetica kinetica, string tableName)
Fetch type from an existing Kinetica table (synchronous version).
Definition: Type.cs:149
API to talk to Kinetica Database
Definition: Kinetica.cs:40