zvec-ruby 0.1.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.
data/test/test_doc.rb ADDED
@@ -0,0 +1,271 @@
1
+ require_relative "test_helper"
2
+
3
+ class TestDoc < Minitest::Test
4
+ def setup
5
+ @schema = Zvec::Schema.new("test") do
6
+ string "title"
7
+ int32 "count"
8
+ float "score"
9
+ double "precise"
10
+ bool "active"
11
+ vector "embedding", dimension: 4
12
+ end
13
+ end
14
+
15
+ # --- Initialization ---
16
+
17
+ def test_initialize_default
18
+ doc = Zvec::Doc.new
19
+ assert_equal "", doc.pk
20
+ assert_equal 0.0, doc.score
21
+ assert doc.empty?
22
+ end
23
+
24
+ def test_initialize_with_pk
25
+ doc = Zvec::Doc.new(pk: "abc")
26
+ assert_equal "abc", doc.pk
27
+ end
28
+
29
+ def test_initialize_with_integer_pk
30
+ doc = Zvec::Doc.new(pk: 42)
31
+ assert_equal "42", doc.pk
32
+ end
33
+
34
+ def test_initialize_with_fields_and_schema
35
+ doc = Zvec::Doc.new(pk: "d1", fields: { "title" => "Hello" }, schema: @schema)
36
+ assert_equal "Hello", doc["title"]
37
+ end
38
+
39
+ # --- pk ---
40
+
41
+ def test_pk_setter
42
+ doc = Zvec::Doc.new
43
+ doc.pk = "new_pk"
44
+ assert_equal "new_pk", doc.pk
45
+ end
46
+
47
+ def test_pk_setter_converts_to_string
48
+ doc = Zvec::Doc.new
49
+ doc.pk = 123
50
+ assert_equal "123", doc.pk
51
+ end
52
+
53
+ # --- String fields ---
54
+
55
+ def test_set_and_get_string
56
+ doc = Zvec::Doc.new(schema: @schema)
57
+ doc["title"] = "Hello World"
58
+ assert_equal "Hello World", doc["title"]
59
+ end
60
+
61
+ def test_set_string_without_schema_auto_detects
62
+ doc = Zvec::Doc.new
63
+ doc["name"] = "auto"
64
+ assert_equal "auto", doc["name"]
65
+ end
66
+
67
+ # --- Integer fields ---
68
+
69
+ def test_set_and_get_int32
70
+ doc = Zvec::Doc.new(schema: @schema)
71
+ doc["count"] = 42
72
+ assert_equal 42, doc["count"]
73
+ end
74
+
75
+ def test_set_integer_without_schema_uses_int64
76
+ doc = Zvec::Doc.new
77
+ doc["num"] = 99
78
+ assert_equal 99, doc["num"]
79
+ end
80
+
81
+ # --- Float fields ---
82
+
83
+ def test_set_and_get_float
84
+ doc = Zvec::Doc.new(schema: @schema)
85
+ doc["score"] = 3.14
86
+ val = doc["score"]
87
+ assert_in_delta 3.14, val, 0.01
88
+ end
89
+
90
+ def test_set_and_get_double
91
+ doc = Zvec::Doc.new(schema: @schema)
92
+ doc["precise"] = 2.718281828
93
+ val = doc["precise"]
94
+ assert_in_delta 2.718281828, val, 0.0001
95
+ end
96
+
97
+ def test_set_float_without_schema_auto_detects
98
+ doc = Zvec::Doc.new
99
+ doc["val"] = 1.5
100
+ assert_in_delta 1.5, doc["val"], 0.01
101
+ end
102
+
103
+ # --- Bool fields ---
104
+
105
+ def test_set_and_get_bool_true
106
+ doc = Zvec::Doc.new(schema: @schema)
107
+ doc["active"] = true
108
+ assert_equal true, doc["active"]
109
+ end
110
+
111
+ def test_set_and_get_bool_false
112
+ doc = Zvec::Doc.new(schema: @schema)
113
+ doc["active"] = false
114
+ assert_equal false, doc["active"]
115
+ end
116
+
117
+ def test_set_bool_without_schema_auto_detects
118
+ doc = Zvec::Doc.new
119
+ doc["flag"] = true
120
+ assert_equal true, doc["flag"]
121
+ end
122
+
123
+ # --- Vector fields ---
124
+
125
+ def test_set_and_get_float_vector
126
+ doc = Zvec::Doc.new(schema: @schema)
127
+ vec = [1.0, 2.0, 3.0, 4.0]
128
+ doc["embedding"] = vec
129
+ result = doc["embedding"]
130
+ assert_kind_of Array, result
131
+ assert_equal 4, result.size
132
+ assert_in_delta 1.0, result[0], 0.001
133
+ assert_in_delta 4.0, result[3], 0.001
134
+ end
135
+
136
+ def test_set_array_of_integers_as_vector
137
+ doc = Zvec::Doc.new
138
+ doc["vec"] = [1, 2, 3]
139
+ result = doc["vec"]
140
+ assert_kind_of Array, result
141
+ assert_in_delta 1.0, result[0], 0.01
142
+ end
143
+
144
+ # --- String array fields ---
145
+
146
+ def test_set_and_get_string_array
147
+ doc = Zvec::Doc.new
148
+ doc["tags"] = ["ruby", "vector", "db"]
149
+ result = doc["tags"]
150
+ assert_kind_of Array, result
151
+ assert_equal ["ruby", "vector", "db"], result
152
+ end
153
+
154
+ # --- Null handling ---
155
+
156
+ def test_set_nil_value
157
+ doc = Zvec::Doc.new
158
+ doc["title"] = "Hello"
159
+ doc["title"] = nil
160
+ assert_nil doc["title"]
161
+ end
162
+
163
+ # --- Missing fields ---
164
+
165
+ def test_get_missing_field_returns_nil
166
+ doc = Zvec::Doc.new
167
+ assert_nil doc["nonexistent"]
168
+ end
169
+
170
+ # --- field_names ---
171
+
172
+ def test_field_names
173
+ doc = Zvec::Doc.new(schema: @schema)
174
+ doc["title"] = "Hello"
175
+ doc["count"] = 1
176
+ names = doc.field_names
177
+ assert_includes names, "title"
178
+ assert_includes names, "count"
179
+ end
180
+
181
+ # --- empty? ---
182
+
183
+ def test_empty_initially
184
+ doc = Zvec::Doc.new
185
+ assert doc.empty?
186
+ end
187
+
188
+ def test_not_empty_after_set
189
+ doc = Zvec::Doc.new
190
+ doc["x"] = "y"
191
+ refute doc.empty?
192
+ end
193
+
194
+ # --- to_h ---
195
+
196
+ def test_to_h
197
+ doc = Zvec::Doc.new(pk: "pk1")
198
+ doc["title"] = "Hi"
199
+ h = doc.to_h
200
+ assert_kind_of Hash, h
201
+ assert_equal "pk1", h["pk"]
202
+ assert_equal 0.0, h["score"]
203
+ assert_equal "Hi", h["title"]
204
+ end
205
+
206
+ def test_to_h_includes_all_fields
207
+ doc = Zvec::Doc.new(pk: "x")
208
+ doc["a"] = "one"
209
+ doc["b"] = "two"
210
+ h = doc.to_h
211
+ assert_equal "one", h["a"]
212
+ assert_equal "two", h["b"]
213
+ end
214
+
215
+ # --- to_s ---
216
+
217
+ def test_to_s_returns_string
218
+ doc = Zvec::Doc.new(pk: "test")
219
+ assert_kind_of String, doc.to_s
220
+ end
221
+
222
+ # --- bracket accessor ---
223
+
224
+ def test_bracket_set_and_get
225
+ doc = Zvec::Doc.new
226
+ doc["key"] = "value"
227
+ assert_equal "value", doc["key"]
228
+ end
229
+
230
+ # --- from_ext ---
231
+
232
+ def test_from_ext
233
+ ext_doc = Zvec::Ext::Doc.new
234
+ ext_doc.pk = "wrapped"
235
+ doc = Zvec::Doc.from_ext(ext_doc, schema: @schema)
236
+ assert_equal "wrapped", doc.pk
237
+ assert_same ext_doc, doc.ext_doc
238
+ end
239
+
240
+ def test_from_ext_without_schema
241
+ ext_doc = Zvec::Ext::Doc.new
242
+ doc = Zvec::Doc.from_ext(ext_doc)
243
+ assert_equal ext_doc, doc.ext_doc
244
+ end
245
+
246
+ # --- symbol keys ---
247
+
248
+ def test_symbol_field_name_set
249
+ doc = Zvec::Doc.new
250
+ doc.set(:sym_field, "value")
251
+ assert_equal "value", doc["sym_field"]
252
+ end
253
+
254
+ def test_symbol_field_name_get
255
+ doc = Zvec::Doc.new
256
+ doc["sym_field"] = "value"
257
+ assert_equal "value", doc.get(:sym_field)
258
+ end
259
+
260
+ # --- empty array ---
261
+
262
+ def test_set_empty_array_as_vector
263
+ doc = Zvec::Doc.new
264
+ doc["empty"] = []
265
+ # Empty arrays treated as float vectors (empty)
266
+ result = doc["empty"]
267
+ # Depending on implementation, may be nil or empty array
268
+ # Just verify it doesn't crash
269
+ assert(result.nil? || result == [])
270
+ end
271
+ end
@@ -0,0 +1,313 @@
1
+ require_relative "test_helper"
2
+
3
+ # Tests for low-level C++ extension bindings.
4
+ # Skipped when native ext is not available.
5
+ class TestExtBindings < Minitest::Test
6
+ def setup
7
+ skip "Native extension not available" unless NATIVE_EXT_AVAILABLE
8
+ end
9
+
10
+ # --- Enums ---
11
+
12
+ def test_data_type_enum
13
+ assert_respond_to Zvec::Ext::DataType::STRING, :to_s
14
+ refute_equal Zvec::Ext::DataType::STRING, Zvec::Ext::DataType::INT32
15
+ end
16
+
17
+ def test_index_type_enum
18
+ refute_nil Zvec::Ext::IndexType::HNSW
19
+ refute_nil Zvec::Ext::IndexType::FLAT
20
+ refute_nil Zvec::Ext::IndexType::IVF
21
+ refute_nil Zvec::Ext::IndexType::INVERT
22
+ end
23
+
24
+ def test_metric_type_enum
25
+ refute_nil Zvec::Ext::MetricType::L2
26
+ refute_nil Zvec::Ext::MetricType::IP
27
+ refute_nil Zvec::Ext::MetricType::COSINE
28
+ end
29
+
30
+ def test_quantize_type_enum
31
+ refute_nil Zvec::Ext::QuantizeType::FP16
32
+ refute_nil Zvec::Ext::QuantizeType::INT8
33
+ refute_nil Zvec::Ext::QuantizeType::INT4
34
+ end
35
+
36
+ # --- CollectionOptions ---
37
+
38
+ def test_collection_options_defaults
39
+ opts = Zvec::Ext::CollectionOptions.new
40
+ refute opts.read_only?
41
+ assert opts.enable_mmap?
42
+ end
43
+
44
+ def test_collection_options_setters
45
+ opts = Zvec::Ext::CollectionOptions.new
46
+ opts.read_only = true
47
+ opts.enable_mmap = false
48
+ opts.max_buffer_size = 1024
49
+ assert opts.read_only?
50
+ refute opts.enable_mmap?
51
+ assert_equal 1024, opts.max_buffer_size
52
+ end
53
+
54
+ # --- FieldSchema ---
55
+
56
+ def test_field_schema_basic
57
+ fs = Zvec::Ext::FieldSchema.new("title", Zvec::Ext::DataType::STRING)
58
+ assert_equal "title", fs.name
59
+ assert_equal Zvec::Ext::DataType::STRING, fs.data_type
60
+ refute fs.nullable?
61
+ assert_equal 0, fs.dimension
62
+ refute fs.vector_field?
63
+ end
64
+
65
+ def test_field_schema_vector
66
+ fs = Zvec::Ext::FieldSchema.new("vec", Zvec::Ext::DataType::VECTOR_FP32)
67
+ fs.dimension = 128
68
+ assert fs.vector_field?
69
+ assert_equal 128, fs.dimension
70
+ end
71
+
72
+ def test_field_schema_nullable
73
+ fs = Zvec::Ext::FieldSchema.new("opt", Zvec::Ext::DataType::STRING)
74
+ fs.nullable = true
75
+ assert fs.nullable?
76
+ end
77
+
78
+ def test_field_schema_to_s
79
+ fs = Zvec::Ext::FieldSchema.new("x", Zvec::Ext::DataType::INT32)
80
+ assert_kind_of String, fs.to_s
81
+ end
82
+
83
+ # --- CollectionSchema ---
84
+
85
+ def test_collection_schema_basic
86
+ cs = Zvec::Ext::CollectionSchema.new("test")
87
+ assert_equal "test", cs.name
88
+ assert_equal [], cs.field_names
89
+ end
90
+
91
+ def test_collection_schema_add_field
92
+ cs = Zvec::Ext::CollectionSchema.new("test")
93
+ fs = Zvec::Ext::FieldSchema.new("title", Zvec::Ext::DataType::STRING)
94
+ cs.add_field(fs)
95
+ assert cs.has_field?("title")
96
+ assert_includes cs.field_names, "title"
97
+ end
98
+
99
+ def test_collection_schema_vector_fields
100
+ cs = Zvec::Ext::CollectionSchema.new("test")
101
+ cs.add_field(Zvec::Ext::FieldSchema.new("title", Zvec::Ext::DataType::STRING))
102
+ vf = Zvec::Ext::FieldSchema.new("vec", Zvec::Ext::DataType::VECTOR_FP32)
103
+ vf.dimension = 4
104
+ cs.add_field(vf)
105
+
106
+ vec_fields = cs.vector_fields
107
+ assert_equal 1, vec_fields.size
108
+ assert_equal "vec", vec_fields.first.name
109
+ end
110
+
111
+ def test_collection_schema_forward_fields
112
+ cs = Zvec::Ext::CollectionSchema.new("test")
113
+ cs.add_field(Zvec::Ext::FieldSchema.new("title", Zvec::Ext::DataType::STRING))
114
+ vf = Zvec::Ext::FieldSchema.new("vec", Zvec::Ext::DataType::VECTOR_FP32)
115
+ vf.dimension = 4
116
+ cs.add_field(vf)
117
+
118
+ fwd = cs.forward_fields
119
+ assert_equal 1, fwd.size
120
+ assert_equal "title", fwd.first.name
121
+ end
122
+
123
+ # --- Doc ---
124
+
125
+ def test_doc_basic
126
+ d = Zvec::Ext::Doc.new
127
+ assert_equal "", d.pk
128
+ assert_equal 0.0, d.score
129
+ assert d.empty?
130
+ end
131
+
132
+ def test_doc_pk
133
+ d = Zvec::Ext::Doc.new
134
+ d.pk = "hello"
135
+ assert_equal "hello", d.pk
136
+ end
137
+
138
+ def test_doc_score
139
+ d = Zvec::Ext::Doc.new
140
+ d.score = 0.95
141
+ assert_in_delta 0.95, d.score, 0.001
142
+ end
143
+
144
+ def test_doc_string_field
145
+ d = Zvec::Ext::Doc.new
146
+ d.set_string("name", "Alice")
147
+ assert d.has?("name")
148
+ assert d.has_value?("name")
149
+ assert_equal "Alice", d.get_string("name")
150
+ end
151
+
152
+ def test_doc_bool_field
153
+ d = Zvec::Ext::Doc.new
154
+ d.set_bool("active", true)
155
+ assert_equal true, d.get_bool("active")
156
+ d.set_bool("active", false)
157
+ assert_equal false, d.get_bool("active")
158
+ end
159
+
160
+ def test_doc_int32_field
161
+ d = Zvec::Ext::Doc.new
162
+ d.set_int32("count", 42)
163
+ assert_equal 42, d.get_int32("count")
164
+ end
165
+
166
+ def test_doc_int64_field
167
+ d = Zvec::Ext::Doc.new
168
+ d.set_int64("big", 9_999_999_999)
169
+ assert_equal 9_999_999_999, d.get_int64("big")
170
+ end
171
+
172
+ def test_doc_float_field
173
+ d = Zvec::Ext::Doc.new
174
+ d.set_float("val", 3.14)
175
+ assert_in_delta 3.14, d.get_float("val"), 0.01
176
+ end
177
+
178
+ def test_doc_double_field
179
+ d = Zvec::Ext::Doc.new
180
+ d.set_double("precise", 2.718281828)
181
+ assert_in_delta 2.718281828, d.get_double("precise"), 0.0001
182
+ end
183
+
184
+ def test_doc_float_vector_field
185
+ d = Zvec::Ext::Doc.new
186
+ d.set_float_vector("vec", [1.0, 2.0, 3.0])
187
+ result = d.get_float_vector("vec")
188
+ assert_kind_of Array, result
189
+ assert_equal 3, result.size
190
+ assert_in_delta 1.0, result[0], 0.001
191
+ end
192
+
193
+ def test_doc_string_array_field
194
+ d = Zvec::Ext::Doc.new
195
+ d.set_string_array("tags", ["a", "b", "c"])
196
+ result = d.get_string_array("tags")
197
+ assert_equal ["a", "b", "c"], result
198
+ end
199
+
200
+ def test_doc_null_field
201
+ d = Zvec::Ext::Doc.new
202
+ d.set_null("empty")
203
+ assert d.has?("empty")
204
+ refute d.has_value?("empty")
205
+ end
206
+
207
+ def test_doc_field_names
208
+ d = Zvec::Ext::Doc.new
209
+ d.set_string("a", "x")
210
+ d.set_int32("b", 1)
211
+ names = d.field_names
212
+ assert_includes names, "a"
213
+ assert_includes names, "b"
214
+ end
215
+
216
+ def test_doc_get_missing_returns_nil
217
+ d = Zvec::Ext::Doc.new
218
+ assert_nil d.get_string("nope")
219
+ assert_nil d.get_int32("nope")
220
+ assert_nil d.get_float("nope")
221
+ assert_nil d.get_float_vector("nope")
222
+ end
223
+
224
+ def test_doc_type_mismatch_returns_nil
225
+ d = Zvec::Ext::Doc.new
226
+ d.set_string("x", "hello")
227
+ assert_nil d.get_int32("x")
228
+ assert_nil d.get_float("x")
229
+ end
230
+
231
+ def test_doc_to_s
232
+ d = Zvec::Ext::Doc.new
233
+ d.pk = "test"
234
+ assert_kind_of String, d.to_s
235
+ assert_includes d.to_s, "test"
236
+ end
237
+
238
+ # --- VectorQuery ---
239
+
240
+ def test_vector_query_basic
241
+ q = Zvec::Ext::VectorQuery.new
242
+ q.field_name = "vec"
243
+ q.topk = 5
244
+ assert_equal "vec", q.field_name
245
+ assert_equal 5, q.topk
246
+ end
247
+
248
+ def test_vector_query_filter
249
+ q = Zvec::Ext::VectorQuery.new
250
+ q.filter = "x > 1"
251
+ assert_equal "x > 1", q.filter
252
+ end
253
+
254
+ def test_vector_query_include_vector
255
+ q = Zvec::Ext::VectorQuery.new
256
+ q.include_vector = true
257
+ assert q.include_vector?
258
+ end
259
+
260
+ # --- Index params ---
261
+
262
+ def test_hnsw_index_params
263
+ p = Zvec::Ext::HnswIndexParams.new(Zvec::Ext::MetricType::COSINE)
264
+ assert_equal 16, p.m
265
+ assert_equal 200, p.ef_construction
266
+ assert_equal Zvec::Ext::MetricType::COSINE, p.metric_type
267
+ end
268
+
269
+ def test_hnsw_index_params_custom
270
+ p = Zvec::Ext::HnswIndexParams.new(Zvec::Ext::MetricType::L2, 32, 400)
271
+ assert_equal 32, p.m
272
+ assert_equal 400, p.ef_construction
273
+ assert_equal Zvec::Ext::MetricType::L2, p.metric_type
274
+ end
275
+
276
+ def test_flat_index_params
277
+ p = Zvec::Ext::FlatIndexParams.new(Zvec::Ext::MetricType::IP)
278
+ assert_equal Zvec::Ext::MetricType::IP, p.metric_type
279
+ end
280
+
281
+ def test_ivf_index_params
282
+ p = Zvec::Ext::IVFIndexParams.new(Zvec::Ext::MetricType::L2, 512, 20)
283
+ assert_equal 512, p.n_list
284
+ assert_equal 20, p.n_iters
285
+ end
286
+
287
+ def test_invert_index_params
288
+ p = Zvec::Ext::InvertIndexParams.new
289
+ assert_kind_of Zvec::Ext::InvertIndexParams, p
290
+ end
291
+
292
+ # --- Query params ---
293
+
294
+ def test_hnsw_query_params
295
+ p = Zvec::Ext::HnswQueryParams.new(300)
296
+ assert_equal 300, p.ef
297
+ end
298
+
299
+ def test_hnsw_query_params_default
300
+ p = Zvec::Ext::HnswQueryParams.new
301
+ assert_equal 200, p.ef
302
+ end
303
+
304
+ def test_ivf_query_params
305
+ p = Zvec::Ext::IVFQueryParams.new(20)
306
+ assert_equal 20, p.nprobe
307
+ end
308
+
309
+ def test_flat_query_params
310
+ p = Zvec::Ext::FlatQueryParams.new
311
+ assert_kind_of Zvec::Ext::FlatQueryParams, p
312
+ end
313
+ end