Kinetica   C#   API  Version 7.2.3.1
ProtocolWireFormatTests.cs
Go to the documentation of this file.
1 using System;
2 using System.Linq;
3 using System.Text;
4 using Xunit;
5 
7 {
22  [Trait("Category", "WireFormat")]
23  [Trait("Category", "Protocol")]
25  {
26  #region RecordKey Wire Format Tests
27 
32  [Fact]
34  {
35  // Arrange: Create a RecordKey with a specific integer value
36  var recordKeyType = typeof(kinetica.Kinetica).Assembly
37  .GetType("kinetica.Utils.RecordKey");
38  var recordKey = Activator.CreateInstance(recordKeyType, 4); // 4-byte buffer
39 
40  var addIntMethod = recordKeyType.GetMethod("addInt");
41  addIntMethod!.Invoke(recordKey, new object[] { 12345 });
42 
43  // Act: Get the binary representation
44  var bufferField = recordKeyType.GetField("buffer",
45  System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
46  var buffer = (byte[])bufferField!.GetValue(recordKey)!;
47 
48  // Assert: Integer 12345 should be encoded as little-endian
49  // 12345 = 0x00003039 in little-endian: 0x39, 0x30, 0x00, 0x00
50  byte[] expected = { 0x39, 0x30, 0x00, 0x00 };
51  Assert.Equal(expected, buffer);
52  }
53 
58  [Fact]
60  {
61  // Arrange
62  var recordKeyType = typeof(kinetica.Kinetica).Assembly
63  .GetType("kinetica.Utils.RecordKey");
64  var recordKey = Activator.CreateInstance(recordKeyType, 8); // 8-byte buffer for char8
65 
66  var addCharNMethod = recordKeyType.GetMethod("addCharN");
67  addCharNMethod!.Invoke(recordKey, new object[] { "ABCD", 8 });
68 
69  // Act
70  var bufferField = recordKeyType.GetField("buffer",
71  System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
72  var buffer = (byte[])bufferField!.GetValue(recordKey)!;
73 
74  // Assert: "ABCD" as UTF-8 with little-endian encoding (null padding first, then bytes in reverse)
75  byte[] expected = { 0x00, 0x00, 0x00, 0x00, 0x44, 0x43, 0x42, 0x41 }; // [null padding, D, C, B, A]
76  Assert.Equal(expected, buffer);
77  }
78 
82  [Fact]
84  {
85  // Arrange
86  var recordKeyType = typeof(kinetica.Kinetica).Assembly
87  .GetType("kinetica.Utils.RecordKey");
88  var recordKey = Activator.CreateInstance(recordKeyType, 8);
89 
90  var addLongMethod = recordKeyType.GetMethod("addLong");
91  addLongMethod!.Invoke(recordKey, new object[] { 9876543210L });
92 
93  // Act
94  var bufferField = recordKeyType.GetField("buffer",
95  System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
96  var buffer = (byte[])bufferField!.GetValue(recordKey)!;
97 
98  // Assert: 9876543210 = 0x0000000024CB016EA, BitConverter encodes as little-endian
99  byte[] expected = { 234, 22, 176, 76, 2, 0, 0, 0 }; // [0xEA, 0x16, 0xB0, 0x4C, 0x02, 0x00, 0x00, 0x00]
100  Assert.Equal(expected, buffer);
101  }
102 
107  [Fact]
109  {
110  // Arrange: Decimal value "123.4567" with scale 4
111  var recordKeyType = typeof(kinetica.Kinetica).Assembly
112  .GetType("kinetica.Utils.RecordKey");
113  var recordKey = Activator.CreateInstance(recordKeyType, 8);
114 
115  var addDecimalMethod = recordKeyType.GetMethod("addDecimal",
116  new[] { typeof(string), typeof(int), typeof(int) });
117  addDecimalMethod!.Invoke(recordKey, new object[] { "123.4567", 18, 4 });
118 
119  // Act
120  var bufferField = recordKeyType.GetField("buffer",
121  System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
122  var buffer = (byte[])bufferField!.GetValue(recordKey)!;
123 
124  // Assert: 123.4567 with scale 4 = 1234567 as long
125  // 1234567 = 0x0012D687 in little-endian
126  long expected = 1234567L;
127  long actual = BitConverter.ToInt64(buffer, 0);
128  Assert.Equal(expected, actual);
129  }
130 
134  [Fact]
136  {
137  // Arrange: Large decimal requiring 12-byte encoding
138  var recordKeyType = typeof(kinetica.Kinetica).Assembly
139  .GetType("kinetica.Utils.RecordKey");
140  var recordKey = Activator.CreateInstance(recordKeyType, 12);
141 
142  var addDecimalMethod = recordKeyType.GetMethod("addDecimal",
143  new[] { typeof(string), typeof(int), typeof(int) });
144  // Use a value that requires more than 8 bytes
145  addDecimalMethod!.Invoke(recordKey, new object[] { "12345678901234567890.123456", 38, 6 });
146 
147  // Act
148  var bufferField = recordKeyType.GetField("buffer",
149  System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
150  var buffer = (byte[])bufferField!.GetValue(recordKey)!;
151  var currentSizeField = recordKeyType.GetField("current_size",
152  System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
153  var size = (int)currentSizeField!.GetValue(recordKey)!;
154 
155  // Assert: Should use all 12 bytes
156  Assert.Equal(12, size);
157 
158  // Verify buffer is not all zeros (contains the large value)
159  bool hasNonZero = buffer.Any(b => b != 0);
160  Assert.True(hasNonZero, "12-byte decimal buffer should contain non-zero bytes");
161  }
162 
163  #endregion
164 
165  #region Type Schema Encoding Tests
166 
171  [Fact]
173  {
174  // Arrange: Define a simple type
175  string schemaJson = @"{
176  ""type"": ""record"",
177  ""name"": ""TestRecord"",
178  ""fields"": [
179  {""name"": ""id"", ""type"": ""int""},
180  {""name"": ""name"", ""type"": ""string""}
181  ]
182  }";
183 
184  // Act: Create type from schema
185  var ktype = new kinetica.KineticaType(schemaJson);
186  string generatedSchema = ktype.getSchemaString();
187 
188  // Assert: Schema should match expected format (normalized JSON)
189  // The exact format may vary, but key elements must be present
190  Assert.Contains("\"type\":\"record\"", generatedSchema.Replace(" ", ""));
191  Assert.Contains("\"name\":\"TestRecord\"", generatedSchema.Replace(" ", ""));
192  Assert.Contains("\"fields\"", generatedSchema);
193  Assert.Contains("\"id\"", generatedSchema);
194  Assert.Contains("\"name\"", generatedSchema);
195  }
196 
200  [Fact]
202  {
203  // Arrange
204  string schemaJson = @"{
205  ""type"": ""record"",
206  ""name"": ""NullableTest"",
207  ""fields"": [
208  {""name"": ""optional_int"", ""type"": [""null"", ""int""]}
209  ]
210  }";
211 
212  // Act
213  var ktype = new kinetica.KineticaType(schemaJson);
214  string generatedSchema = ktype.getSchemaString();
215 
216  // Assert: Should contain union type [null, int]
217  // Note: Order matters in Avro unions - null first is standard
218  Assert.Contains("[\"null\",\"int\"]", generatedSchema.Replace(" ", ""));
219  }
220 
221  #endregion
222 
223  #region Column Property Encoding Tests
224 
229  [Fact]
231  {
232  // This is a smoke test - actual property encoding happens in server protocol
233  // We verify that the constant values don't change
234  Assert.Equal("shard_key", kinetica.ColumnProperty.SHARD_KEY);
235  Assert.Equal("primary_key", kinetica.ColumnProperty.PRIMARY_KEY);
236  Assert.Equal("nullable", kinetica.ColumnProperty.NULLABLE);
237  Assert.Equal("dict", kinetica.ColumnProperty.DICT);
238  Assert.Equal("timestamp", kinetica.ColumnProperty.TIMESTAMP);
239  }
240 
241  #endregion
242 
243  #region Compression Tests
244 
250  [Fact]
252  {
253  // Arrange: Sample data
254  string originalText = "Hello, Kinetica! This is a test of compression. " +
255  "The data should compress well due to repetition. " +
256  "The data should compress well due to repetition.";
257  byte[] originalBytes = Encoding.UTF8.GetBytes(originalText);
258 
259  // Act: Compress and decompress using Snappier
260  byte[] compressed = Snappier.Snappy.CompressToArray(originalBytes);
261  byte[] decompressed = Snappier.Snappy.DecompressToArray(compressed);
262 
263  // Assert: Round-trip should preserve data exactly
264  Assert.Equal(originalBytes, decompressed);
265 
266  // Also verify compression actually reduced size
267  Assert.True(compressed.Length < originalBytes.Length,
268  $"Compressed size ({compressed.Length}) should be less than original ({originalBytes.Length})");
269  }
270 
271  #endregion
272 
273  #region Regression Tests
274 
279  [Fact(Skip = "KineticaType does not support empty schemas - requires at least one field")]
281  {
282  // Arrange
283  string schemaJson = @"{
284  ""type"": ""record"",
285  ""name"": ""EmptyRecord"",
286  ""fields"": []
287  }";
288 
289  var ktype = new kinetica.KineticaType(schemaJson);
290 
291  // Act: Encode an empty record class
292  // This tests the edge case of records with no fields
293  var schema = ktype.getSchema();
294 
295  // Assert: Schema should have zero fields
296  var recordSchema = schema as Avro.RecordSchema;
297  Assert.NotNull(recordSchema);
298  Assert.Empty(recordSchema!.Fields);
299  }
300 
305  [Fact]
307  {
308  // Arrange: Composite key with int + string
309  var recordKeyType = typeof(kinetica.Kinetica).Assembly
310  .GetType("kinetica.Utils.RecordKey");
311  var recordKey = Activator.CreateInstance(recordKeyType, 12); // 4 bytes int + 8 bytes char8
312 
313  var addIntMethod = recordKeyType.GetMethod("addInt");
314  var addCharNMethod = recordKeyType.GetMethod("addCharN");
315 
316  // Act: Add fields in order
317  addIntMethod!.Invoke(recordKey, new object[] { 999 });
318  addCharNMethod!.Invoke(recordKey, new object[] { "KEY", 8 });
319 
320  // Get buffer
321  var bufferField = recordKeyType.GetField("buffer",
322  System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
323  var buffer = (byte[])bufferField!.GetValue(recordKey)!;
324 
325  // Assert: First 4 bytes are the int, next 8 bytes are the string
326  // 999 = 0x000003E7 in little-endian: 0xE7, 0x03, 0x00, 0x00
327  Assert.Equal(0xE7, buffer[0]);
328  Assert.Equal(0x03, buffer[1]);
329  Assert.Equal(0x00, buffer[2]);
330  Assert.Equal(0x00, buffer[3]);
331 
332  // "KEY" as UTF-8 with little-endian encoding (padding first: 5 nulls, then Y, E, K in reverse)
333  Assert.Equal(0x00, buffer[4]); // null padding
334  Assert.Equal(0x00, buffer[5]); // null padding
335  Assert.Equal(0x00, buffer[6]); // null padding
336  Assert.Equal(0x00, buffer[7]); // null padding
337  Assert.Equal(0x00, buffer[8]); // null padding
338  Assert.Equal((byte)'Y', buffer[9]); // Reversed
339  Assert.Equal((byte)'E', buffer[10]); // Reversed
340  Assert.Equal((byte)'K', buffer[11]); // Reversed
341  }
342 
343  #endregion
344  }
345 }
void ColumnProperties_ShardKeyProperty_EncodesConsistently()
Tests that column properties are encoded consistently.
Class for record schemas
Definition: RecordSchema.cs:31
const string PRIMARY_KEY
This property indicates that this column will be part of (or the entire) primary key.
void RecordKey_LongShardKey_ProducesStableEncoding()
Tests RecordKey encoding for long values.
void RecordKey_Decimal12Byte_StableWireFormat()
Regression test: Decimal encoding with precision > 18 (12-byte format).
Column properties used for Kinetica types.
const string TIMESTAMP
Valid only for 'long' columns.
void Compression_Snappy_RoundTripsCorrectly()
Tests that data compression produces deterministic output (when supported).
void RecordKey_Decimal8Byte_StableWireFormat()
Regression test: Decimal encoding with precision <= 18 (8-byte format).
Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.
Protocol-level wire-format tests for Kinetica binary protocol.
void KineticaType_NullableField_UsesConsistentUnionSchema()
Tests that nullable field schemas use consistent union encoding.
void RecordKey_StringShardKey_ProducesStableEncoding()
Tests RecordKey encoding for strings (charN routing columns).
void RecordKey_IntegerShardKey_ProducesStableEncoding()
Tests that RecordKey encoding for integers produces consistent byte patterns.
void EmptyRecord_EncodesConsistently()
Regression test: Verify that empty Avro records encode consistently.
void RecordKey_CompositeKey_MaintainsFieldOrder()
Regression test: Multi-field RecordKey stability.
const string SHARD_KEY
This property indicates that this column will be part of (or the entire) shard key.
const string DICT
This property indicates that this column should be dictionary encoded.
const string NULLABLE
This property indicates that this column is nullable.
API to talk to Kinetica Database
Definition: Kinetica.cs:40
void KineticaType_SimpleSchema_ProducesStableSchemaString()
Tests that KineticaType schema strings remain stable.