lancelot 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.
@@ -0,0 +1,454 @@
1
+ use magnus::{Error, Ruby, RHash, RArray, Symbol, TryConvert, function, method, RClass, Module, Object};
2
+ use std::cell::RefCell;
3
+ use std::sync::Arc;
4
+ use tokio::runtime::Runtime;
5
+ use lance::Dataset;
6
+ use lance::index::vector::VectorIndexParams;
7
+ use lance_index::{IndexType, DatasetIndexExt};
8
+ use lance_index::scalar::{InvertedIndexParams, FullTextSearchQuery};
9
+ use arrow_array::{RecordBatch, RecordBatchIterator, Float32Array};
10
+ use futures::stream::TryStreamExt;
11
+
12
+ use crate::schema::build_arrow_schema;
13
+ use crate::conversion::{build_record_batch, convert_batch_to_ruby};
14
+
15
+ #[magnus::wrap(class = "Lancelot::Dataset", free_immediately, size)]
16
+ pub struct LancelotDataset {
17
+ dataset: RefCell<Option<Dataset>>,
18
+ runtime: RefCell<Runtime>,
19
+ path: String,
20
+ }
21
+
22
+ impl LancelotDataset {
23
+ pub fn new(path: String) -> Result<Self, Error> {
24
+ let runtime = Runtime::new()
25
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
26
+
27
+ Ok(Self {
28
+ dataset: RefCell::new(None),
29
+ runtime: RefCell::new(runtime),
30
+ path,
31
+ })
32
+ }
33
+
34
+ pub fn path(&self) -> String {
35
+ self.path.clone()
36
+ }
37
+
38
+ pub fn create(&self, schema_hash: RHash) -> Result<(), Error> {
39
+ let schema = build_arrow_schema(schema_hash)?;
40
+
41
+ let empty_batch = RecordBatch::new_empty(Arc::new(schema.clone()));
42
+ let batches = vec![empty_batch];
43
+ let reader = RecordBatchIterator::new(
44
+ batches.into_iter().map(Ok),
45
+ Arc::new(schema)
46
+ );
47
+
48
+ let dataset = self.runtime.borrow_mut().block_on(async {
49
+ Dataset::write(
50
+ reader,
51
+ &self.path,
52
+ None,
53
+ )
54
+ .await
55
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))
56
+ })?;
57
+
58
+ self.dataset.replace(Some(dataset));
59
+ Ok(())
60
+ }
61
+
62
+ pub fn open(&self) -> Result<(), Error> {
63
+ let dataset = self.runtime.borrow_mut().block_on(async {
64
+ Dataset::open(&self.path)
65
+ .await
66
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))
67
+ })?;
68
+
69
+ self.dataset.replace(Some(dataset));
70
+ Ok(())
71
+ }
72
+
73
+ pub fn add_data(&self, data: RArray) -> Result<(), Error> {
74
+ let mut dataset = self.dataset.borrow_mut();
75
+ let dataset = dataset.as_mut()
76
+ .ok_or_else(|| Error::new(magnus::exception::runtime_error(), "Dataset not opened"))?;
77
+
78
+ // Check if data is empty
79
+ if data.len() == 0 {
80
+ return Ok(()); // Nothing to add
81
+ }
82
+
83
+ // Get the dataset's schema
84
+ let schema = self.runtime.borrow_mut().block_on(async {
85
+ dataset.schema()
86
+ });
87
+
88
+ // Convert Lance schema to Arrow schema
89
+ let arrow_schema = schema.into();
90
+
91
+ let batch = build_record_batch(data, &arrow_schema)?;
92
+
93
+ let batches = vec![batch];
94
+ let reader = RecordBatchIterator::new(
95
+ batches.into_iter().map(Ok),
96
+ Arc::new(arrow_schema)
97
+ );
98
+
99
+ self.runtime.borrow_mut().block_on(async move {
100
+ dataset.append(reader, None)
101
+ .await
102
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))
103
+ })?;
104
+
105
+ Ok(())
106
+ }
107
+
108
+ pub fn count_rows(&self) -> Result<i64, Error> {
109
+ let dataset = self.dataset.borrow();
110
+ let dataset = dataset.as_ref()
111
+ .ok_or_else(|| Error::new(magnus::exception::runtime_error(), "Dataset not opened"))?;
112
+
113
+ let count = self.runtime.borrow_mut().block_on(async {
114
+ dataset.count_rows(None)
115
+ .await
116
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))
117
+ })?;
118
+
119
+ Ok(count as i64)
120
+ }
121
+
122
+ pub fn schema(&self) -> Result<RHash, Error> {
123
+ let dataset = self.dataset.borrow();
124
+ let _dataset = dataset.as_ref()
125
+ .ok_or_else(|| Error::new(magnus::exception::runtime_error(), "Dataset not opened"))?;
126
+
127
+ let ruby = Ruby::get().unwrap();
128
+ let hash = ruby.hash_new();
129
+
130
+ // TODO: Read actual schema from Lance dataset once we figure out the 0.31 API
131
+ // For now, return a hardcoded schema that matches what we support
132
+ hash.aset(Symbol::new("text"), "string")?;
133
+ hash.aset(Symbol::new("score"), "float32")?;
134
+
135
+ Ok(hash)
136
+ }
137
+
138
+ pub fn scan_all(&self) -> Result<RArray, Error> {
139
+ let dataset = self.dataset.borrow();
140
+ let dataset = dataset.as_ref()
141
+ .ok_or_else(|| Error::new(magnus::exception::runtime_error(), "Dataset not opened"))?;
142
+
143
+ let batches: Vec<RecordBatch> = self.runtime.borrow_mut().block_on(async {
144
+ let scanner = dataset.scan();
145
+ let stream = scanner
146
+ .try_into_stream()
147
+ .await
148
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
149
+
150
+ stream
151
+ .try_collect::<Vec<_>>()
152
+ .await
153
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))
154
+ })?;
155
+
156
+ let ruby = Ruby::get().unwrap();
157
+ let result_array = ruby.ary_new();
158
+
159
+ for batch in batches {
160
+ let documents = convert_batch_to_ruby(&batch)?;
161
+ for doc in documents {
162
+ result_array.push(doc)?;
163
+ }
164
+ }
165
+
166
+ Ok(result_array)
167
+ }
168
+
169
+ pub fn scan_limit(&self, limit: i64) -> Result<RArray, Error> {
170
+ let dataset = self.dataset.borrow();
171
+ let dataset = dataset.as_ref()
172
+ .ok_or_else(|| Error::new(magnus::exception::runtime_error(), "Dataset not opened"))?;
173
+
174
+ let batches: Vec<RecordBatch> = self.runtime.borrow_mut().block_on(async {
175
+ let mut scanner = dataset.scan();
176
+ scanner.limit(Some(limit), None)
177
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
178
+
179
+ let stream = scanner
180
+ .try_into_stream()
181
+ .await
182
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
183
+
184
+ stream
185
+ .try_collect::<Vec<_>>()
186
+ .await
187
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))
188
+ })?;
189
+
190
+ let ruby = Ruby::get().unwrap();
191
+ let result_array = ruby.ary_new();
192
+
193
+ for batch in batches {
194
+ let documents = convert_batch_to_ruby(&batch)?;
195
+ for doc in documents {
196
+ result_array.push(doc)?;
197
+ }
198
+ }
199
+
200
+ Ok(result_array)
201
+ }
202
+
203
+ pub fn create_vector_index(&self, column: String) -> Result<(), Error> {
204
+ let mut dataset = self.dataset.borrow_mut();
205
+ let dataset = dataset.as_mut()
206
+ .ok_or_else(|| Error::new(magnus::exception::runtime_error(), "Dataset not opened"))?;
207
+
208
+ self.runtime.borrow_mut().block_on(async move {
209
+ // Get row count to determine optimal number of partitions
210
+ let num_rows = dataset.count_rows(None).await
211
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
212
+
213
+ // Use fewer partitions for small datasets
214
+ let num_partitions = if num_rows < 256 {
215
+ std::cmp::max(1, (num_rows / 4) as usize)
216
+ } else {
217
+ 256
218
+ };
219
+
220
+ // Create IVF_FLAT vector index parameters
221
+ let params = VectorIndexParams::ivf_flat(num_partitions, lance_linalg::distance::MetricType::L2);
222
+
223
+ dataset.create_index(
224
+ &[&column],
225
+ IndexType::Vector,
226
+ None,
227
+ &params,
228
+ true
229
+ )
230
+ .await
231
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))
232
+ })
233
+ }
234
+
235
+ pub fn vector_search(&self, column: String, query_vector: RArray, limit: i64) -> Result<RArray, Error> {
236
+ let dataset = self.dataset.borrow();
237
+ let dataset = dataset.as_ref()
238
+ .ok_or_else(|| Error::new(magnus::exception::runtime_error(), "Dataset not opened"))?;
239
+
240
+ // Convert Ruby array to Vec<f32>
241
+ let vector: Vec<f32> = query_vector
242
+ .into_iter()
243
+ .map(|v| f64::try_convert(v).map(|f| f as f32))
244
+ .collect::<Result<Vec<_>, _>>()?;
245
+
246
+ let batches: Vec<RecordBatch> = self.runtime.borrow_mut().block_on(async {
247
+ let mut scanner = dataset.scan();
248
+
249
+ // Use nearest for vector search
250
+ scanner.nearest(&column, &Float32Array::from(vector), limit as usize)
251
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
252
+
253
+ let stream = scanner
254
+ .try_into_stream()
255
+ .await
256
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
257
+
258
+ stream
259
+ .try_collect::<Vec<_>>()
260
+ .await
261
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))
262
+ })?;
263
+
264
+ let ruby = Ruby::get().unwrap();
265
+ let result_array = ruby.ary_new();
266
+
267
+ for batch in batches {
268
+ let documents = convert_batch_to_ruby(&batch)?;
269
+ for doc in documents {
270
+ result_array.push(doc)?;
271
+ }
272
+ }
273
+
274
+ Ok(result_array)
275
+ }
276
+
277
+ pub fn create_text_index(&self, column: String) -> Result<(), Error> {
278
+ let mut dataset = self.dataset.borrow_mut();
279
+ let dataset = dataset.as_mut()
280
+ .ok_or_else(|| Error::new(magnus::exception::runtime_error(), "Dataset not opened"))?;
281
+
282
+ self.runtime.borrow_mut().block_on(async move {
283
+ // Create inverted index for full-text search
284
+ let params = InvertedIndexParams::default();
285
+
286
+ dataset.create_index(
287
+ &[&column],
288
+ IndexType::Inverted,
289
+ None,
290
+ &params,
291
+ true
292
+ )
293
+ .await
294
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))
295
+ })
296
+ }
297
+
298
+ pub fn text_search(&self, column: String, query: String, limit: i64) -> Result<RArray, Error> {
299
+ let dataset = self.dataset.borrow();
300
+ let dataset = dataset.as_ref()
301
+ .ok_or_else(|| Error::new(magnus::exception::runtime_error(), "Dataset not opened"))?;
302
+
303
+ let batches: Vec<RecordBatch> = self.runtime.borrow_mut().block_on(async {
304
+ let mut scanner = dataset.scan();
305
+
306
+ // Use full-text search with inverted index
307
+ let fts_query = FullTextSearchQuery::new(query)
308
+ .with_column(column)
309
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
310
+
311
+ scanner.full_text_search(fts_query)
312
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
313
+
314
+ // Apply limit
315
+ scanner.limit(Some(limit), None)
316
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
317
+
318
+ let stream = scanner
319
+ .try_into_stream()
320
+ .await
321
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
322
+
323
+ stream
324
+ .try_collect::<Vec<_>>()
325
+ .await
326
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))
327
+ })?;
328
+
329
+ let ruby = Ruby::get().unwrap();
330
+ let result_array = ruby.ary_new();
331
+
332
+ for batch in batches {
333
+ let documents = convert_batch_to_ruby(&batch)?;
334
+ for doc in documents {
335
+ result_array.push(doc)?;
336
+ }
337
+ }
338
+
339
+ Ok(result_array)
340
+ }
341
+
342
+ pub fn multi_column_text_search(&self, columns: RArray, query: String, limit: i64) -> Result<RArray, Error> {
343
+ let dataset = self.dataset.borrow();
344
+ let dataset = dataset.as_ref()
345
+ .ok_or_else(|| Error::new(magnus::exception::runtime_error(), "Dataset not opened"))?;
346
+
347
+ // Convert Ruby array of columns to Vec<String>
348
+ let columns: Vec<String> = columns
349
+ .into_iter()
350
+ .map(|v| String::try_convert(v))
351
+ .collect::<Result<Vec<_>, _>>()?;
352
+
353
+ let batches: Vec<RecordBatch> = self.runtime.borrow_mut().block_on(async {
354
+ let mut scanner = dataset.scan();
355
+
356
+ // Create a full-text search query for multiple columns
357
+ let fts_query = FullTextSearchQuery::new(query)
358
+ .with_columns(&columns)
359
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
360
+
361
+ scanner.full_text_search(fts_query)
362
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
363
+
364
+ // Apply limit
365
+ scanner.limit(Some(limit), None)
366
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
367
+
368
+ let stream = scanner
369
+ .try_into_stream()
370
+ .await
371
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
372
+
373
+ stream
374
+ .try_collect::<Vec<_>>()
375
+ .await
376
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))
377
+ })?;
378
+
379
+ let ruby = Ruby::get().unwrap();
380
+ let result_array = ruby.ary_new();
381
+
382
+ for batch in batches {
383
+ let documents = convert_batch_to_ruby(&batch)?;
384
+ for doc in documents {
385
+ result_array.push(doc)?;
386
+ }
387
+ }
388
+
389
+ Ok(result_array)
390
+ }
391
+
392
+ pub fn filter_scan(&self, filter_expr: String, limit: Option<i64>) -> Result<RArray, Error> {
393
+ let dataset = self.dataset.borrow();
394
+ let dataset = dataset.as_ref()
395
+ .ok_or_else(|| Error::new(magnus::exception::runtime_error(), "Dataset not opened"))?;
396
+
397
+ let batches: Vec<RecordBatch> = self.runtime.borrow_mut().block_on(async {
398
+ let mut scanner = dataset.scan();
399
+
400
+ // Apply SQL-like filter
401
+ scanner.filter(&filter_expr)
402
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
403
+
404
+ // Apply limit if provided
405
+ if let Some(lim) = limit {
406
+ scanner.limit(Some(lim), None)
407
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
408
+ }
409
+
410
+ let stream = scanner
411
+ .try_into_stream()
412
+ .await
413
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))?;
414
+
415
+ stream
416
+ .try_collect::<Vec<_>>()
417
+ .await
418
+ .map_err(|e| Error::new(magnus::exception::runtime_error(), e.to_string()))
419
+ })?;
420
+
421
+ let ruby = Ruby::get().unwrap();
422
+ let result_array = ruby.ary_new();
423
+
424
+ for batch in batches {
425
+ let documents = convert_batch_to_ruby(&batch)?;
426
+ for doc in documents {
427
+ result_array.push(doc)?;
428
+ }
429
+ }
430
+
431
+ Ok(result_array)
432
+ }
433
+ }
434
+
435
+ impl LancelotDataset {
436
+ pub fn bind(class: &RClass) -> Result<(), Error> {
437
+ class.define_singleton_method("new", function!(LancelotDataset::new, 1))?;
438
+ class.define_method("path", method!(LancelotDataset::path, 0))?;
439
+ class.define_method("create", method!(LancelotDataset::create, 1))?;
440
+ class.define_method("open", method!(LancelotDataset::open, 0))?;
441
+ class.define_method("add_data", method!(LancelotDataset::add_data, 1))?;
442
+ class.define_method("count_rows", method!(LancelotDataset::count_rows, 0))?;
443
+ class.define_method("schema", method!(LancelotDataset::schema, 0))?;
444
+ class.define_method("scan_all", method!(LancelotDataset::scan_all, 0))?;
445
+ class.define_method("scan_limit", method!(LancelotDataset::scan_limit, 1))?;
446
+ class.define_method("create_vector_index", method!(LancelotDataset::create_vector_index, 1))?;
447
+ class.define_method("create_text_index", method!(LancelotDataset::create_text_index, 1))?;
448
+ class.define_method("_rust_vector_search", method!(LancelotDataset::vector_search, 3))?;
449
+ class.define_method("_rust_text_search", method!(LancelotDataset::text_search, 3))?;
450
+ class.define_method("_rust_multi_column_text_search", method!(LancelotDataset::multi_column_text_search, 3))?;
451
+ class.define_method("filter_scan", method!(LancelotDataset::filter_scan, 2))?;
452
+ Ok(())
453
+ }
454
+ }
@@ -0,0 +1,17 @@
1
+ use magnus::{define_module, Error, Ruby, Module};
2
+
3
+ mod dataset;
4
+ mod schema;
5
+ mod conversion;
6
+
7
+ use dataset::LancelotDataset;
8
+
9
+ #[magnus::init]
10
+ fn init(ruby: &Ruby) -> Result<(), Error> {
11
+ let module = define_module("Lancelot")?;
12
+
13
+ let dataset_class = module.define_class("Dataset", ruby.class_object())?;
14
+ LancelotDataset::bind(&dataset_class)?;
15
+
16
+ Ok(())
17
+ }
@@ -0,0 +1,50 @@
1
+ use magnus::{Error, RHash, Symbol, Value, TryConvert, r_hash::ForEach, value::ReprValue};
2
+ use arrow_schema::{DataType, Field, Schema as ArrowSchema};
3
+ use std::sync::Arc;
4
+
5
+ pub fn build_arrow_schema(schema_hash: RHash) -> Result<ArrowSchema, Error> {
6
+ let mut fields = Vec::new();
7
+
8
+ schema_hash.foreach(|key: Symbol, value: Value| {
9
+ let field_name = key.name()?.to_string();
10
+
11
+ let data_type = if value.is_kind_of(magnus::class::hash()) {
12
+ let hash = RHash::from_value(value)
13
+ .ok_or_else(|| Error::new(magnus::exception::arg_error(), "Invalid hash value"))?;
14
+ let type_str: String = hash.fetch(Symbol::new("type"))?;
15
+
16
+ match type_str.as_str() {
17
+ "vector" => {
18
+ let dimension: i32 = hash.fetch(Symbol::new("dimension"))?;
19
+ DataType::FixedSizeList(
20
+ Arc::new(Field::new("item", DataType::Float32, true)),
21
+ dimension,
22
+ )
23
+ }
24
+ _ => return Err(Error::new(
25
+ magnus::exception::arg_error(),
26
+ format!("Unknown field type: {}", type_str)
27
+ ))
28
+ }
29
+ } else {
30
+ let type_str = String::try_convert(value)?;
31
+ match type_str.as_str() {
32
+ "string" => DataType::Utf8,
33
+ "float32" => DataType::Float32,
34
+ "float64" => DataType::Float64,
35
+ "int32" => DataType::Int32,
36
+ "int64" => DataType::Int64,
37
+ "boolean" => DataType::Boolean,
38
+ _ => return Err(Error::new(
39
+ magnus::exception::arg_error(),
40
+ format!("Unknown field type: {}", type_str)
41
+ ))
42
+ }
43
+ };
44
+
45
+ fields.push(Field::new(field_name, data_type, true));
46
+ Ok(ForEach::Continue)
47
+ })?;
48
+
49
+ Ok(ArrowSchema::new(fields))
50
+ }
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lancelot
4
+ class Dataset
5
+ class << self
6
+ def create(path, schema:)
7
+ dataset = new(path)
8
+ dataset.create(normalize_schema(schema))
9
+ dataset
10
+ end
11
+
12
+ def open(path)
13
+ dataset = new(path)
14
+ dataset.open
15
+ dataset
16
+ end
17
+
18
+ private
19
+
20
+ def normalize_schema(schema)
21
+ schema.transform_values do |type|
22
+ case type
23
+ when Hash
24
+ type
25
+ when :string, "string"
26
+ "string"
27
+ when :float, :float32, "float", "float32"
28
+ "float32"
29
+ when :float64, "float64"
30
+ "float64"
31
+ when :int, :int32, "int", "int32"
32
+ "int32"
33
+ when :int64, "int64"
34
+ "int64"
35
+ when :bool, :boolean, "bool", "boolean"
36
+ "boolean"
37
+ else
38
+ raise ArgumentError, "Unknown type: #{type}"
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def add_documents(documents)
45
+ add_data(documents.map { |doc| normalize_document(doc) })
46
+ end
47
+
48
+ def <<(document)
49
+ add_documents([document])
50
+ self
51
+ end
52
+
53
+ def size
54
+ count_rows
55
+ end
56
+
57
+ alias_method :count, :size
58
+ alias_method :length, :size
59
+
60
+ def all
61
+ scan_all
62
+ end
63
+
64
+ def first(n = nil)
65
+ if n.nil?
66
+ scan_limit(1).first
67
+ else
68
+ scan_limit(n)
69
+ end
70
+ end
71
+
72
+ def each(&block)
73
+ return enum_for(:each) unless block_given?
74
+ scan_all.each(&block)
75
+ end
76
+
77
+ include Enumerable
78
+
79
+ def vector_search(query_vector, column: "vector", limit: 10)
80
+ unless query_vector.is_a?(Array)
81
+ raise ArgumentError, "Query vector must be an array of numbers"
82
+ end
83
+
84
+ _rust_vector_search(column.to_s, query_vector, limit)
85
+ end
86
+
87
+ def nearest_neighbors(vector, k: 10, column: "vector")
88
+ vector_search(vector, column: column, limit: k)
89
+ end
90
+
91
+ def text_search(query, column: nil, columns: nil, limit: 10)
92
+ unless query.is_a?(String)
93
+ raise ArgumentError, "Query must be a string"
94
+ end
95
+
96
+ if column && columns
97
+ raise ArgumentError, "Cannot specify both column and columns"
98
+ elsif columns
99
+ # Multi-column search
100
+ columns = Array(columns).map(&:to_s)
101
+ _rust_multi_column_text_search(columns, query, limit)
102
+ else
103
+ # Single column search (default to "text" if not specified)
104
+ column ||= "text"
105
+ _rust_text_search(column.to_s, query, limit)
106
+ end
107
+ end
108
+
109
+ def where(filter_expression, limit: nil)
110
+ filter_scan(filter_expression.to_s, limit)
111
+ end
112
+
113
+ private
114
+
115
+ def normalize_document(doc)
116
+ doc.transform_keys(&:to_sym)
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lancelot
4
+ VERSION = "0.1.0"
5
+ end
data/lib/lancelot.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lancelot/version"
4
+ require_relative "lancelot/lancelot"
5
+ require_relative "lancelot/dataset"
6
+
7
+ module Lancelot
8
+ class Error < StandardError; end
9
+ end
data/sig/lancelot.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Lancelot
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end