1
1
//! In-memory store of Atomic data.
2
2
//! This provides many methods for finding, changing, serializing and parsing Atomic Data.
3
3
4
+ use crate :: storelike:: QueryResult ;
5
+ use crate :: Value ;
4
6
use crate :: { atoms:: Atom , storelike:: Storelike } ;
5
7
use crate :: { errors:: AtomicResult , Resource } ;
6
8
use std:: { collections:: HashMap , sync:: Arc , sync:: Mutex } ;
@@ -24,6 +26,97 @@ impl Store {
24
26
crate :: populate:: populate_base_models ( & store) ?;
25
27
Ok ( store)
26
28
}
29
+
30
+ /// Triple Pattern Fragments interface.
31
+ /// Use this for most queries, e.g. finding all items with some property / value combination.
32
+ /// Returns an empty array if nothing is found.
33
+ ///
34
+ /// # Example
35
+ ///
36
+ /// For example, if I want to view all Resources that are instances of the class "Property", I'd do:
37
+ ///
38
+ /// ```
39
+ /// use atomic_lib::Storelike;
40
+ /// let mut store = atomic_lib::Store::init().unwrap();
41
+ /// store.populate();
42
+ /// let atoms = store.tpf(
43
+ /// None,
44
+ /// Some("https://atomicdata.dev/properties/isA"),
45
+ /// Some(&atomic_lib::Value::AtomicUrl("https://atomicdata.dev/classes/Class".into())),
46
+ /// true
47
+ /// ).unwrap();
48
+ /// assert!(atoms.len() > 11)
49
+ /// ```
50
+ // Very costly, slow implementation.
51
+ // Does not assume any indexing.
52
+ fn tpf (
53
+ & self ,
54
+ q_subject : Option < & str > ,
55
+ q_property : Option < & str > ,
56
+ q_value : Option < & Value > ,
57
+ // Whether resources from outside the store should be searched through
58
+ include_external : bool ,
59
+ ) -> AtomicResult < Vec < Atom > > {
60
+ let mut vec: Vec < Atom > = Vec :: new ( ) ;
61
+
62
+ let hassub = q_subject. is_some ( ) ;
63
+ let hasprop = q_property. is_some ( ) ;
64
+ let hasval = q_value. is_some ( ) ;
65
+
66
+ // Simply return all the atoms
67
+ if !hassub && !hasprop && !hasval {
68
+ for resource in self . all_resources ( include_external) {
69
+ for ( property, value) in resource. get_propvals ( ) {
70
+ vec. push ( Atom :: new (
71
+ resource. get_subject ( ) . clone ( ) ,
72
+ property. clone ( ) ,
73
+ value. clone ( ) ,
74
+ ) )
75
+ }
76
+ }
77
+ return Ok ( vec) ;
78
+ }
79
+
80
+ // Find atoms matching the TPF query in a single resource
81
+ let mut find_in_resource = |resource : & Resource | {
82
+ let subj = resource. get_subject ( ) ;
83
+ for ( prop, val) in resource. get_propvals ( ) . iter ( ) {
84
+ if hasprop && q_property. as_ref ( ) . unwrap ( ) == prop {
85
+ if hasval {
86
+ if val. contains_value ( q_value. unwrap ( ) ) {
87
+ vec. push ( Atom :: new ( subj. into ( ) , prop. into ( ) , val. clone ( ) ) )
88
+ }
89
+ break ;
90
+ } else {
91
+ vec. push ( Atom :: new ( subj. into ( ) , prop. into ( ) , val. clone ( ) ) )
92
+ }
93
+ break ;
94
+ } else if hasval && !hasprop && val. contains_value ( q_value. unwrap ( ) ) {
95
+ vec. push ( Atom :: new ( subj. into ( ) , prop. into ( ) , val. clone ( ) ) )
96
+ }
97
+ }
98
+ } ;
99
+
100
+ match q_subject {
101
+ Some ( sub) => match self . get_resource ( sub) {
102
+ Ok ( resource) => {
103
+ if hasprop | hasval {
104
+ find_in_resource ( & resource) ;
105
+ Ok ( vec)
106
+ } else {
107
+ Ok ( resource. to_atoms ( ) )
108
+ }
109
+ }
110
+ Err ( _) => Ok ( vec) ,
111
+ } ,
112
+ None => {
113
+ for resource in self . all_resources ( include_external) {
114
+ find_in_resource ( & resource) ;
115
+ }
116
+ Ok ( vec)
117
+ }
118
+ }
119
+ }
27
120
}
28
121
29
122
impl Storelike for Store {
@@ -126,6 +219,62 @@ impl Storelike for Store {
126
219
fn set_default_agent ( & self , agent : crate :: agents:: Agent ) {
127
220
self . default_agent . lock ( ) . unwrap ( ) . replace ( agent) ;
128
221
}
222
+
223
+ fn query ( & self , q : & crate :: storelike:: Query ) -> AtomicResult < crate :: storelike:: QueryResult > {
224
+ let atoms = self . tpf (
225
+ None ,
226
+ q. property . as_deref ( ) ,
227
+ q. value . as_ref ( ) ,
228
+ q. include_external ,
229
+ ) ?;
230
+
231
+ // Remove duplicate subjects
232
+ let mut subjects_deduplicated: Vec < String > = atoms
233
+ . iter ( )
234
+ . map ( |atom| atom. subject . clone ( ) )
235
+ . collect :: < std:: collections:: HashSet < String > > ( )
236
+ . into_iter ( )
237
+ . collect ( ) ;
238
+
239
+ // Sort by subject, better than no sorting
240
+ subjects_deduplicated. sort ( ) ;
241
+
242
+ // WARNING: Entering expensive loop!
243
+ // This is needed for sorting, authorization and including nested resources.
244
+ // It could be skipped if there is no authorization and sorting requirement.
245
+ let mut resources = Vec :: new ( ) ;
246
+ for subject in subjects_deduplicated. iter ( ) {
247
+ // These nested resources are not fully calculated - they will be presented as -is
248
+ match self . get_resource_extended ( subject, true , q. for_agent . as_deref ( ) ) {
249
+ Ok ( resource) => {
250
+ resources. push ( resource) ;
251
+ }
252
+ Err ( e) => match & e. error_type {
253
+ crate :: AtomicErrorType :: NotFoundError => { }
254
+ crate :: AtomicErrorType :: UnauthorizedError => { }
255
+ _other => {
256
+ return Err (
257
+ format ! ( "Error when getting resource in collection: {}" , e) . into ( )
258
+ )
259
+ }
260
+ } ,
261
+ }
262
+ }
263
+
264
+ if let Some ( sort) = & q. sort_by {
265
+ resources = crate :: collections:: sort_resources ( resources, sort, q. sort_desc ) ;
266
+ }
267
+ let mut subjects = Vec :: new ( ) ;
268
+ for r in resources. iter ( ) {
269
+ subjects. push ( r. get_subject ( ) . clone ( ) )
270
+ }
271
+
272
+ Ok ( QueryResult {
273
+ count : atoms. len ( ) ,
274
+ subjects,
275
+ resources,
276
+ } )
277
+ }
129
278
}
130
279
131
280
#[ cfg( test) ]
0 commit comments