oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1use crate::any::AnyArrayIter;
2use crate::json_path::JsonPathToken;
3use crate::{Any, Array, JsonPath, JsonPathEval, Map, Out, ReadTxn, Xml, XmlFragment};
4
5impl<T> JsonPathEval for T
6where
7 T: ReadTxn,
8{
9 type Iter<'a>
10 = JsonPathIter<'a, T>
11 where
12 T: 'a;
13
14 /// Evaluate JSON path on the current transaction, starting from current transaction [Doc] as
15 /// its root.
16 ///
17 /// # Example
18 /// ```rust
19 /// use yrs::{any, Array, ArrayPrelim, Doc, In, JsonPath, JsonPathEval, Map, MapPrelim, Out, Transact, WriteTxn};
20 ///
21 /// let doc = Doc::new();
22 /// let mut txn = doc.transact_mut();
23 /// let users = txn.get_or_insert_array("users");
24 ///
25 /// // populate the document with some data to query
26 /// users.insert(&mut txn, 0, MapPrelim::from([
27 /// ("name".to_string(), In::Any(any!("Alice"))),
28 /// ("surname".into(), In::Any(any!("Smith"))),
29 /// ("age".into(), In::Any(any!(25))),
30 /// (
31 /// "friends".into(),
32 /// In::from(ArrayPrelim::from([
33 /// any!({ "name": "Bob", "nick": "boreas" }),
34 /// any!({ "nick": "crocodile91" }),
35 /// ])),
36 /// ),
37 /// ]));
38 ///
39 /// let query = JsonPath::parse("$.users..friends.*.nick").unwrap();
40 /// let values: Vec<Out> = txn.json_path(&query).collect();
41 /// assert_eq!(values, vec![Out::Any(any!("boreas")), Out::Any(any!("crocodile91"))]);
42 /// ```
43 fn json_path<'a>(&'a self, path: &'a JsonPath<'a>) -> Self::Iter<'a> {
44 JsonPathIter::new(self, path.as_ref())
45 }
46}
47
48fn slice_iter<'a, T: ReadTxn>(
49 txn: &'a T,
50 value: Option<Out>,
51 from: usize,
52 to: usize,
53 by: usize,
54) -> Option<Box<dyn Iterator<Item = Out> + 'a>> {
55 match value {
56 Some(Out::Any(Any::Array(array))) => {
57 let iter = AnyArrayIter::new(array);
58 Some(Box::new(
59 iter.skip(from).take(to - from).step_by(by).map(Out::Any),
60 ))
61 }
62 Some(Out::YArray(array)) => {
63 let iter = array.iter(txn);
64 Some(Box::new(iter.skip(from).take(to - from).step_by(by)))
65 }
66 Some(Out::YXmlElement(xml)) => {
67 let iter = xml.children(txn);
68 Some(Box::new(
69 iter.skip(from).take(to - from).step_by(by).map(Out::from),
70 ))
71 }
72 Some(Out::YXmlFragment(xml)) => {
73 let iter = xml.children(txn);
74 Some(Box::new(
75 iter.skip(from).take(to - from).step_by(by).map(Out::from),
76 ))
77 }
78 _ => None,
79 }
80}
81
82fn any_iter<'a, T: ReadTxn>(
83 txn: &'a T,
84 out: Option<Out>,
85) -> Option<Box<dyn Iterator<Item = Out> + 'a>> {
86 #[inline]
87 fn dyn_iter<'a, I: Iterator<Item = Out> + 'a>(iter: I) -> Box<dyn Iterator<Item = Out> + 'a> {
88 Box::new(iter)
89 }
90
91 match out {
92 None => Some(dyn_iter(txn.root_refs().map(|(_, out)| out))),
93 Some(Out::Any(any)) => {
94 let iter = any.try_into_iter();
95 iter.map(|iter| dyn_iter(iter.map(|(_, v)| Out::Any(v))))
96 }
97 Some(Out::YArray(array)) => Some(dyn_iter(array.iter(txn))),
98 Some(Out::YXmlElement(elem)) => Some(dyn_iter(elem.children(txn).map(Out::from))),
99 Some(Out::YXmlFragment(elem)) => Some(dyn_iter(elem.children(txn).map(Out::from))),
100 Some(Out::YMap(map)) => Some(dyn_iter(map.into_iter(txn).map(|(_, value)| value))),
101 Some(Out::UndefinedRef(branch)) => {
102 // undefined ref only happens for root level types. These can be: ArrayRef, MapRef,
103 // XmlFragmentRef, TextRef. For JsonPath, we only care about ArrayRef and MapRef.
104 if branch.start.is_some() {
105 // a list component is not empty: we assume it's an Array
106 let array = crate::ArrayRef::from(branch);
107 Some(dyn_iter(array.iter(txn)))
108 } else {
109 // we assume it's a YMap
110 let map = crate::MapRef::from(branch);
111 Some(dyn_iter(map.into_iter(txn).map(|(_, value)| value)))
112 }
113 }
114 _ => None,
115 }
116}
117
118fn member_union_iter<'a, T: ReadTxn>(
119 txn: &'a T,
120 value: Option<Out>,
121 members: &'a [&'a str],
122) -> Option<Box<dyn Iterator<Item = Out> + 'a>> {
123 match value {
124 Some(Out::Any(Any::Map(map))) => {
125 let iter = members
126 .into_iter()
127 .flat_map(move |key| map.get(*key).cloned().map(Out::Any));
128 Some(Box::new(iter))
129 }
130 Some(Out::YMap(map)) => {
131 let iter = members.into_iter().flat_map(move |key| map.get(txn, *key));
132 Some(Box::new(iter))
133 }
134 None => {
135 let iter = members.into_iter().flat_map(move |key| txn.get(key));
136 Some(Box::new(iter))
137 }
138 _ => None,
139 }
140}
141
142fn index_union_iter<'a, T: ReadTxn>(
143 txn: &'a T,
144 value: Option<Out>,
145 indices: &'a [i32],
146) -> Option<Box<dyn Iterator<Item = Out> + 'a>> {
147 match value {
148 Some(Out::Any(Any::Array(array))) => {
149 let iter = indices.into_iter().flat_map(move |i| {
150 let i = if *i < 0 {
151 (array.len() as i32 + *i) as usize
152 } else {
153 *i as usize
154 };
155 array.get(i).cloned().map(Out::Any)
156 });
157 Some(Box::new(iter))
158 }
159 Some(Out::YArray(array)) => {
160 let len = array.len(txn);
161 let iter = indices.into_iter().flat_map(move |i| {
162 let i = if *i < 0 {
163 (len as i32 + *i) as u32
164 } else {
165 *i as u32
166 };
167 array.get(txn, i)
168 });
169 Some(Box::new(iter))
170 }
171 Some(Out::YXmlFragment(xml)) => {
172 let len = xml.len(txn);
173 let iter = indices.into_iter().flat_map(move |i| {
174 let i = if *i < 0 {
175 (len as i32 + *i) as u32
176 } else {
177 *i as u32
178 };
179 xml.get(txn, i).map(Out::from)
180 });
181 Some(Box::new(iter))
182 }
183 Some(Out::YXmlElement(xml)) => {
184 let len = xml.len(txn);
185 let iter = indices.into_iter().flat_map(move |i| {
186 let i = if *i < 0 {
187 (len as i32 + *i) as u32
188 } else {
189 *i as u32
190 };
191 xml.get(txn, i).map(Out::from)
192 });
193 Some(Box::new(iter))
194 }
195 _ => None,
196 }
197}
198
199pub struct JsonPathIter<'a, T> {
200 txn: &'a T,
201 pattern: &'a [JsonPathToken<'a>],
202 frame: ExecutionFrame<'a>,
203}
204
205impl<'a, T> JsonPathIter<'a, T>
206where
207 T: ReadTxn,
208{
209 fn new(txn: &'a T, path: &'a [JsonPathToken<'a>]) -> Self {
210 Self {
211 txn,
212 pattern: path.as_ref(),
213 frame: ExecutionFrame::new(None, 0, None),
214 }
215 }
216}
217
218impl<'a, T> Iterator for JsonPathIter<'a, T>
219where
220 T: ReadTxn,
221{
222 type Item = Out;
223
224 fn next(&mut self) -> Option<Self::Item> {
225 if let Some(iter) = &mut self.frame.iter {
226 match iter.next() {
227 None => {
228 self.frame.iter = None;
229 return if self.frame.ascend() {
230 self.next()
231 } else {
232 None
233 };
234 }
235 Some(curr) => {
236 self.frame.descend(curr);
237 }
238 }
239 } else if self.frame.index == self.pattern.len() {
240 return if self.frame.ascend() {
241 self.next()
242 } else {
243 None
244 };
245 }
246 // early return only works if the loop after has at least one iteration
247 let mut early_return = false;
248 while self.frame.index < self.pattern.len() {
249 let segment = &self.pattern[self.frame.index];
250 self.frame.index += 1;
251 match segment {
252 JsonPathToken::Root => self.frame.current = None,
253 JsonPathToken::Current => { /* do nothing */ }
254 JsonPathToken::Member(key) => {
255 if let Some(value) = get_member(self.txn, self.frame.current.as_ref(), *key) {
256 self.frame.current = Some(value);
257 } else {
258 early_return = true;
259 }
260 }
261 JsonPathToken::Index(idx) => {
262 if let Some(value) = get_index(self.txn, self.frame.current.as_ref(), *idx) {
263 self.frame.current = Some(value);
264 } else {
265 early_return = true;
266 }
267 }
268 JsonPathToken::Wildcard => {
269 if let Some(iter) = any_iter(self.txn, self.frame.current.clone()) {
270 self.frame.iter = Some(iter);
271 return self.next();
272 }
273 early_return = true;
274 }
275 JsonPathToken::RecursiveDescend => {
276 if let Some(iter) = any_iter(self.txn, self.frame.current.clone()) {
277 self.frame.iter = Some(iter);
278 self.frame.is_descending = true;
279 return self.next();
280 }
281 early_return = true;
282 }
283 JsonPathToken::Slice(from, to, by) => {
284 if let Some(iter) = slice_iter(
285 self.txn,
286 self.frame.current.clone(),
287 *from as usize,
288 *to as usize,
289 *by as usize,
290 ) {
291 self.frame.iter = Some(iter);
292 return self.next();
293 }
294 early_return = true;
295 }
296 JsonPathToken::MemberUnion(members) => {
297 if let Some(iter) =
298 member_union_iter(self.txn, self.frame.current.clone(), &members)
299 {
300 self.frame.iter = Some(iter);
301 return self.next();
302 }
303 early_return = true;
304 }
305 JsonPathToken::IndexUnion(indices) => {
306 if let Some(iter) =
307 index_union_iter(self.txn, self.frame.current.clone(), &indices)
308 {
309 self.frame.iter = Some(iter);
310 return self.next();
311 }
312 early_return = true;
313 }
314 }
315
316 if early_return {
317 break;
318 }
319 }
320
321 if !early_return {
322 return self.frame.current.clone();
323 } else if self.frame.is_descending {
324 if let Some(iter) = any_iter(self.txn, self.frame.current.clone()) {
325 self.frame.iter = Some(iter);
326 self.frame.is_descending = true;
327 self.frame.index -= 1; // '..' means we're not consuming the segment in this iteration
328 return self.next();
329 }
330 }
331
332 if self.frame.iter.is_none() && !self.frame.ascend() {
333 None // we got to the end of the evaluator
334 } else {
335 self.next()
336 }
337 }
338}
339
340fn get_member<T: ReadTxn>(txn: &T, out: Option<&Out>, key: &str) -> Option<Out> {
341 match out {
342 None => txn.get(key),
343 Some(Out::YMap(map)) => map.get(txn, key),
344 Some(Out::Any(Any::Map(map))) => map.get(key).map(|any| Out::Any(any.clone())),
345 Some(Out::YXmlElement(elem)) => elem
346 .get_attribute(txn, key)
347 .map(|attr| Out::Any(Any::String(attr.into()))),
348 Some(Out::YXmlText(elem)) => elem
349 .get_attribute(txn, key)
350 .map(|attr| Out::Any(Any::String(attr.into()))),
351 Some(Out::UndefinedRef(branch)) => {
352 // we assume it's a YMap
353 let map = crate::MapRef::from(*branch);
354 map.get(txn, key)
355 }
356 _ => None,
357 }
358}
359
360fn get_index<T: ReadTxn>(txn: &T, out: Option<&Out>, idx: i32) -> Option<Out> {
361 match out {
362 Some(Out::YArray(array)) => {
363 let idx = if idx < 0 {
364 array.len(txn) as i32 + idx
365 } else {
366 idx
367 } as u32;
368 array.get(txn, idx)
369 }
370 Some(Out::Any(Any::Array(array))) => {
371 let idx = if idx < 0 {
372 array.len() as i32 + idx
373 } else {
374 idx
375 } as usize;
376 array.get(idx).cloned().map(Out::Any)
377 }
378 Some(Out::YXmlFragment(elem)) => {
379 let idx = if idx < 0 {
380 elem.len(txn) as i32 + idx
381 } else {
382 idx
383 } as u32;
384 elem.get(txn, idx).map(Out::from)
385 }
386 Some(Out::YXmlElement(elem)) => {
387 let idx = if idx < 0 {
388 elem.len(txn) as i32 + idx
389 } else {
390 idx
391 } as u32;
392 elem.get(txn, idx).map(Out::from)
393 }
394 Some(Out::UndefinedRef(array)) => {
395 // we assume it's a YMap
396 let array = crate::ArrayRef::from(*array);
397 let idx = if idx < 0 {
398 array.len(txn) as i32 + idx
399 } else {
400 idx
401 } as u32;
402 array.get(txn, idx)
403 }
404 _ => None,
405 }
406}
407
408/// Scope used for recursive iteration, i.e. wildcard, descent or slice.
409struct ExecutionFrame<'a> {
410 /// Offset to tokens array, where the current scope starts.
411 index: usize,
412 /// Whether we're in recursive descent scope.
413 is_descending: bool,
414 /// Current object this scope is iterating over.
415 current: Option<Out>,
416 /// Iterator used by this scope.
417 iter: Option<ScopeIterator<'a>>,
418 /// Scopes can be nested in each other i.e. `$.people.*.friends[*]name`. In such case they
419 /// are organized in a linked list, with the first elements being the innermost scopes.
420 next: Option<Box<ExecutionFrame<'a>>>,
421}
422
423impl<'a> ExecutionFrame<'a> {
424 fn new(current: Option<Out>, index: usize, iter: Option<ScopeIterator<'a>>) -> Self {
425 Self {
426 index,
427 is_descending: false,
428 current,
429 iter,
430 next: None,
431 }
432 }
433
434 /// Descent into given iterator context, moving current frame to the stack and replacing it with
435 /// a new one executing in a context of that iterator.
436 fn descend(&mut self, current: Out) {
437 let new_self = ExecutionFrame {
438 index: self.index,
439 is_descending: self.is_descending,
440 current: Some(current),
441 iter: None,
442 next: None,
443 };
444 let old_frame = std::mem::replace(self, new_self);
445 self.next = Some(Box::new(old_frame));
446 }
447
448 /// Return from current scope, restoring previous iter frame from the stack.
449 /// If there are no more iter frames, return false.
450 fn ascend(&mut self) -> bool {
451 match self.next.take() {
452 None => false,
453 Some(next) => {
454 *self = *next;
455 true
456 }
457 }
458 }
459}
460
461type ScopeIterator<'a> = Box<dyn Iterator<Item = Out> + 'a>;
462
463#[cfg(test)]
464mod test {
465 use crate::updates::decoder::Decode;
466 use crate::{
467 any, Array, ArrayPrelim, Doc, In, JsonPath, JsonPathEval, MapPrelim, Out, ReadTxn,
468 Transact, Update, WriteTxn,
469 };
470
471 fn mixed_sample() -> Doc {
472 let doc = Doc::new();
473 let mut tx = doc.transact_mut();
474 let users = tx.get_or_insert_array("users");
475 users.insert(
476 &mut tx,
477 0,
478 MapPrelim::from([
479 ("name".to_string(), In::Any(any!("Alice"))),
480 ("surname".into(), In::Any(any!("Smith"))),
481 ("age".into(), In::Any(any!(25))),
482 (
483 "friends".into(),
484 In::from(ArrayPrelim::from([
485 any!({ "name": "Bob", "nick": "boreas" }),
486 any!({ "nick": "crocodile91" }),
487 ])),
488 ),
489 ]),
490 );
491 users.insert(
492 &mut tx,
493 1,
494 MapPrelim::from([
495 ("name".to_string(), In::Any(any!("Bob"))),
496 ("nick".into(), In::Any(any!("boreas"))),
497 ("age".into(), In::Any(any!(30))),
498 ]),
499 );
500 users.insert(
501 &mut tx,
502 2,
503 MapPrelim::from([
504 ("nick".to_string(), In::Any(any!("crocodile91"))),
505 ("age".into(), In::Any(any!(35))),
506 ]),
507 );
508 users.insert(
509 &mut tx,
510 3,
511 MapPrelim::from([
512 ("name".to_string(), In::Any(any!("Damian"))),
513 ("surname".into(), In::Any(any!("Smith"))),
514 ("age".into(), In::Any(any!(30))),
515 ]),
516 );
517 users.insert(
518 &mut tx,
519 4,
520 MapPrelim::from([
521 ("name".to_string(), In::Any(any!("Elise"))),
522 ("age".into(), In::Any(any!(35))),
523 ]),
524 );
525 drop(tx);
526 doc
527 }
528
529 #[test]
530 fn eval_member_partial() {
531 let doc = mixed_sample();
532 let path = JsonPath::parse("$.users").unwrap();
533 let tx = doc.transact();
534 let values: Vec<_> = tx.json_path(&path).collect();
535 let expected = tx.get("users").unwrap();
536 assert_eq!(values, vec![expected]);
537 }
538
539 #[test]
540 fn eval_member_full() {
541 let doc = mixed_sample();
542 let path = JsonPath::parse("$.users[0].name").unwrap();
543 let tx = doc.transact();
544 let values: Vec<_> = tx.json_path(&path).collect();
545 assert_eq!(values, vec![Out::Any(any!("Alice"))]);
546 }
547
548 #[test]
549 fn eval_member_negative_index() {
550 let doc = mixed_sample();
551 let path = JsonPath::parse("$.users[-1].name").unwrap();
552 let tx = doc.transact();
553 let values: Vec<_> = tx.json_path(&path).collect();
554 assert_eq!(values, vec![Out::Any(any!("Elise"))]);
555 }
556
557 #[test]
558 fn eval_member_wildcard_array() {
559 let doc = mixed_sample();
560 let path = JsonPath::parse("$.users[*].name").unwrap();
561 let tx = doc.transact();
562 let values: Vec<_> = tx.json_path(&path).collect();
563 assert_eq!(
564 values,
565 vec![
566 Out::Any(any!("Alice")),
567 Out::Any(any!("Bob")),
568 Out::Any(any!("Damian")),
569 Out::Any(any!("Elise"))
570 ]
571 );
572 }
573
574 #[test]
575 fn eval_member_slice() {
576 let doc = mixed_sample();
577 let path = JsonPath::parse("$.users[1:3].nick").unwrap();
578 let tx = doc.transact();
579 let values: Vec<_> = tx.json_path(&path).collect();
580 assert_eq!(
581 values,
582 vec![Out::Any(any!("boreas")), Out::Any(any!("crocodile91"))]
583 );
584 }
585
586 #[test]
587 fn eval_index_union() {
588 let doc = mixed_sample();
589 let path = JsonPath::parse("$.users[1,3].name").unwrap();
590 let tx = doc.transact();
591 let values: Vec<_> = tx.json_path(&path).collect();
592 assert_eq!(
593 values,
594 vec![Out::Any(any!("Bob")), Out::Any(any!("Damian"))]
595 );
596 }
597
598 #[test]
599 fn eval_member_union() {
600 let doc = mixed_sample();
601 let path = JsonPath::parse("$.users[0]['name','surname']").unwrap();
602 let tx = doc.transact();
603 let values: Vec<_> = tx.json_path(&path).collect();
604 assert_eq!(
605 values,
606 vec![Out::Any(any!("Alice")), Out::Any(any!("Smith"))]
607 );
608 }
609
610 #[test]
611 fn eval_descent_flat() {
612 let doc = mixed_sample();
613 let path = JsonPath::parse("$.users..name").unwrap();
614 let tx = doc.transact();
615 let values: Vec<_> = tx.json_path(&path).collect();
616 assert_eq!(
617 values,
618 vec![
619 Out::Any(any!("Alice")),
620 Out::Any(any!("Bob")),
621 Out::Any(any!("Damian")),
622 Out::Any(any!("Elise"))
623 ]
624 );
625 }
626 #[test]
627 fn eval_on_fresh_document() {
628 let doc_state = mixed_sample()
629 .transact()
630 .encode_state_as_update_v1(&Default::default());
631 let doc = Doc::new();
632 doc.transact_mut()
633 .apply_update(Update::decode_v1(&doc_state).unwrap())
634 .unwrap();
635 let path = JsonPath::parse("$.users[*].name").unwrap();
636 let tx = doc.transact();
637 let values: Vec<_> = tx.json_path(&path).collect();
638 assert_eq!(
639 values,
640 vec![
641 Out::Any(any!("Alice")),
642 Out::Any(any!("Bob")),
643 Out::Any(any!("Damian")),
644 Out::Any(any!("Elise"))
645 ]
646 );
647 }
648}
649