gqlite 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/ext/Cargo.toml +12 -6
  3. data/ext/db-index/Cargo.toml +29 -0
  4. data/ext/db-index/src/hnsw/error.rs +66 -0
  5. data/ext/db-index/src/hnsw/graph_store.rs +272 -0
  6. data/ext/db-index/src/hnsw/id.rs +36 -0
  7. data/ext/db-index/src/hnsw/implementation.rs +1517 -0
  8. data/ext/db-index/src/hnsw/kernels.rs +1228 -0
  9. data/ext/db-index/src/hnsw/metric.rs +244 -0
  10. data/ext/db-index/src/hnsw/scalar.rs +72 -0
  11. data/ext/db-index/src/hnsw/simple_store.rs +140 -0
  12. data/ext/db-index/src/hnsw/store.rs +472 -0
  13. data/ext/db-index/src/hnsw/vector.rs +105 -0
  14. data/ext/db-index/src/hnsw/vectors.rs +568 -0
  15. data/ext/db-index/src/hnsw.rs +42 -0
  16. data/ext/db-index/src/lib.rs +3 -0
  17. data/ext/gqlitedb/Cargo.toml +19 -8
  18. data/ext/gqlitedb/benches/common/pokec.rs +62 -2
  19. data/ext/gqlitedb/benches/pokec_divan.rs +66 -2
  20. data/ext/gqlitedb/benches/pokec_iai.rs +60 -3
  21. data/ext/gqlitedb/release.toml +2 -2
  22. data/ext/gqlitedb/src/aggregators/arithmetic.rs +3 -1
  23. data/ext/gqlitedb/src/aggregators/containers.rs +1 -1
  24. data/ext/gqlitedb/src/aggregators/stats.rs +21 -12
  25. data/ext/gqlitedb/src/capi.rs +20 -24
  26. data/ext/gqlitedb/src/compiler/expression_analyser.rs +28 -20
  27. data/ext/gqlitedb/src/compiler/variables_manager.rs +104 -38
  28. data/ext/gqlitedb/src/compiler.rs +506 -227
  29. data/ext/gqlitedb/src/connection.rs +151 -11
  30. data/ext/gqlitedb/src/consts.rs +1 -2
  31. data/ext/gqlitedb/src/error.rs +123 -61
  32. data/ext/gqlitedb/src/functions/common.rs +64 -0
  33. data/ext/gqlitedb/src/functions/containers.rs +2 -46
  34. data/ext/gqlitedb/src/functions/edge.rs +1 -1
  35. data/ext/gqlitedb/src/functions/math.rs +87 -15
  36. data/ext/gqlitedb/src/functions/node.rs +1 -1
  37. data/ext/gqlitedb/src/functions/path.rs +48 -11
  38. data/ext/gqlitedb/src/functions/scalar.rs +47 -4
  39. data/ext/gqlitedb/src/functions/string.rs +123 -1
  40. data/ext/gqlitedb/src/functions/value.rs +1 -1
  41. data/ext/gqlitedb/src/functions.rs +165 -28
  42. data/ext/gqlitedb/src/graph.rs +1 -9
  43. data/ext/gqlitedb/src/interpreter/evaluators.rs +968 -130
  44. data/ext/gqlitedb/src/interpreter/instructions.rs +39 -2
  45. data/ext/gqlitedb/src/lib.rs +5 -4
  46. data/ext/gqlitedb/src/parser/gql.pest +1 -1
  47. data/ext/gqlitedb/src/planner.rs +329 -0
  48. data/ext/gqlitedb/src/prelude.rs +3 -3
  49. data/ext/gqlitedb/src/store/pgrx.rs +1 -1
  50. data/ext/gqlitedb/src/store/postgres.rs +742 -16
  51. data/ext/gqlitedb/src/store/redb/hnsw_store.rs +702 -0
  52. data/ext/gqlitedb/src/store/redb/index.rs +274 -0
  53. data/ext/gqlitedb/src/store/redb.rs +1268 -113
  54. data/ext/gqlitedb/src/store/sqlbase/sqlmetadata.rs +28 -0
  55. data/ext/gqlitedb/src/store/sqlbase/sqlstore.rs +103 -0
  56. data/ext/gqlitedb/src/store/sqlbase.rs +146 -16
  57. data/ext/gqlitedb/src/store/sqlite.rs +576 -14
  58. data/ext/gqlitedb/src/store/vector_extract.rs +56 -0
  59. data/ext/gqlitedb/src/store.rs +140 -29
  60. data/ext/gqlitedb/src/tests/compiler.rs +207 -10
  61. data/ext/gqlitedb/src/tests/connection/postgres.rs +38 -3
  62. data/ext/gqlitedb/src/tests/connection/redb.rs +23 -0
  63. data/ext/gqlitedb/src/tests/connection/sqlite.rs +31 -0
  64. data/ext/gqlitedb/src/tests/connection.rs +61 -1
  65. data/ext/gqlitedb/src/tests/evaluators.rs +162 -7
  66. data/ext/gqlitedb/src/tests/parser.rs +54 -23
  67. data/ext/gqlitedb/src/tests/planner.rs +511 -0
  68. data/ext/gqlitedb/src/tests/store/postgres.rs +7 -0
  69. data/ext/gqlitedb/src/tests/store/redb.rs +8 -0
  70. data/ext/gqlitedb/src/tests/store/sqlite.rs +8 -0
  71. data/ext/gqlitedb/src/tests/store/vector_index/postgres.rs +182 -0
  72. data/ext/gqlitedb/src/tests/store/vector_index/redb.rs +386 -0
  73. data/ext/gqlitedb/src/tests/store/vector_index/sqlite.rs +166 -0
  74. data/ext/gqlitedb/src/tests/store/vector_index.rs +2313 -0
  75. data/ext/gqlitedb/src/tests/store.rs +78 -14
  76. data/ext/gqlitedb/src/tests/templates/ast.rs +92 -16
  77. data/ext/gqlitedb/src/tests/templates/programs.rs +14 -7
  78. data/ext/gqlitedb/src/tests.rs +15 -9
  79. data/ext/gqlitedb/src/utils.rs +6 -1
  80. data/ext/gqlitedb/src/value/compare.rs +61 -3
  81. data/ext/gqlitedb/src/value.rs +136 -7
  82. data/ext/gqlitedb/templates/sql/postgres/metadata_delete.sql +1 -0
  83. data/ext/gqlitedb/templates/sql/postgres/node_select.sql +1 -1
  84. data/ext/gqlitedb/templates/sql/sqlite/metadata_delete.sql +1 -0
  85. data/ext/gqliterb/src/lib.rs +53 -8
  86. data/ext/gqlparser/Cargo.toml +25 -0
  87. data/ext/gqlparser/README.MD +9 -0
  88. data/ext/gqlparser/benches/pokec_divan.rs +34 -0
  89. data/ext/gqlparser/src/common.rs +69 -0
  90. data/ext/gqlparser/src/gqls/ast.rs +69 -0
  91. data/ext/gqlparser/src/gqls/constraint.rs +79 -0
  92. data/ext/gqlparser/src/gqls/error.rs +22 -0
  93. data/ext/gqlparser/src/gqls/parser.rs +813 -0
  94. data/ext/gqlparser/src/gqls/prelude.rs +8 -0
  95. data/ext/gqlparser/src/gqls/properties.rs +115 -0
  96. data/ext/gqlparser/src/gqls/resolve.rs +207 -0
  97. data/ext/gqlparser/src/gqls.rs +265 -0
  98. data/ext/gqlparser/src/lib.rs +7 -0
  99. data/ext/gqlparser/src/oc/ast.rs +680 -0
  100. data/ext/gqlparser/src/oc/error.rs +172 -0
  101. data/ext/gqlparser/src/oc/lexer.rs +429 -0
  102. data/ext/gqlparser/src/oc/parser/tests.rs +2284 -0
  103. data/ext/gqlparser/src/oc/parser.rs +2005 -0
  104. data/ext/gqlparser/src/oc.rs +33 -0
  105. data/ext/gqlparser/src/prelude.rs +3 -0
  106. data/ext/graphcore/Cargo.toml +1 -0
  107. data/ext/graphcore/src/error.rs +26 -0
  108. data/ext/graphcore/src/graph.rs +177 -51
  109. data/ext/graphcore/src/lib.rs +4 -2
  110. data/ext/graphcore/src/open_cypher.rs +12 -0
  111. data/ext/graphcore/src/table.rs +50 -2
  112. data/ext/graphcore/src/timestamp.rs +127 -104
  113. data/ext/graphcore/src/value/tensor.rs +739 -0
  114. data/ext/graphcore/src/value/value_map.rs +1 -1
  115. data/ext/graphcore/src/value.rs +343 -19
  116. metadata +93 -28
  117. data/ext/gqlitedb/src/parser/ast.rs +0 -604
  118. data/ext/gqlitedb/src/parser/parser_impl.rs +0 -1213
  119. data/ext/gqlitedb/src/parser.rs +0 -4
@@ -0,0 +1,472 @@
1
+ use super::error::{Error, Result};
2
+
3
+ #[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
4
+ pub enum VectorEncoding
5
+ {
6
+ F32,
7
+ F16,
8
+ Qi8
9
+ {
10
+ scale: f32,
11
+ zero_point: i8,
12
+ },
13
+ }
14
+
15
+ #[allow(dead_code)]
16
+ pub fn encode_adjacency(level: u8, layers: &[Vec<u32>]) -> Vec<u8>
17
+ {
18
+ assert!(
19
+ level as usize + 1 == layers.len(),
20
+ "layers must have level+1 entries"
21
+ );
22
+ let mut out = Vec::with_capacity(1 + layers.iter().map(|l| 2 + 4 * l.len()).sum::<usize>());
23
+ out.push(level);
24
+ for layer in layers
25
+ {
26
+ let count: u16 = layer.len().try_into().expect("neighbour count exceeds u16");
27
+ out.extend_from_slice(&count.to_le_bytes());
28
+ for &n in layer
29
+ {
30
+ out.extend_from_slice(&n.to_le_bytes());
31
+ }
32
+ }
33
+ out
34
+ }
35
+
36
+ /// Decode a live adjacency record into its level and per-layer neighbour
37
+ /// lists. Passing empty bytes returns `Err(EmptyDecodedAdjency)`;
38
+ /// structurally truncated or padded records return `Err(TruncatedDecodedAdjency)`
39
+ /// or `Err(TrailingBytesDecodedAdjency)` respectively.
40
+ #[allow(dead_code)]
41
+ pub fn decode_adjacency_layers(bytes: &[u8]) -> Result<(u8, Vec<Vec<u32>>)>
42
+ {
43
+ if bytes.is_empty()
44
+ {
45
+ return Err(Error::EmptyDecodedAdjency);
46
+ }
47
+ let level = bytes[0];
48
+ let mut pos = 1usize;
49
+ let mut layers = Vec::with_capacity(level as usize + 1);
50
+ for _ in 0..=level
51
+ {
52
+ if pos + 2 > bytes.len()
53
+ {
54
+ return Err(Error::TruncatedDecodedAdjency);
55
+ }
56
+ let count = u16::from_le_bytes([bytes[pos], bytes[pos + 1]]) as usize;
57
+ pos += 2;
58
+ let mut layer = Vec::with_capacity(count);
59
+ for _ in 0..count
60
+ {
61
+ if pos + 4 > bytes.len()
62
+ {
63
+ return Err(Error::TruncatedDecodedAdjency);
64
+ }
65
+ layer.push(u32::from_le_bytes([
66
+ bytes[pos],
67
+ bytes[pos + 1],
68
+ bytes[pos + 2],
69
+ bytes[pos + 3],
70
+ ]));
71
+ pos += 4;
72
+ }
73
+ layers.push(layer);
74
+ }
75
+ if pos != bytes.len()
76
+ {
77
+ return Err(Error::TrailingBytesDecodedAdjency);
78
+ }
79
+ Ok((level, layers))
80
+ }
81
+
82
+ #[allow(dead_code)]
83
+ pub(crate) fn validate_qi8_scale(encoding: &VectorEncoding) -> Result<()>
84
+ {
85
+ if let VectorEncoding::Qi8 { scale, .. } = encoding
86
+ {
87
+ if *scale <= 0.0 || !scale.is_finite()
88
+ {
89
+ return Err(Error::InvalidQi8Scale { scale: *scale });
90
+ }
91
+ }
92
+ Ok(())
93
+ }
94
+
95
+ #[allow(dead_code)]
96
+ pub fn encode_vector(encoding: &VectorEncoding, floats: &[f32]) -> Result<Vec<u8>>
97
+ {
98
+ validate_qi8_scale(encoding)?;
99
+ Ok(match encoding
100
+ {
101
+ VectorEncoding::F32 => floats.iter().flat_map(|f| f.to_le_bytes()).collect(),
102
+ VectorEncoding::F16 => floats
103
+ .iter()
104
+ .flat_map(|f| half::f16::from_f32(*f).to_le_bytes())
105
+ .collect(),
106
+ VectorEncoding::Qi8 { scale, zero_point } => floats
107
+ .iter()
108
+ .map(|f| {
109
+ let q = (*f / scale) + (*zero_point as f32);
110
+ q.round().clamp(i8::MIN as f32, i8::MAX as f32) as i8 as u8
111
+ })
112
+ .collect(),
113
+ })
114
+ }
115
+
116
+ #[allow(dead_code)]
117
+ pub fn decode_vector(encoding: &VectorEncoding, bytes: &[u8], dim: usize) -> Result<Vec<f32>>
118
+ {
119
+ validate_qi8_scale(encoding)?;
120
+ let unit_size = match encoding
121
+ {
122
+ VectorEncoding::F32 => 4,
123
+ VectorEncoding::F16 => 2,
124
+ VectorEncoding::Qi8 { .. } => 1,
125
+ };
126
+ let expected = dim * unit_size;
127
+ if bytes.len() != expected
128
+ {
129
+ return Err(Error::DimensionMismatch {
130
+ expected,
131
+ actual: bytes.len(),
132
+ });
133
+ }
134
+ Ok(match encoding
135
+ {
136
+ VectorEncoding::F32 => bytes
137
+ .as_chunks::<4>()
138
+ .0
139
+ .iter()
140
+ .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
141
+ .collect(),
142
+ VectorEncoding::F16 => bytes
143
+ .as_chunks::<2>()
144
+ .0
145
+ .iter()
146
+ .map(|c| half::f16::from_le_bytes([c[0], c[1]]).to_f32())
147
+ .collect(),
148
+ VectorEncoding::Qi8 { scale, zero_point } => bytes
149
+ .iter()
150
+ .map(|&b| (b as i8 as f32 - *zero_point as f32) * scale)
151
+ .collect(),
152
+ })
153
+ }
154
+
155
+ #[allow(dead_code)]
156
+ const HNSW_META_SCHEMA_VERSION: u8 = 1;
157
+
158
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
159
+ #[allow(dead_code)]
160
+ struct VersionOnly
161
+ {
162
+ schema_version: u8,
163
+ }
164
+
165
+ #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
166
+ #[allow(dead_code)]
167
+ pub(crate) struct HnswMetaBlob
168
+ {
169
+ pub schema_version: u8,
170
+ pub entry_point: Option<u32>,
171
+ pub max_level: i32,
172
+ pub next_node_id: u32,
173
+ pub dim: u32,
174
+ pub m: u32,
175
+ pub ef_construction: u32,
176
+ pub ef_search: u32,
177
+ pub vector_encoding: VectorEncoding,
178
+ }
179
+
180
+ #[allow(dead_code)]
181
+ pub(crate) fn encode_meta(meta: &HnswMetaBlob) -> Vec<u8>
182
+ {
183
+ let mut data = Vec::<u8>::new();
184
+ ciborium::into_writer(meta, &mut data).unwrap();
185
+ data
186
+ }
187
+
188
+ #[allow(dead_code)]
189
+ pub(crate) fn decode_meta(bytes: &[u8]) -> Result<HnswMetaBlob>
190
+ {
191
+ let version_probe: VersionOnly =
192
+ ciborium::from_reader(bytes).map_err(|e| Error::MalformedMetadata(e.to_string()))?;
193
+ if version_probe.schema_version > HNSW_META_SCHEMA_VERSION
194
+ {
195
+ return Err(Error::SchemaVersionTooNew {
196
+ found: version_probe.schema_version,
197
+ });
198
+ }
199
+ if version_probe.schema_version < HNSW_META_SCHEMA_VERSION
200
+ {
201
+ return Err(Error::SchemaVersionUnsupported {
202
+ found: version_probe.schema_version,
203
+ });
204
+ }
205
+ let meta: HnswMetaBlob =
206
+ ciborium::from_reader(bytes).map_err(|e| Error::MalformedMetadata(e.to_string()))?;
207
+ validate_qi8_scale(&meta.vector_encoding)?;
208
+ Ok(meta)
209
+ }
210
+
211
+ #[cfg(test)]
212
+ mod testing
213
+ {
214
+ use super::*;
215
+ use crate::hnsw::id::NodeId;
216
+ use std::assert_matches;
217
+
218
+ #[test]
219
+ fn node_id_from_u32_roundtrips_through_as_u32()
220
+ {
221
+ assert_eq!(NodeId::from_u32(12345).as_u32(), 12345);
222
+ }
223
+
224
+ #[test]
225
+ fn adjacency_roundtrip_level0_m16()
226
+ {
227
+ let layer0: Vec<u32> = (0..16).collect();
228
+ let bytes = encode_adjacency(0, std::slice::from_ref(&layer0));
229
+ assert_eq!(bytes.len(), 1 + 2 + 16 * 4);
230
+ let (level, layers) = decode_adjacency_layers(&bytes).unwrap();
231
+ assert_eq!(level, 0);
232
+ assert_eq!(layers, vec![layer0]);
233
+ }
234
+
235
+ #[test]
236
+ fn adjacency_count_field_does_not_overflow_past_u8_neighbour_counts()
237
+ {
238
+ let layer0: Vec<u32> = (0..200).collect();
239
+ let bytes = encode_adjacency(0, std::slice::from_ref(&layer0));
240
+ let (_, layers) = decode_adjacency_layers(&bytes).unwrap();
241
+ assert_eq!(layers[0].len(), 200);
242
+ }
243
+
244
+ #[test]
245
+ #[should_panic(expected = "neighbour count exceeds u16")]
246
+ fn adjacency_encode_panics_rather_than_silently_truncating_above_u16_max()
247
+ {
248
+ let huge: Vec<u32> = (0..70_000).collect();
249
+ encode_adjacency(0, &[huge]);
250
+ }
251
+
252
+ #[test]
253
+ fn vector_f32_roundtrip_exact()
254
+ {
255
+ let v = vec![1.0f32, -2.5, 0.0, std::f32::consts::PI];
256
+ let bytes = encode_vector(&VectorEncoding::F32, &v).unwrap();
257
+ let back = decode_vector(&VectorEncoding::F32, &bytes, v.len()).unwrap();
258
+ assert_eq!(back, v);
259
+ }
260
+
261
+ #[test]
262
+ fn vector_f16_roundtrip_within_tolerance()
263
+ {
264
+ let v = vec![1.0f32, -2.5, 100.25, 0.001];
265
+ let bytes = encode_vector(&VectorEncoding::F16, &v).unwrap();
266
+ let back = decode_vector(&VectorEncoding::F16, &bytes, v.len()).unwrap();
267
+ for (a, b) in v.iter().zip(back.iter())
268
+ {
269
+ let rel_err = if *a != 0.0
270
+ {
271
+ (a - b).abs() / a.abs()
272
+ }
273
+ else
274
+ {
275
+ (a - b).abs()
276
+ };
277
+ assert!(rel_err < 0.001, "a={a} b={b} rel_err={rel_err}");
278
+ }
279
+ }
280
+
281
+ #[test]
282
+ fn vector_qi8_roundtrip_within_scale()
283
+ {
284
+ let scale = 0.05f32;
285
+ let zero_point = 0i8;
286
+ let v = vec![1.0f32, -1.0, 0.0, 2.5];
287
+ let enc = VectorEncoding::Qi8 { scale, zero_point };
288
+ let bytes = encode_vector(&enc, &v).unwrap();
289
+ let back = decode_vector(&enc, &bytes, v.len()).unwrap();
290
+ for (a, b) in v.iter().zip(back.iter())
291
+ {
292
+ assert!((a - b).abs() <= scale, "a={a} b={b}");
293
+ }
294
+ }
295
+
296
+ #[test]
297
+ fn vector_qi8_clamps_out_of_range_values_instead_of_wrapping()
298
+ {
299
+ let enc = VectorEncoding::Qi8 {
300
+ scale: 1.0,
301
+ zero_point: 0,
302
+ };
303
+ let bytes = encode_vector(&enc, &[1000.0, -1000.0]).unwrap();
304
+ assert_eq!(bytes[0] as i8, i8::MAX);
305
+ assert_eq!(bytes[1] as i8, i8::MIN);
306
+ }
307
+
308
+ #[test]
309
+ fn vector_decode_rejects_wrong_length()
310
+ {
311
+ let bytes = encode_vector(&VectorEncoding::F32, &[1.0, 2.0]).unwrap();
312
+ assert_matches!(
313
+ decode_vector(&VectorEncoding::F32, &bytes, 3),
314
+ Err(Error::DimensionMismatch {
315
+ expected: 12,
316
+ actual: 8
317
+ })
318
+ );
319
+ }
320
+
321
+ #[test]
322
+ fn vector_decode_rejects_extra_trailing_bytes_even_when_a_naive_take_would_hide_it()
323
+ {
324
+ let bytes = encode_vector(&VectorEncoding::F32, &[1.0, 2.0, 3.0, 4.0, 5.0]).unwrap();
325
+ assert_matches!(
326
+ decode_vector(&VectorEncoding::F32, &bytes, 4),
327
+ Err(Error::DimensionMismatch {
328
+ expected: 16,
329
+ actual: 20
330
+ })
331
+ );
332
+ }
333
+
334
+ #[test]
335
+ fn vector_qi8_encode_rejects_zero_scale()
336
+ {
337
+ let enc = VectorEncoding::Qi8 {
338
+ scale: 0.0,
339
+ zero_point: 0,
340
+ };
341
+ assert_matches!(
342
+ encode_vector(&enc, &[1.0, 2.0]),
343
+ Err(Error::InvalidQi8Scale { scale: 0.0 })
344
+ );
345
+ }
346
+
347
+ #[test]
348
+ fn vector_qi8_encode_rejects_negative_scale()
349
+ {
350
+ let enc = VectorEncoding::Qi8 {
351
+ scale: -1.0,
352
+ zero_point: 0,
353
+ };
354
+ assert_matches!(
355
+ encode_vector(&enc, &[1.0]),
356
+ Err(Error::InvalidQi8Scale { scale: -1.0 })
357
+ );
358
+ }
359
+
360
+ #[test]
361
+ fn vector_qi8_decode_also_rejects_invalid_scale_not_just_encode()
362
+ {
363
+ let enc = VectorEncoding::Qi8 {
364
+ scale: 0.0,
365
+ zero_point: 0,
366
+ };
367
+ assert_matches!(
368
+ decode_vector(&enc, &[0, 0], 2),
369
+ Err(Error::InvalidQi8Scale { scale: 0.0 })
370
+ );
371
+ }
372
+
373
+ #[test]
374
+ fn vector_encoding_sizes_are_expected()
375
+ {
376
+ let v = vec![1.0f32; 10];
377
+ assert_eq!(encode_vector(&VectorEncoding::F32, &v).unwrap().len(), 40);
378
+ assert_eq!(encode_vector(&VectorEncoding::F16, &v).unwrap().len(), 20);
379
+ assert_eq!(
380
+ encode_vector(
381
+ &VectorEncoding::Qi8 {
382
+ scale: 1.0,
383
+ zero_point: 0
384
+ },
385
+ &v
386
+ )
387
+ .unwrap()
388
+ .len(),
389
+ 10
390
+ );
391
+ }
392
+
393
+ fn sample_meta() -> HnswMetaBlob
394
+ {
395
+ HnswMetaBlob {
396
+ schema_version: HNSW_META_SCHEMA_VERSION,
397
+ entry_point: Some(7),
398
+ max_level: 2,
399
+ next_node_id: 103,
400
+ dim: 384,
401
+ m: 16,
402
+ ef_construction: 200,
403
+ ef_search: 50,
404
+ vector_encoding: VectorEncoding::F16,
405
+ }
406
+ }
407
+
408
+ #[test]
409
+ fn meta_roundtrip()
410
+ {
411
+ let meta = sample_meta();
412
+ let bytes = encode_meta(&meta);
413
+ let back = decode_meta(&bytes).unwrap();
414
+ assert_eq!(meta, back);
415
+ }
416
+
417
+ #[test]
418
+ fn meta_empty_graph_has_no_entry_point()
419
+ {
420
+ let mut meta = sample_meta();
421
+ meta.entry_point = None;
422
+ meta.max_level = -1;
423
+ let bytes = encode_meta(&meta);
424
+ let back = decode_meta(&bytes).unwrap();
425
+ assert_eq!(back.entry_point, None);
426
+ assert_eq!(back.max_level, -1);
427
+ }
428
+
429
+ #[test]
430
+ fn meta_rejects_newer_schema_version()
431
+ {
432
+ let mut meta = sample_meta();
433
+ meta.schema_version = HNSW_META_SCHEMA_VERSION + 1;
434
+ let bytes = encode_meta(&meta);
435
+
436
+ const HNSW_META_SCHEMA_VERSION_PLUS_1: u8 = HNSW_META_SCHEMA_VERSION + 1;
437
+
438
+ assert_matches!(
439
+ decode_meta(&bytes),
440
+ Err(Error::SchemaVersionTooNew {
441
+ found: HNSW_META_SCHEMA_VERSION_PLUS_1,
442
+ })
443
+ );
444
+ }
445
+
446
+ #[test]
447
+ fn meta_rejects_older_schema_version_no_downgrade_path()
448
+ {
449
+ let mut meta = sample_meta();
450
+ meta.schema_version = 0;
451
+ let bytes = encode_meta(&meta);
452
+ assert_matches!(
453
+ decode_meta(&bytes),
454
+ Err(Error::SchemaVersionUnsupported { found: 0 })
455
+ );
456
+ }
457
+
458
+ #[test]
459
+ fn meta_rejects_corrupted_qi8_scale_at_load_boundary()
460
+ {
461
+ let mut meta = sample_meta();
462
+ meta.vector_encoding = VectorEncoding::Qi8 {
463
+ scale: 0.0,
464
+ zero_point: 0,
465
+ };
466
+ let bytes = encode_meta(&meta);
467
+ assert_matches!(
468
+ decode_meta(&bytes),
469
+ Err(Error::InvalidQi8Scale { scale: 0.0 })
470
+ );
471
+ }
472
+ }
@@ -0,0 +1,105 @@
1
+ use super::scalar::Dtype;
2
+ use super::scalar::Scalar;
3
+ use std::marker::PhantomData;
4
+
5
+ pub trait VectorRef: Copy
6
+ {
7
+ fn len(self) -> usize;
8
+ fn is_empty(self) -> bool;
9
+ }
10
+
11
+ impl<T> VectorRef for &[T]
12
+ {
13
+ #[inline]
14
+ fn len(self) -> usize
15
+ {
16
+ self.len()
17
+ }
18
+ #[inline]
19
+ fn is_empty(self) -> bool
20
+ {
21
+ self.is_empty()
22
+ }
23
+ }
24
+
25
+ #[derive(Clone, Copy, Debug)]
26
+ pub struct Qi8Ref<'a>
27
+ {
28
+ pub data: &'a [i8],
29
+ pub scale: f32,
30
+ pub zero_point: i8,
31
+ }
32
+
33
+ impl VectorRef for Qi8Ref<'_>
34
+ {
35
+ #[inline]
36
+ fn len(self) -> usize
37
+ {
38
+ self.data.len()
39
+ }
40
+ #[inline]
41
+ fn is_empty(self) -> bool
42
+ {
43
+ self.data.is_empty()
44
+ }
45
+ }
46
+
47
+ pub trait VectorFamily: Copy + Clone + Send + Sync + 'static
48
+ {
49
+ type Ref<'a>: VectorRef
50
+ where
51
+ Self: 'a;
52
+
53
+ const DTYPE: Dtype;
54
+ }
55
+
56
+ #[derive(Clone, Copy, Debug, Default)]
57
+ pub struct Dense<S: Scalar>(PhantomData<S>);
58
+
59
+ impl<S: Scalar> VectorFamily for Dense<S>
60
+ {
61
+ type Ref<'a>
62
+ = &'a [S]
63
+ where
64
+ Self: 'a;
65
+
66
+ const DTYPE: Dtype = S::DTYPE;
67
+ }
68
+
69
+ #[derive(Clone, Copy, Debug, Default)]
70
+ pub struct Qi8;
71
+
72
+ impl VectorFamily for Qi8
73
+ {
74
+ type Ref<'a>
75
+ = Qi8Ref<'a>
76
+ where
77
+ Self: 'a;
78
+
79
+ const DTYPE: Dtype = Dtype::QI8;
80
+ }
81
+
82
+ pub trait VectorView<F: VectorFamily>: Clone + Send + Sync
83
+ {
84
+ fn view<'a>(&'a self) -> <F as VectorFamily>::Ref<'a>;
85
+ }
86
+
87
+ impl<S: Scalar> VectorView<Dense<S>> for &[S]
88
+ {
89
+ fn view(&self) -> &[S]
90
+ {
91
+ self
92
+ }
93
+ }
94
+
95
+ impl<'v> VectorView<Qi8> for Qi8Ref<'v>
96
+ {
97
+ fn view<'a>(&'a self) -> Qi8Ref<'a>
98
+ {
99
+ Qi8Ref {
100
+ data: self.data,
101
+ scale: self.scale,
102
+ zero_point: self.zero_point,
103
+ }
104
+ }
105
+ }