Kinetica   C#   API  Version 7.2.3.1
KineticaData.cs
Go to the documentation of this file.
1 using Avro;
2 using Avro.IO;
3 using Avro.Specific;
4 using System;
5 using System.Collections.Generic;
6 using System.IO;
7 using System.Reflection;
8 
9 namespace kinetica;
10 
15  {
16  private RecordSchema m_schema;
17  private PropertyInfo[] m_properties;
18 
22  public Schema Schema
23  {
24  get
25  {
26  return m_schema;
27  }
28  }
29 
35  {
36  m_schema = Avro.Schema.Parse(NormalizeSchemaJson(type.getSchemaString())) as RecordSchema;
37  m_properties = this.GetType().GetProperties();
38  }
39 
44  public KineticaData(System.Type type = null)
45  {
46  var t = type ?? this.GetType();
47  m_schema = SchemaFromType(t, null);
48  m_properties = this.GetType().GetProperties();
49  }
50 
56  public object Get(int fieldPos)
57  {
58  return m_properties[fieldPos].GetValue(this);
59  }
60 
66  public void Put(int fieldPos, object fieldValue)
67  {
68  m_properties[fieldPos].SetValue(this, fieldValue);
69  }
70 
71  private static string? GetEmbeddedSchema( Type t) {
72  string? schema = null;
73  // Get the FieldInfo for "m_schema"
74  FieldInfo? field = t.GetField("Schema_", BindingFlags.NonPublic | BindingFlags.Static);
75 
76  if (field != null)
77  {
78  // Get the value of const 'Schema_' from the class
79  schema = (string)field.GetValue(null);
80  }
81 
82  return schema;
83  }
84 
85 
92  public static RecordSchema? SchemaFromType( System.Type t, KineticaType? ktype = null )
93  {
94  string? jsonType = GetEmbeddedSchema(t);
95  jsonType ??= AvroType( t, ktype );
96  // using JsonDocument doc = JsonDocument.Parse(jsonType);
97  // string v = JsonSerializer.Serialize(doc.RootElement, new JsonSerializerOptions { WriteIndented = true });
98  // Console.WriteLine(t.ToString() + "::::" + v);
99  return Schema.Parse(NormalizeSchemaJson(jsonType)) as RecordSchema;
100  }
101 
109  internal static string NormalizeSchemaJson(string? schemaJson)
110  {
111  if (string.IsNullOrWhiteSpace(schemaJson))
112  return schemaJson ?? string.Empty;
113 
114  try
115  {
116  // Round-trip through JToken to normalize JSON formatting
117  var token = Newtonsoft.Json.Linq.JToken.Parse(schemaJson);
118  return token.ToString(Newtonsoft.Json.Formatting.None);
119  }
120  catch (Newtonsoft.Json.JsonException)
121  {
122  // If parsing fails, return original string
123  // Avro.Schema.Parse() will throw proper error
124  return schemaJson;
125  }
126  }
127 
128  private static bool IsNullable(Type type)
129  {
130  if (type == null) return false;
131 
132  // 1. Check for Nullable<T> (works for value types like int?, double?)
133  if (Nullable.GetUnderlyingType(type) != null)
134  return true;
135 
136  // 2. Check for nullable reference types (C# 8+ feature)
137  if (!type.IsValueType)
138  {
139  var attributes = type.CustomAttributes;
140  return attributes.Any(attr => attr.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute");
141  }
142 
143  return false;
144  }
145 
146 
154  private static string AvroType( System.Type? t, KineticaType? ktype )
155  {
156  if ( t == null)
157  throw new KineticaException( "Null type passed to AvroType()" );
158 
159  switch ( t.Name)
160  {
161  case "Boolean": return "\"boolean\"";
162  case "Int32": return "\"int\"";
163  case "Int64": return "\"long\"";
164  case "Double": return "\"double\"";
165  case "Single": return "\"float\"";
166  case "Byte[]": return "\"bytes\"";
167  case "String": return "\"string\"";
168  case "String[]": return $"{{ \"type\":\"array\", \"items\":\"string\"}}";
169  case "String[][]": return $"{{ \"type\":\"array\", \"items\":{{ \"type\":\"array\", \"items\":\"string\"}}}}";
170 
171  // For a nullable object, return the avro type of the underlying type (e.g. double)
172  case "Nullable`1": return AvroType(Nullable.GetUnderlyingType(t), ktype);
173 
174  case "List`1":
175  case "IList`1":
176  if ( t.IsGenericType )
177  {
178  var genericParams = t.GenericTypeArguments;
179  if (1 == genericParams.Length)
180  {
181  return $"{{ \"type\":\"array\", \"items\":{AvroType( genericParams[0], ktype )}}}";
182  }
183  }
184  break;
185 
186  case "Dictionary`2":
187  case "IDictionary`2":
188  if (t.IsGenericType)
189  {
190  var genericParams = t.GenericTypeArguments;
191  if (2 == genericParams.Length)
192  {
193  return $"{{ \"type\":\"map\", \"values\":{AvroType( genericParams[1], ktype )}}}";
194  }
195  }
196  break;
197 
198  // Ignore the "Schema" property inherited from KineticaData
199  case "Schema": break;
200 
201  // Ignore the "RecordSchema" property inherited from KineticaRecord
202  case "RecordSchema":
203  break;
204 
205  // If Type is an object, treat it as a sub-record in Avro
206  default:
207  if (t.IsSubclassOf(typeof(Object)))
208  {
209  string fields = "";
210  // Create the avro string for each property of the class
211  PropertyInfo[] type_properties = t.GetProperties( BindingFlags.DeclaredOnly |
212  BindingFlags.Instance |
213  BindingFlags.Public );
214  Array.Sort( type_properties, delegate ( PropertyInfo p1, PropertyInfo p2 )
215  { return p1.MetadataToken.CompareTo( p2.MetadataToken ); } );
216 
217  foreach ( var prop in type_properties )
218  {
219  bool is_nullable = false;
220  var prop_type = prop.PropertyType;
221  if ( prop_type.IsGenericType && prop_type.GetGenericTypeDefinition() == typeof( Nullable<> ) )
222  { // the property is nullable based on reflection
223  is_nullable = true;
224  }
225  else if ( (ktype != null) && ktype.getColumn( prop.Name ).isNullable() )
226  { // the property is nullable based on information saved in the associated KineticaType
227  is_nullable = true;
228  }
229 
230  // Get the avro type string for the property type
231  string avroType = AvroType( prop_type, ktype );
232  if ( !String.IsNullOrWhiteSpace( avroType ) )
233  {
234  if ( is_nullable )
235  { // the field is nullable
236  fields += $"{{\"name\":\"{prop.Name}\",\"type\":[{avroType},\"null\"]}},";
237  }
238  else
239  { // it's a regular field
240  fields += $"{{\"name\":\"{prop.Name}\",\"type\":{avroType}}},";
241  }
242  }
243  }
244 
245  // Trim the trailing comma from the fields
246  char[] comma = [','];
247  fields = fields.TrimEnd( comma );
248 
249  // Put together the avro fields with the name to create a record type
250  return $"{{\"type\":\"record\",\"name\":\"{t.Name}\",\"fields\":[{fields}]}}";
251  }
252  System.Diagnostics.Debug.WriteLine($"Unkonwn type: {t.Name}"); break;
253  }
254 
255  return "";
256  } // end AvroType
257 
258  /* Code to copy current object into a new GenericRecord - Not currently used (or tested)
259  public Avro.Generic.GenericRecord CopyTo()
260  {
261  Avro.Generic.GenericRecord record = new Avro.Generic.GenericRecord(m_schema);
262  foreach (var prop in m_properties)
263  {
264  if (m_schema.Contains(prop.Name))
265  {
266  record.Add(prop.Name, prop.GetValue(this));
267  }
268  }
269 
270  return record;
271  }
272  */
273  }
string getSchemaString()
Class for record schemas
Definition: RecordSchema.cs:31
AvroType
Avro primitive types for schema building.
Definition: AvroType.cs:7
Base class for all schema types
Definition: Schema.cs:29
KineticaData - class to help with Avro Encoding for Kinetica
Definition: KineticaData.cs:14
bool isNullable()
Returns if the column is nullable.
Definition: KineticaType.cs:88
KineticaData(KineticaType type)
Constructor from Kinetica Type
Definition: KineticaData.cs:34
Interface class for generated classes
Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.
override string ToString()
Returns the canonical JSON representation of this schema.
Definition: Schema.cs:191
object Get(int fieldPos)
Retrieve a specific property from this object
Definition: KineticaData.cs:56
Column getColumn(int index)
static ? RecordSchema SchemaFromType(System.Type t, KineticaType? ktype=null)
Create an Avro Schema from a System.Type and a KineticaType.
Definition: KineticaData.cs:92
KineticaData(System.Type type=null)
Default constructor, with optional System.Type
Definition: KineticaData.cs:44
Immutable collection of metadata about a Kinetica type.
Definition: Type.cs:36
static Schema Parse(string json)
Parses a given JSON string to create a new schema object
Definition: Schema.cs:141
void Put(int fieldPos, object fieldValue)
Write a specific property to this object
Definition: KineticaData.cs:66