completr 0.2.1__tar.gz → 0.2.3__tar.gz
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.
- {completr-0.2.1 → completr-0.2.3}/Cargo.lock +3 -3
- {completr-0.2.1 → completr-0.2.3}/Cargo.toml +1 -1
- {completr-0.2.1 → completr-0.2.3}/PKG-INFO +1 -1
- {completr-0.2.1 → completr-0.2.3}/crates/completr/Cargo.toml +1 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/client.rs +6 -1
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/database.rs +11 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/engine.rs +10 -9
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/search.rs +11 -3
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/store.rs +153 -2
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/python/completr/__init__.pyi +1 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/python/completr/completr.pyi +8 -1
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/src/lib.rs +13 -7
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/tests/test_database.py +13 -0
- {completr-0.2.1 → completr-0.2.3}/LICENSE +0 -0
- {completr-0.2.1 → completr-0.2.3}/README.md +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/README.md +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/blocked.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/codec.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/dict.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/document.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/elias_fano.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/fuzzy.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/highlight.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/hybrid.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/inbox.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/index.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/lib.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/postings.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/segment/merge.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/segment/titles.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/segment/words.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/segment.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/staged.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/text.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/trie.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr/src/vectors.rs +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/Cargo.toml +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/examples/layers.py +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/examples/live_updates.py +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/examples/quickstart.py +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/python/completr/__init__.py +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/python/completr/_async.py +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/python/completr/_async.pyi +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/python/completr/_errors.py +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/python/completr/_types.py +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/python/completr/py.typed +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/tests/test_allocators.py +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/tests/test_api.py +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/tests/test_client.py +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/tests/test_completr.py +0 -0
- {completr-0.2.1 → completr-0.2.3}/crates/completr-py/tests/test_vectors.py +0 -0
- {completr-0.2.1 → completr-0.2.3}/pyproject.toml +0 -0
|
@@ -735,7 +735,7 @@ dependencies = [
|
|
|
735
735
|
|
|
736
736
|
[[package]]
|
|
737
737
|
name = "completr"
|
|
738
|
-
version = "0.2.
|
|
738
|
+
version = "0.2.3"
|
|
739
739
|
dependencies = [
|
|
740
740
|
"arc-swap",
|
|
741
741
|
"async-trait",
|
|
@@ -765,7 +765,7 @@ dependencies = [
|
|
|
765
765
|
|
|
766
766
|
[[package]]
|
|
767
767
|
name = "completr-cli"
|
|
768
|
-
version = "0.2.
|
|
768
|
+
version = "0.2.3"
|
|
769
769
|
dependencies = [
|
|
770
770
|
"clap",
|
|
771
771
|
"completr",
|
|
@@ -776,7 +776,7 @@ dependencies = [
|
|
|
776
776
|
|
|
777
777
|
[[package]]
|
|
778
778
|
name = "completr-py"
|
|
779
|
-
version = "0.2.
|
|
779
|
+
version = "0.2.3"
|
|
780
780
|
dependencies = [
|
|
781
781
|
"completr",
|
|
782
782
|
"mimalloc",
|
|
@@ -53,6 +53,7 @@ aws-credentials = ["aws", "dep:aws-config", "dep:aws-credential-types", "dep:asy
|
|
|
53
53
|
[dev-dependencies]
|
|
54
54
|
turbovec = "1.0.0"
|
|
55
55
|
tempfile = "3"
|
|
56
|
+
async-trait = "0.1"
|
|
56
57
|
xxhash-rust = { version = "0.8", features = ["xxh3"] }
|
|
57
58
|
serde_json = { version = "1", features = ["float_roundtrip"] }
|
|
58
59
|
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
|
@@ -28,6 +28,8 @@ pub struct ConnectOptions {
|
|
|
28
28
|
pub cache_dir: Option<PathBuf>,
|
|
29
29
|
/// Storage options, such as credentials or a region, passed to the object store.
|
|
30
30
|
pub storage: Vec<(String, String)>,
|
|
31
|
+
/// Tags on every object written, e.g. to select bucket lifecycle rules.
|
|
32
|
+
pub tags: Vec<(String, String)>,
|
|
31
33
|
pub build: BuildOptions,
|
|
32
34
|
pub index: IndexOptions,
|
|
33
35
|
}
|
|
@@ -38,6 +40,7 @@ impl Default for ConnectOptions {
|
|
|
38
40
|
sync_every: Some(Duration::from_secs(5)),
|
|
39
41
|
cache_dir: None,
|
|
40
42
|
storage: Vec::new(),
|
|
43
|
+
tags: Vec::new(),
|
|
41
44
|
build: BuildOptions::default(),
|
|
42
45
|
index: IndexOptions::default(),
|
|
43
46
|
}
|
|
@@ -48,6 +51,7 @@ crate::setters!(ConnectOptions {
|
|
|
48
51
|
sync_every: Option<Duration>,
|
|
49
52
|
cache_dir: Option<PathBuf>,
|
|
50
53
|
storage: Vec<(String, String)>,
|
|
54
|
+
tags: Vec<(String, String)>,
|
|
51
55
|
build: BuildOptions,
|
|
52
56
|
index: IndexOptions,
|
|
53
57
|
});
|
|
@@ -218,7 +222,8 @@ const LEGACY_SETTINGS: &str = "collection/";
|
|
|
218
222
|
pub async fn connect(url: &str, options: ConnectOptions) -> Result<Client, Error> {
|
|
219
223
|
let mut database = Database::open(url, options.storage)
|
|
220
224
|
.await?
|
|
221
|
-
.with_build_options(options.build)
|
|
225
|
+
.with_build_options(options.build)
|
|
226
|
+
.with_tags(options.tags);
|
|
222
227
|
if let Some(dir) = &options.cache_dir {
|
|
223
228
|
database = database.with_cache_dir(dir)?;
|
|
224
229
|
}
|
|
@@ -196,6 +196,17 @@ impl Database {
|
|
|
196
196
|
Ok(Self::new(Store::open_with(url, options).await?))
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/// Tags every object this database writes, e.g. to select bucket lifecycle rules.
|
|
200
|
+
pub fn with_tags<K: AsRef<str>, V: AsRef<str>>(
|
|
201
|
+
self,
|
|
202
|
+
tags: impl IntoIterator<Item = (K, V)>,
|
|
203
|
+
) -> Self {
|
|
204
|
+
Self {
|
|
205
|
+
store: self.store.with_tags(tags),
|
|
206
|
+
..self
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
199
210
|
/// Caches downloaded segments in `dir` and memory-maps them.
|
|
200
211
|
pub fn with_cache_dir(self, dir: impl AsRef<std::path::Path>) -> Result<Self, Error> {
|
|
201
212
|
Ok(Self {
|
|
@@ -5,7 +5,8 @@ use rustc_hash::FxHashMap;
|
|
|
5
5
|
|
|
6
6
|
use crate::hybrid::fuse;
|
|
7
7
|
use crate::{
|
|
8
|
-
AliasSuggestion, Error, HybridOptions, HybridSuggestion, Index,
|
|
8
|
+
AliasSuggestion, Error, HybridOptions, HybridSuggestion, Index, MatchKind, SearchOptions,
|
|
9
|
+
Suggestion,
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
/// The namespace of indexes published or written without naming one.
|
|
@@ -409,7 +410,7 @@ pub fn layered_complete(
|
|
|
409
410
|
})
|
|
410
411
|
.collect();
|
|
411
412
|
merge(layers, per_layer, options.limit, |s: &Suggestion| {
|
|
412
|
-
(s.id, s.score)
|
|
413
|
+
(s.id, s.kind == MatchKind::Fuzzy, s.score)
|
|
413
414
|
})
|
|
414
415
|
}
|
|
415
416
|
|
|
@@ -433,7 +434,7 @@ pub fn layered_complete_aliases(
|
|
|
433
434
|
})
|
|
434
435
|
.collect();
|
|
435
436
|
merge(layers, per_layer, options.limit, |s: &AliasSuggestion| {
|
|
436
|
-
(s.id, s.score)
|
|
437
|
+
(s.id, false, s.score)
|
|
437
438
|
})
|
|
438
439
|
}
|
|
439
440
|
|
|
@@ -452,7 +453,7 @@ pub fn layered_vector_search(
|
|
|
452
453
|
});
|
|
453
454
|
}
|
|
454
455
|
Ok(merge(layers, per_layer, options.limit, |s: &Suggestion| {
|
|
455
|
-
(s.id, s.score)
|
|
456
|
+
(s.id, false, s.score)
|
|
456
457
|
}))
|
|
457
458
|
}
|
|
458
459
|
|
|
@@ -471,7 +472,7 @@ fn merge<S>(
|
|
|
471
472
|
layers: &[Option<&Index>],
|
|
472
473
|
per_layer: Vec<Vec<S>>,
|
|
473
474
|
limit: usize,
|
|
474
|
-
key: impl Fn(&S) -> (u64, f64),
|
|
475
|
+
key: impl Fn(&S) -> (u64, bool, f64),
|
|
475
476
|
) -> Vec<LayeredSuggestion<S>> {
|
|
476
477
|
let mut merged: Vec<LayeredSuggestion<S>> = Vec::new();
|
|
477
478
|
let mut position: FxHashMap<u64, usize> = FxHashMap::default();
|
|
@@ -495,11 +496,11 @@ fn merge<S>(
|
|
|
495
496
|
.flatten()
|
|
496
497
|
.any(|index| index.covers(id))
|
|
497
498
|
});
|
|
499
|
+
// A correction from one layer never outranks a direct match from another.
|
|
498
500
|
merged.sort_by(|a, b| {
|
|
499
|
-
key(&b.suggestion)
|
|
500
|
-
|
|
501
|
-
.partial_cmp(&
|
|
502
|
-
.unwrap_or(std::cmp::Ordering::Equal)
|
|
501
|
+
let (a, b) = (key(&a.suggestion), key(&b.suggestion));
|
|
502
|
+
a.1.cmp(&b.1)
|
|
503
|
+
.then(b.2.partial_cmp(&a.2).unwrap_or(std::cmp::Ordering::Equal))
|
|
503
504
|
});
|
|
504
505
|
merged.truncate(limit);
|
|
505
506
|
merged
|
|
@@ -9,6 +9,9 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
|
|
9
9
|
use crate::index::{char_count, current_filter, with_filter, Field};
|
|
10
10
|
use crate::{fuzzy, text, Index};
|
|
11
11
|
|
|
12
|
+
/// Shorter queries are not corrected: one edit away from two characters is almost anything.
|
|
13
|
+
const MIN_CORRECTED_CHARS: usize = 3;
|
|
14
|
+
|
|
12
15
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
13
16
|
#[non_exhaustive]
|
|
14
17
|
pub enum MatchKind {
|
|
@@ -898,7 +901,10 @@ impl Index {
|
|
|
898
901
|
.collect();
|
|
899
902
|
|
|
900
903
|
let mut fuzzy_matches: FxHashMap<u32, (usize, usize, f64)> = FxHashMap::default();
|
|
901
|
-
if prefix_matches.len() < max_results
|
|
904
|
+
if prefix_matches.len() < max_results
|
|
905
|
+
&& depth < 2
|
|
906
|
+
&& text::char_len(q) >= MIN_CORRECTED_CHARS
|
|
907
|
+
{
|
|
902
908
|
let mut last_distance = 0;
|
|
903
909
|
for (rank, (phrase, distance)) in self
|
|
904
910
|
.fix_spell(
|
|
@@ -1017,9 +1023,11 @@ impl Index {
|
|
|
1017
1023
|
(score, len, self.id_key(doc), doc, m)
|
|
1018
1024
|
})
|
|
1019
1025
|
.collect();
|
|
1026
|
+
// Corrections rank below every direct match before the cut, so a longer limit only appends.
|
|
1020
1027
|
scored.sort_unstable_by(|a, b| {
|
|
1021
|
-
|
|
1022
|
-
.
|
|
1028
|
+
(a.4.fuzzy_distance > 0)
|
|
1029
|
+
.cmp(&(b.4.fuzzy_distance > 0))
|
|
1030
|
+
.then(b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal))
|
|
1023
1031
|
.then(a.1.cmp(&b.1))
|
|
1024
1032
|
.then(a.2.cmp(&b.2))
|
|
1025
1033
|
});
|
|
@@ -7,7 +7,7 @@ use futures::{StreamExt, TryStreamExt};
|
|
|
7
7
|
use object_store::local::LocalFileSystem;
|
|
8
8
|
use object_store::memory::InMemory;
|
|
9
9
|
use object_store::path::Path;
|
|
10
|
-
use object_store::{ObjectStore, ObjectStoreExt, PutMode, PutOptions, PutPayload};
|
|
10
|
+
use object_store::{ObjectStore, ObjectStoreExt, PutMode, PutOptions, PutPayload, TagSet};
|
|
11
11
|
use url::Url;
|
|
12
12
|
|
|
13
13
|
use crate::{Error, Segment};
|
|
@@ -29,6 +29,8 @@ pub struct Store {
|
|
|
29
29
|
/// Where remote segments are downloaded to and memory-mapped from, per store URL.
|
|
30
30
|
cache: Option<PathBuf>,
|
|
31
31
|
namespace: String,
|
|
32
|
+
/// Tags on every object written, e.g. to select bucket lifecycle rules.
|
|
33
|
+
tags: TagSet,
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
impl Store {
|
|
@@ -80,6 +82,7 @@ impl Store {
|
|
|
80
82
|
local_root: None,
|
|
81
83
|
cache: None,
|
|
82
84
|
namespace,
|
|
85
|
+
tags: TagSet::default(),
|
|
83
86
|
})
|
|
84
87
|
}
|
|
85
88
|
|
|
@@ -93,6 +96,7 @@ impl Store {
|
|
|
93
96
|
local_root: Some(root),
|
|
94
97
|
cache: None,
|
|
95
98
|
namespace: String::new(),
|
|
99
|
+
tags: TagSet::default(),
|
|
96
100
|
})
|
|
97
101
|
}
|
|
98
102
|
|
|
@@ -115,9 +119,31 @@ impl Store {
|
|
|
115
119
|
.to_vec())
|
|
116
120
|
}
|
|
117
121
|
|
|
122
|
+
/// Tags every object this store writes from now on; S3 and Azure keep them, other stores ignore them.
|
|
123
|
+
pub fn with_tags<K: AsRef<str>, V: AsRef<str>>(
|
|
124
|
+
mut self,
|
|
125
|
+
tags: impl IntoIterator<Item = (K, V)>,
|
|
126
|
+
) -> Self {
|
|
127
|
+
let mut set = TagSet::default();
|
|
128
|
+
for (key, value) in tags {
|
|
129
|
+
set.push(key.as_ref(), value.as_ref());
|
|
130
|
+
}
|
|
131
|
+
self.tags = set;
|
|
132
|
+
self
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// The tags written with every object, URL-encoded as S3 takes them.
|
|
136
|
+
pub fn tags(&self) -> &str {
|
|
137
|
+
self.tags.encoded()
|
|
138
|
+
}
|
|
139
|
+
|
|
118
140
|
pub async fn put(&self, key: &str, data: Vec<u8>) -> Result<(), Error> {
|
|
141
|
+
let options = PutOptions {
|
|
142
|
+
tags: self.tags.clone(),
|
|
143
|
+
..PutOptions::default()
|
|
144
|
+
};
|
|
119
145
|
self.inner
|
|
120
|
-
.
|
|
146
|
+
.put_opts(&self.path(key), PutPayload::from(data), options)
|
|
121
147
|
.await?;
|
|
122
148
|
Ok(())
|
|
123
149
|
}
|
|
@@ -127,6 +153,7 @@ impl Store {
|
|
|
127
153
|
pub async fn put_if_absent(&self, key: &str, data: Vec<u8>) -> Result<bool, Error> {
|
|
128
154
|
let options = PutOptions {
|
|
129
155
|
mode: PutMode::Create,
|
|
156
|
+
tags: self.tags.clone(),
|
|
130
157
|
..PutOptions::default()
|
|
131
158
|
};
|
|
132
159
|
match self
|
|
@@ -431,6 +458,13 @@ impl BlockingStore {
|
|
|
431
458
|
self.0.with_cache_dir(dir).map(Self)
|
|
432
459
|
}
|
|
433
460
|
|
|
461
|
+
pub fn with_tags<K: AsRef<str>, V: AsRef<str>>(
|
|
462
|
+
self,
|
|
463
|
+
tags: impl IntoIterator<Item = (K, V)>,
|
|
464
|
+
) -> Self {
|
|
465
|
+
Self(self.0.with_tags(tags))
|
|
466
|
+
}
|
|
467
|
+
|
|
434
468
|
pub fn prune_cache<'a>(&self, keep: impl IntoIterator<Item = &'a str>) -> Result<usize, Error> {
|
|
435
469
|
self.0.prune_cache(keep)
|
|
436
470
|
}
|
|
@@ -557,6 +591,123 @@ mod tests {
|
|
|
557
591
|
use super::*;
|
|
558
592
|
use crate::Document;
|
|
559
593
|
|
|
594
|
+
/// An in-memory store that records the tags each write carries.
|
|
595
|
+
#[derive(Debug, Default)]
|
|
596
|
+
struct Recording {
|
|
597
|
+
inner: InMemory,
|
|
598
|
+
tags: std::sync::Mutex<Vec<String>>,
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
impl std::fmt::Display for Recording {
|
|
602
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
603
|
+
write!(f, "Recording")
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
#[async_trait::async_trait]
|
|
608
|
+
impl ObjectStore for Recording {
|
|
609
|
+
async fn put_opts(
|
|
610
|
+
&self,
|
|
611
|
+
location: &Path,
|
|
612
|
+
payload: PutPayload,
|
|
613
|
+
opts: PutOptions,
|
|
614
|
+
) -> object_store::Result<object_store::PutResult> {
|
|
615
|
+
self.tags
|
|
616
|
+
.lock()
|
|
617
|
+
.unwrap()
|
|
618
|
+
.push(opts.tags.encoded().to_owned());
|
|
619
|
+
self.inner.put_opts(location, payload, opts).await
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
async fn put_multipart_opts(
|
|
623
|
+
&self,
|
|
624
|
+
location: &Path,
|
|
625
|
+
opts: object_store::PutMultipartOptions,
|
|
626
|
+
) -> object_store::Result<Box<dyn object_store::MultipartUpload>> {
|
|
627
|
+
self.inner.put_multipart_opts(location, opts).await
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
async fn get_opts(
|
|
631
|
+
&self,
|
|
632
|
+
location: &Path,
|
|
633
|
+
options: object_store::GetOptions,
|
|
634
|
+
) -> object_store::Result<object_store::GetResult> {
|
|
635
|
+
self.inner.get_opts(location, options).await
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
fn delete_stream(
|
|
639
|
+
&self,
|
|
640
|
+
locations: futures::stream::BoxStream<'static, object_store::Result<Path>>,
|
|
641
|
+
) -> futures::stream::BoxStream<'static, object_store::Result<Path>> {
|
|
642
|
+
self.inner.delete_stream(locations)
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
fn list(
|
|
646
|
+
&self,
|
|
647
|
+
prefix: Option<&Path>,
|
|
648
|
+
) -> futures::stream::BoxStream<'static, object_store::Result<object_store::ObjectMeta>>
|
|
649
|
+
{
|
|
650
|
+
self.inner.list(prefix)
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
async fn list_with_delimiter(
|
|
654
|
+
&self,
|
|
655
|
+
prefix: Option<&Path>,
|
|
656
|
+
) -> object_store::Result<object_store::ListResult> {
|
|
657
|
+
self.inner.list_with_delimiter(prefix).await
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
async fn copy_opts(
|
|
661
|
+
&self,
|
|
662
|
+
from: &Path,
|
|
663
|
+
to: &Path,
|
|
664
|
+
options: object_store::CopyOptions,
|
|
665
|
+
) -> object_store::Result<()> {
|
|
666
|
+
self.inner.copy_opts(from, to, options).await
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
#[tokio::test]
|
|
671
|
+
async fn every_write_carries_the_tags() {
|
|
672
|
+
let recording = Arc::new(Recording::default());
|
|
673
|
+
let store = Store {
|
|
674
|
+
inner: recording.clone(),
|
|
675
|
+
prefix: Path::default(),
|
|
676
|
+
local_root: None,
|
|
677
|
+
cache: None,
|
|
678
|
+
namespace: "recording".into(),
|
|
679
|
+
tags: TagSet::default(),
|
|
680
|
+
}
|
|
681
|
+
.with_tags([
|
|
682
|
+
("LifecycleRule", "KeepForever"),
|
|
683
|
+
("team", "search & ranking"),
|
|
684
|
+
]);
|
|
685
|
+
assert_eq!(
|
|
686
|
+
store.tags(),
|
|
687
|
+
"LifecycleRule=KeepForever&team=search+%26+ranking"
|
|
688
|
+
);
|
|
689
|
+
|
|
690
|
+
let database = crate::Database::new(store);
|
|
691
|
+
let mut txn = database.begin().await.unwrap();
|
|
692
|
+
txn.append_documents("songs", [Document::new(1, "Rust", 0.5)], [])
|
|
693
|
+
.unwrap();
|
|
694
|
+
txn.commit().await.unwrap();
|
|
695
|
+
let mut changes = crate::ChangeSet::new();
|
|
696
|
+
changes.upsert("songs", [Document::new(2, "Go", 0.5)]);
|
|
697
|
+
database.submit(changes).await.unwrap();
|
|
698
|
+
database
|
|
699
|
+
.acquire_lease("ingestor", "me", std::time::Duration::from_secs(60))
|
|
700
|
+
.await
|
|
701
|
+
.unwrap();
|
|
702
|
+
|
|
703
|
+
let written = recording.tags.lock().unwrap().clone();
|
|
704
|
+
// A segment, a version, a change set and a lease.
|
|
705
|
+
assert!(written.len() >= 4, "{written:?}");
|
|
706
|
+
assert!(written
|
|
707
|
+
.iter()
|
|
708
|
+
.all(|t| t == "LifecycleRule=KeepForever&team=search+%26+ranking"));
|
|
709
|
+
}
|
|
710
|
+
|
|
560
711
|
#[test]
|
|
561
712
|
fn local_and_memory_round_trip() {
|
|
562
713
|
let dir = tempfile::tempdir().unwrap();
|
|
@@ -311,7 +311,12 @@ class Engine:
|
|
|
311
311
|
|
|
312
312
|
class Store:
|
|
313
313
|
def __init__(
|
|
314
|
-
self,
|
|
314
|
+
self,
|
|
315
|
+
url: str,
|
|
316
|
+
options: Mapping[str, str] | None = None,
|
|
317
|
+
cache_dir: str | PathLike[str] | None = None,
|
|
318
|
+
*,
|
|
319
|
+
tags: Mapping[str, str] | None = None,
|
|
315
320
|
) -> None: ...
|
|
316
321
|
def prune_cache(self, keep: Sequence[str]) -> int: ...
|
|
317
322
|
def get(self, key: str) -> bytes: ...
|
|
@@ -394,6 +399,7 @@ class Database:
|
|
|
394
399
|
options: Mapping[str, str] | None = None,
|
|
395
400
|
cache_dir: str | PathLike[str] | None = None,
|
|
396
401
|
*,
|
|
402
|
+
tags: Mapping[str, str] | None = None,
|
|
397
403
|
min_word_chars: int = 3,
|
|
398
404
|
max_edit_distance: int = 2,
|
|
399
405
|
fuzzy_prefix_chars: int = 7,
|
|
@@ -564,6 +570,7 @@ class Client:
|
|
|
564
570
|
sync_every: float | None = 5.0,
|
|
565
571
|
cache_dir: str | PathLike[str] | None = None,
|
|
566
572
|
options: Mapping[str, str] | None = None,
|
|
573
|
+
tags: Mapping[str, str] | None = None,
|
|
567
574
|
min_word_chars: int = 3,
|
|
568
575
|
max_edit_distance: int = 2,
|
|
569
576
|
fuzzy_prefix_chars: int = 7,
|
|
@@ -1543,17 +1543,19 @@ struct Store(completr_rs::BlockingStore);
|
|
|
1543
1543
|
#[pymethods]
|
|
1544
1544
|
impl Store {
|
|
1545
1545
|
#[new]
|
|
1546
|
-
#[pyo3(signature = (url, options = None, cache_dir = None))]
|
|
1546
|
+
#[pyo3(signature = (url, options = None, cache_dir = None, *, tags = None))]
|
|
1547
1547
|
fn new(
|
|
1548
1548
|
py: Python<'_>,
|
|
1549
1549
|
url: &str,
|
|
1550
1550
|
options: Option<HashMap<String, String>>,
|
|
1551
1551
|
cache_dir: Option<PathBuf>,
|
|
1552
|
+
tags: Option<HashMap<String, String>>,
|
|
1552
1553
|
) -> PyResult<Self> {
|
|
1553
1554
|
let options = options.unwrap_or_default();
|
|
1554
1555
|
let store = py
|
|
1555
1556
|
.detach(|| completr_rs::BlockingStore::open_with(url, options))
|
|
1556
|
-
.map_err(to_py_err)
|
|
1557
|
+
.map_err(to_py_err)?
|
|
1558
|
+
.with_tags(tags.unwrap_or_default());
|
|
1557
1559
|
let store = match cache_dir {
|
|
1558
1560
|
Some(dir) => store.with_cache_dir(dir).map_err(to_py_err)?,
|
|
1559
1561
|
None => store,
|
|
@@ -1637,11 +1639,11 @@ impl Database {
|
|
|
1637
1639
|
|
|
1638
1640
|
#[pymethods]
|
|
1639
1641
|
impl Database {
|
|
1640
|
-
/// `cache_dir` keeps downloaded segments on local disk, memory-mapped
|
|
1641
|
-
/// apply to segments this database builds from documents.
|
|
1642
|
+
/// `cache_dir` keeps downloaded segments on local disk, memory-mapped; `tags` go on every object
|
|
1643
|
+
/// written. The other keyword settings apply to segments this database builds from documents.
|
|
1642
1644
|
#[new]
|
|
1643
1645
|
#[pyo3(signature = (
|
|
1644
|
-
url, options = None, cache_dir = None, *,
|
|
1646
|
+
url, options = None, cache_dir = None, *, tags = None,
|
|
1645
1647
|
min_word_chars = 3, max_edit_distance = 2, fuzzy_prefix_chars = 7, vector_bits = 4, compact_keys = false, build_threads = 1,
|
|
1646
1648
|
))]
|
|
1647
1649
|
#[allow(clippy::too_many_arguments)]
|
|
@@ -1650,6 +1652,7 @@ impl Database {
|
|
|
1650
1652
|
url: &str,
|
|
1651
1653
|
options: Option<HashMap<String, String>>,
|
|
1652
1654
|
cache_dir: Option<PathBuf>,
|
|
1655
|
+
tags: Option<HashMap<String, String>>,
|
|
1653
1656
|
min_word_chars: u8,
|
|
1654
1657
|
max_edit_distance: u8,
|
|
1655
1658
|
fuzzy_prefix_chars: u8,
|
|
@@ -1660,7 +1663,8 @@ impl Database {
|
|
|
1660
1663
|
let options = options.unwrap_or_default();
|
|
1661
1664
|
let database = py
|
|
1662
1665
|
.detach(|| completr_rs::block_on(completr_rs::Database::open(url, options)))
|
|
1663
|
-
.map_err(to_py_err)
|
|
1666
|
+
.map_err(to_py_err)?
|
|
1667
|
+
.with_tags(tags.unwrap_or_default());
|
|
1664
1668
|
let database = match cache_dir {
|
|
1665
1669
|
Some(dir) => database.with_cache_dir(dir).map_err(to_py_err)?,
|
|
1666
1670
|
None => database,
|
|
@@ -2519,7 +2523,7 @@ impl Client {
|
|
|
2519
2523
|
/// `None` syncs only on `sync()`. The keyword settings apply to segments this client builds.
|
|
2520
2524
|
#[new]
|
|
2521
2525
|
#[pyo3(signature = (
|
|
2522
|
-
url = "memory://", *, sync_every = Some(5.0), cache_dir = None, options = None,
|
|
2526
|
+
url = "memory://", *, sync_every = Some(5.0), cache_dir = None, options = None, tags = None,
|
|
2523
2527
|
min_word_chars = 3, max_edit_distance = 2, fuzzy_prefix_chars = 7, vector_bits = 4, compact_keys = false, build_threads = 1,
|
|
2524
2528
|
))]
|
|
2525
2529
|
#[allow(clippy::too_many_arguments)]
|
|
@@ -2529,6 +2533,7 @@ impl Client {
|
|
|
2529
2533
|
sync_every: Option<f64>,
|
|
2530
2534
|
cache_dir: Option<PathBuf>,
|
|
2531
2535
|
options: Option<HashMap<String, String>>,
|
|
2536
|
+
tags: Option<HashMap<String, String>>,
|
|
2532
2537
|
min_word_chars: u8,
|
|
2533
2538
|
max_edit_distance: u8,
|
|
2534
2539
|
fuzzy_prefix_chars: u8,
|
|
@@ -2548,6 +2553,7 @@ impl Client {
|
|
|
2548
2553
|
.sync_every(sync_every.map(|s| Duration::from_secs_f64(s.max(0.001))))
|
|
2549
2554
|
.cache_dir(cache_dir)
|
|
2550
2555
|
.storage(options.unwrap_or_default().into_iter().collect())
|
|
2556
|
+
.tags(tags.unwrap_or_default().into_iter().collect())
|
|
2551
2557
|
.build(build);
|
|
2552
2558
|
let client = py
|
|
2553
2559
|
.detach(|| completr_rs::block_on(completr_rs::connect(url, connect)))
|
|
@@ -3,6 +3,8 @@ import time
|
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
5
|
|
|
6
|
+
import completr
|
|
7
|
+
|
|
6
8
|
from completr import ConflictError
|
|
7
9
|
from completr import ChangeSet, Database, Engine, Replica, Store, Ingestor
|
|
8
10
|
|
|
@@ -184,3 +186,14 @@ def test_threaded_queries_during_updates(database):
|
|
|
184
186
|
assert len({id(index) for index, _, _ in samples}) > 1
|
|
185
187
|
for index, query, result in samples:
|
|
186
188
|
assert [(h.id, h.score, h.kind) for h in index.complete(query, 10)] == result
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_tags_are_accepted_everywhere_and_ignored_by_local_stores(tmp_path):
|
|
192
|
+
tags = {"LifecycleRule": "KeepForever"}
|
|
193
|
+
db = completr.connect(str(tmp_path / "db"), tags=tags)
|
|
194
|
+
with db.begin() as txn:
|
|
195
|
+
txn.append("songs", DOCS[:2])
|
|
196
|
+
assert db.index_names() == ["songs"]
|
|
197
|
+
completr.Store(str(tmp_path / "store"), tags=tags).put("a", b"x")
|
|
198
|
+
client = completr.Client(str(tmp_path / "client"), sync_every=None, tags=tags)
|
|
199
|
+
client.create_collection("songs").add(DOCS[:2])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|