2 using System.Collections.Generic;
5 using System.Text.Json;
6 using System.Threading.Tasks;
8 namespace kinetica.Records;
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;
53 public Type(
string label, IEnumerable<Column> columns)
55 _label = label ??
throw new ArgumentNullException(nameof(label));
56 var columnList = columns?.ToList() ??
throw new ArgumentNullException(nameof(columns));
58 if (columnList.Count == 0)
59 throw new ArgumentException(
"At least one column is required", nameof(columns));
61 _columns = columnList.AsReadOnly();
64 var columnIndices =
new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
65 var shardKeyIndices =
new List<int>();
66 var primaryKeyIndices =
new List<int>();
68 for (
int i = 0; i < columnList.Count; i++)
70 var col = columnList[i];
71 if (columnIndices.ContainsKey(col.Name))
72 throw new ArgumentException($
"Duplicate column name: {col.Name}", nameof(columns));
74 columnIndices[col.Name] = i;
77 shardKeyIndices.Add(i);
80 primaryKeyIndices.Add(i);
83 _columnIndices = columnIndices;
84 _shardKeyIndices = shardKeyIndices.AsReadOnly();
85 _primaryKeyIndices = primaryKeyIndices.AsReadOnly();
88 _schemaString = BuildAvroSchema(label, columnList);
95 IReadOnlyList<Column> columns,
96 IReadOnlyDictionary<string, int> columnIndices,
97 IReadOnlyList<int> shardKeyIndices,
98 IReadOnlyList<int> primaryKeyIndices,
104 _columnIndices = columnIndices;
105 _shardKeyIndices = shardKeyIndices;
106 _primaryKeyIndices = primaryKeyIndices;
107 _schemaString = schemaString;
111 #region Factory Methods 131 throw new ArgumentNullException(nameof(
kinetica));
132 if (
string.IsNullOrEmpty(tableName))
133 throw new ArgumentException(
"Table name is required", nameof(tableName));
135 var response = await Task.Run(() =>
kinetica.showTable(tableName,
new Dictionary<string, string>
137 [
"get_sizes"] =
"false" 152 throw new ArgumentNullException(nameof(
kinetica));
153 if (
string.IsNullOrEmpty(tableName))
154 throw new ArgumentException(
"Table name is required", nameof(tableName));
156 var response =
kinetica.showTable(tableName,
new Dictionary<string, string>
158 [
"get_sizes"] =
"false" 170 if (response.type_schemas ==
null || response.type_schemas.Count == 0)
171 throw new InvalidOperationException($
"No type schema for table '{tableName}'");
173 var schemaStr = response.type_schemas[0];
176 using var doc = JsonDocument.Parse(schemaStr);
177 var root = doc.RootElement;
179 var label = root.TryGetProperty(
"name", out var nameProp)
180 ? nameProp.GetString() ?? tableName
183 if (!root.TryGetProperty(
"fields", out var fieldsProp) || fieldsProp.ValueKind != JsonValueKind.Array)
184 throw new InvalidOperationException(
"Schema missing fields array");
187 if (response.properties ==
null || response.properties.Count == 0)
188 throw new InvalidOperationException(
"No properties in response");
190 var properties = response.properties[0];
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>();
199 foreach (var field
in fieldsProp.EnumerateArray())
201 if (!field.TryGetProperty(
"name", out var fieldNameProp))
202 throw new InvalidOperationException(
"Field missing name");
204 var fieldName = fieldNameProp.GetString()
205 ??
throw new InvalidOperationException(
"Field name is null");
207 var avroType = ExtractAvroType(field);
208 var colProps = properties.TryGetValue(fieldName, out var propList)
210 :
new List<string>();
216 columnIndices[fieldName] = idx;
218 if (column.IsShardKey)
219 shardKeyIndices.Add(idx);
220 if (column.IsPrimaryKey)
221 primaryKeyIndices.Add(idx);
227 string? typeId =
null;
228 if (response.type_ids !=
null && response.type_ids.Count > 0)
229 typeId = response.type_ids[0];
233 columns.AsReadOnly(),
235 shardKeyIndices.AsReadOnly(),
236 primaryKeyIndices.AsReadOnly(),
241 private static string ExtractAvroType(JsonElement field)
243 if (!field.TryGetProperty(
"type", out var typeProp))
246 return typeProp.ValueKind
switch 248 JsonValueKind.String => typeProp.GetString() ??
"string",
249 JsonValueKind.Array => ExtractTypeFromUnion(typeProp),
250 JsonValueKind.Object => typeProp.TryGetProperty(
"type", out var innerType)
251 ? innerType.GetString() ??
"string" 257 private static string ExtractTypeFromUnion(JsonElement unionArray)
260 foreach (var element
in unionArray.EnumerateArray())
262 if (element.ValueKind == JsonValueKind.String)
264 var typeName = element.GetString();
265 if (typeName !=
null && typeName !=
"null")
301 public IReadOnlyList<Column>
Columns => _columns;
305 #region Column Access 314 if (index < 0 || index >= _columns.Count)
316 return _columns[index];
326 if (_columnIndices.TryGetValue(name, out var index))
327 return _columns[index];
338 if (_columnIndices.TryGetValue(name, out var index))
351 if (_columnIndices.TryGetValue(name, out var index))
353 throw new KeyNotFoundException($
"Column not found: {name}");
358 #region Record Creation 369 #region Schema Building 371 private static string BuildAvroSchema(
string name, IReadOnlyList<Column> columns)
373 var sb =
new StringBuilder();
374 sb.Append(
"{\"type\":\"record\",\"name\":\"");
375 sb.Append(EscapeJsonString(name));
376 sb.Append(
"\",\"fields\":[");
378 for (
int i = 0; i < columns.Count; i++)
380 if (i > 0) sb.Append(
',');
382 var col = columns[i];
383 var avroType = col.AvroTypeName;
385 sb.Append(
"{\"name\":\"");
386 sb.Append(EscapeJsonString(col.Name));
387 sb.Append(
"\",\"type\":");
391 sb.Append(
"[\"null\",\"");
406 return sb.ToString();
409 private static string EscapeJsonString(
string s)
412 if (s.IndexOfAny(
new[] {
'"',
'\\',
'\n',
'\r',
'\t' }) < 0)
415 var sb =
new StringBuilder(s.Length + 10);
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;
428 return sb.ToString();
435 return $
"Type({_label}, {_columns.Count} columns)";
bool HasShardKey
Returns true if this type has a shard key.
string SchemaString
Gets the Avro schema string.
int ColumnCount
Gets the number of columns.
static Type FromShowTableResponse(string tableName, kinetica.ShowTableResponse response)
Build Type from ShowTableResponse.
Column? GetColumnByName(string name)
Gets a column by name.
static TypeBuilder Builder(string label)
Creates a TypeBuilder for fluent construction.
Column? GetColumn(int index)
Gets a column by index.
int? GetColumnIndex(string name)
Gets a column index by name.
bool HasPrimaryKey
Returns true if this type has a primary key.
string? TypeId
Gets the type ID (if fetched from Kinetica).
string Label
Gets the type label/name.
GenericRecord NewInstance()
Creates a new GenericRecord instance of this type.
IReadOnlyList< int > ShardKeyIndices
Gets the shard key column indices.
IReadOnlyList< int > PrimaryKeyIndices
Gets the primary key column indices.
A set of results returned by Kinetica.showTable.
Type(string label, IEnumerable< Column > columns)
Creates a Type from column definitions.
override string ToString()
Extension methods for ColumnType.
static ColumnType FromAvroAndProperties(string avroType, IEnumerable< string >? properties)
Parse column type from Avro type and properties.
Immutable metadata about a column in a Kinetica type.
static async Task< Type > FromTableAsync(kinetica.Kinetica kinetica, string tableName)
Fetch type from an existing Kinetica table.
int GetColumnIndexOrThrow(string name)
Gets a column index by name, throwing if not found.
Builder for constructing a Type with a fluent API.
Immutable collection of metadata about a Kinetica type.
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.
static Type FromTable(kinetica.Kinetica kinetica, string tableName)
Fetch type from an existing Kinetica table (synchronous version).
API to talk to Kinetica Database