Kinetica   C#   API  Version 7.2.3.0
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 {
14  public class KineticaData : ISpecificRecord
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(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(jsonType) as RecordSchema;
100  }
101 
102  private static bool IsNullable(Type type)
103  {
104  if (type == null) return false;
105 
106  // 1. Check for Nullable<T> (works for value types like int?, double?)
107  if (Nullable.GetUnderlyingType(type) != null)
108  return true;
109 
110  // 2. Check for nullable reference types (C# 8+ feature)
111  if (!type.IsValueType)
112  {
113  var attributes = type.CustomAttributes;
114  return attributes.Any(attr => attr.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute");
115  }
116 
117  return false;
118  }
119 
120 
128  private static string AvroType( System.Type? t, KineticaType? ktype )
129  {
130  if ( t == null)
131  throw new KineticaException( "Null type passed to AvroType()" );
132 
133  switch ( t.Name)
134  {
135  case "Boolean": return "\"boolean\"";
136  case "Int32": return "\"int\"";
137  case "Int64": return "\"long\"";
138  case "Double": return "\"double\"";
139  case "Single": return "\"float\"";
140  case "Byte[]": return "\"bytes\"";
141  case "String": return "\"string\"";
142  case "String[]": return $"{{ \"type\":\"array\", \"items\":\"string\"}}";
143  case "String[][]": return $"{{ \"type\":\"array\", \"items\":{{ \"type\":\"array\", \"items\":\"string\"}}}}";
144 
145  // For a nullable object, return the avro type of the underlying type (e.g. double)
146  case "Nullable`1": return AvroType(Nullable.GetUnderlyingType(t), ktype);
147 
148  case "List`1":
149  case "IList`1":
150  if ( t.IsGenericType )
151  {
152  var genericParams = t.GenericTypeArguments;
153  if (1 == genericParams.Length)
154  {
155  return $"{{ \"type\":\"array\", \"items\":{AvroType( genericParams[0], ktype )}}}";
156  }
157  }
158  break;
159 
160  case "Dictionary`2":
161  case "IDictionary`2":
162  if (t.IsGenericType)
163  {
164  var genericParams = t.GenericTypeArguments;
165  if (2 == genericParams.Length)
166  {
167  return $"{{ \"type\":\"map\", \"values\":{AvroType( genericParams[1], ktype )}}}";
168  }
169  }
170  break;
171 
172  // Ignore the "Schema" property inherited from KineticaData
173  case "Schema": break;
174 
175  // Ignore the "RecordSchema" property inherited from KineticaRecord
176  case "RecordSchema":
177  break;
178 
179  // If Type is an object, treat it as a sub-record in Avro
180  default:
181  if (t.IsSubclassOf(typeof(Object)))
182  {
183  string fields = "";
184  // Create the avro string for each property of the class
185  PropertyInfo[] type_properties = t.GetProperties( BindingFlags.DeclaredOnly |
186  BindingFlags.Instance |
187  BindingFlags.Public );
188  Array.Sort( type_properties, delegate ( PropertyInfo p1, PropertyInfo p2 )
189  { return p1.MetadataToken.CompareTo( p2.MetadataToken ); } );
190 
191  foreach ( var prop in type_properties )
192  {
193  bool is_nullable = false;
194  var prop_type = prop.PropertyType;
195  if ( prop_type.IsGenericType && prop_type.GetGenericTypeDefinition() == typeof( Nullable<> ) )
196  { // the property is nullable based on reflection
197  is_nullable = true;
198  }
199  else if ( (ktype != null) && ktype.getColumn( prop.Name ).isNullable() )
200  { // the property is nullable based on information saved in the associated KineticaType
201  is_nullable = true;
202  }
203 
204  // Get the avro type string for the property type
205  string avroType = AvroType( prop_type, ktype );
206  if ( !String.IsNullOrWhiteSpace( avroType ) )
207  {
208  if ( is_nullable )
209  { // the field is nullable
210  fields += $"{{\"name\":\"{prop.Name}\",\"type\":[{avroType},\"null\"]}},";
211  }
212  else
213  { // it's a regular field
214  fields += $"{{\"name\":\"{prop.Name}\",\"type\":{avroType}}},";
215  }
216  }
217  }
218 
219  // Trim the trailing comma from the fields
220  char[] comma = [','];
221  fields = fields.TrimEnd( comma );
222 
223  // Put together the avro fields with the name to create a record type
224  return $"{{\"type\":\"record\",\"name\":\"{t.Name}\",\"fields\":[{fields}]}}";
225  }
226  System.Diagnostics.Debug.WriteLine($"Unkonwn type: {t.Name}"); break;
227  }
228 
229  return "";
230  } // end AvroType
231 
232  /* Code to copy current object into a new GenericRecord - Not currently used (or tested)
233  public Avro.Generic.GenericRecord CopyTo()
234  {
235  Avro.Generic.GenericRecord record = new Avro.Generic.GenericRecord(m_schema);
236  foreach (var prop in m_properties)
237  {
238  if (m_schema.Contains(prop.Name))
239  {
240  record.Add(prop.Name, prop.GetValue(this));
241  }
242  }
243 
244  return record;
245  }
246  */
247  }
248 }
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
KineticaData(KineticaType type)
Constructor from Kinetica Type
Definition: KineticaData.cs:34
void Put(int fieldPos, object fieldValue)
Write a specific property to this object
Definition: KineticaData.cs:66
Schema Schema
Avro Schema for this class
Definition: KineticaData.cs:23
KineticaData - class to help with Avro Encoding for Kinetica
Definition: KineticaData.cs:14
object Get(int fieldPos)
Retrieve a specific property from this object
Definition: KineticaData.cs:56