1use std::cmp::Ordering;
2use std::collections::HashMap;
3use std::ops::Deref;
4use std::str::FromStr;
5
6use common::fmt::EscapeKwFreeIdent;
7use reblessive::Stack;
8use revision::{DeserializeRevisioned, Revisioned, SerializeRevisioned};
9use surrealdb_strand::Strand;
10use surrealdb_types::{SqlFormat, ToSql};
11
12use crate::expr::paths::{ID, IN, OUT};
13use crate::expr::statements::info::InfoStructure;
14use crate::expr::{Expr, Literal, Part, Value};
15
16pub mod recursion;
17
18#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
19#[allow(dead_code)]
20pub struct Idioms(pub Vec<Idiom>);
21
22impl Deref for Idioms {
23 type Target = Vec<Idiom>;
24 fn deref(&self) -> &Self::Target {
25 &self.0
26 }
27}
28
29impl IntoIterator for Idioms {
30 type Item = Idiom;
31 type IntoIter = std::vec::IntoIter<Self::Item>;
32 fn into_iter(self) -> Self::IntoIter {
33 self.0.into_iter()
34 }
35}
36
37impl PartialOrd for Idioms {
38 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
39 Some(self.cmp(other))
40 }
41}
42
43impl Ord for Idioms {
44 fn cmp(&self, other: &Self) -> Ordering {
45 for (a, b) in self.0.iter().zip(other.0.iter()) {
46 let o = a.cmp(b);
47 if o != Ordering::Equal {
48 return o;
49 }
50 }
51 Ordering::Equal
52 }
53}
54
55#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
57pub struct Idiom(pub Vec<Part>);
58
59impl Idiom {
60 pub fn field(field_name: impl Into<Strand>) -> Self {
62 Idiom(vec![Part::Field(field_name.into())])
63 }
64
65 pub fn push(mut self, n: Part) -> Idiom {
67 self.0.push(n);
68 self
69 }
70 pub fn simplify(&self) -> Idiom {
72 self.0
73 .iter()
74 .filter(|&p| matches!(p, Part::Field(_) | Part::Start(_) | Part::Lookup(_)))
75 .cloned()
76 .collect::<Vec<_>>()
77 .into()
78 }
79 pub fn is_id(&self) -> bool {
81 self.0.len() == 1 && self.0[0].eq(&ID[0])
82 }
83 pub fn is_special(&self) -> bool {
85 self.0.len() == 1 && [&ID[0], &IN[0], &OUT[0]].contains(&&self.0[0])
86 }
87
88 pub fn to_raw_string(&self) -> String {
90 use std::fmt::Write;
91
92 let mut s = String::new();
93
94 let mut iter = self.0.iter();
95 match iter.next() {
96 Some(Part::Field(v)) => {
97 write!(&mut s, "{}", EscapeKwFreeIdent(v).to_sql()).expect("writing to string")
98 }
99 Some(x) => s.push_str(&x.to_raw_string()),
100 None => {}
101 };
102
103 for p in iter {
104 s.push_str(&p.to_raw_string());
105 }
106
107 s
108 }
109
110 pub fn is_multi_yield(&self) -> bool {
112 self.iter().any(Self::part_is_multi_yield)
113 }
114 pub fn part_is_multi_yield(v: &Part) -> bool {
116 matches!(v, Part::Lookup(g) if g.alias.is_some())
117 }
118
119 pub fn starts_with(&self, other: &[Part]) -> bool {
121 self.0.starts_with(other)
122 }
123
124 pub fn read_only(&self) -> bool {
126 self.0.iter().all(|v| v.read_only())
127 }
128
129 pub fn validate_local(&self, into: &str) -> anyhow::Result<()> {
134 for part in self.iter() {
135 match part {
136 Part::Field(_) | Part::All | Part::Flatten | Part::First | Part::Last => {}
137 Part::Value(Expr::Literal(lit)) => match lit {
138 Literal::Integer(_) | Literal::String(_) => {}
139 _ => {
140 return Err(anyhow::anyhow!(
141 "Invalid {into}: Field path index must evaluate to an integer or string"
142 ));
143 }
144 },
145 _ => {
146 return Err(anyhow::anyhow!(
147 "Invalid {into}: contains a part that is not allowed in a field path"
148 ));
149 }
150 }
151 }
152 Ok(())
153 }
154}
155
156impl Deref for Idiom {
157 type Target = [Part];
158 fn deref(&self) -> &Self::Target {
159 self.0.as_slice()
160 }
161}
162
163impl From<Vec<Part>> for Idiom {
164 fn from(v: Vec<Part>) -> Self {
165 Self(v)
166 }
167}
168
169impl PartialOrd for Idiom {
170 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
171 Some(self.cmp(other))
172 }
173}
174
175impl Ord for Idiom {
176 fn cmp(&self, other: &Self) -> Ordering {
177 for (a, b) in self.0.iter().zip(other.0.iter()) {
178 let o = a.partial_cmp(b).unwrap_or(Ordering::Equal);
179 if o != Ordering::Equal {
180 return o;
181 }
182 }
183
184 self.0.len().cmp(&other.0.len())
187 }
188}
189
190impl ToSql for Idiom {
191 fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
192 let sql_idiom: crate::sql::Idiom = self.clone().into();
193 sql_idiom.fmt_sql(f, sql_fmt);
194 }
195}
196
197impl Idiom {
198 fn parse_str_with_settings(
228 s: &str,
229 settings: crate::syn::parser::ParserSettings,
230 ) -> Result<Self, revision::Error> {
231 let buf = s.as_bytes();
232 let mut stack = Stack::new();
233 let mut parser = crate::syn::parser::Parser::new_with_settings(buf, settings);
234 let idiom = stack
235 .enter(|stk| parser.parse_plain_idiom(stk))
236 .finish()
237 .map_err(|err| revision::Error::Conversion(format!("{err:?}")))?;
238 parser.assert_finished().map_err(|err| revision::Error::Conversion(format!("{err:?}")))?;
239
240 Ok(idiom.into())
241 }
242}
243
244impl FromStr for Idiom {
245 type Err = revision::Error;
246 fn from_str(s: &str) -> Result<Self, Self::Err> {
247 Self::parse_str_with_settings(
250 s,
251 crate::syn::parser::ParserSettings::default_with_experimental(true),
252 )
253 }
254}
255
256impl Revisioned for Idiom {
257 fn revision() -> u16 {
258 1
259 }
260}
261
262impl SerializeRevisioned for Idiom {
263 fn serialize_revisioned<W: std::io::Write>(
264 &self,
265 writer: &mut W,
266 ) -> Result<(), revision::Error> {
267 SerializeRevisioned::serialize_revisioned(&self.to_raw_string(), writer)
268 }
269}
270
271impl DeserializeRevisioned for Idiom {
272 fn deserialize_revisioned<R: std::io::Read>(reader: &mut R) -> Result<Self, revision::Error> {
273 let s: String = DeserializeRevisioned::deserialize_revisioned(reader)?;
274 Idiom::parse_str_with_settings(&s, crate::syn::parser::ParserSettings::STORED_TEXT)
277 }
278}
279
280impl revision::SkipRevisioned for Idiom {
281 fn skip_revisioned<R: std::io::Read>(reader: &mut R) -> Result<(), revision::Error> {
282 <String as revision::SkipRevisioned>::skip_revisioned(reader)
283 }
284}
285
286impl revision::WalkRevisioned for Idiom {
287 type Walker<'r, R: revision::BorrowedReader + 'r> = revision::LeafWalker<'r, Idiom, R>;
288
289 fn walk_revisioned<'r, R: revision::BorrowedReader>(
290 reader: &'r mut R,
291 ) -> Result<Self::Walker<'r, R>, revision::Error> {
292 Ok(revision::LeafWalker::new(reader))
293 }
294}
295
296impl revision::LengthPrefixedBytes for Idiom {}
297
298impl InfoStructure for Idiom {
299 fn structure(self) -> Value {
300 self.to_sql().into()
301 }
302}
303
304#[derive(Debug)]
312pub struct IdiomTrie<T> {
313 pub children: HashMap<Part, IdiomTrie<T>>,
315 pub data: Option<T>,
317}
318
319impl<T: Clone + std::fmt::Debug> Default for IdiomTrie<T> {
320 fn default() -> Self {
321 Self::new()
322 }
323}
324
325impl<T: Clone + std::fmt::Debug> IdiomTrie<T> {
326 pub fn new() -> Self {
328 IdiomTrie {
329 children: HashMap::new(),
330 data: None,
331 }
332 }
333
334 pub fn insert(&mut self, path: &[Part], data: T) {
336 let mut node = self;
337 for part in path {
338 node = node.children.entry(part.clone()).or_default();
339 }
340 node.data = Some(data);
341 }
342
343 pub fn contains(&self, path: &[Part]) -> IdiomTrieContains<T> {
350 let mut node = self;
351 let mut last_node_had_data = false;
352
353 for part in path {
354 if let Some(child) = node.children.get(part) {
355 last_node_had_data = child.data.is_some();
356 node = child;
357 } else {
358 last_node_had_data = false;
360 break;
361 }
362 }
363
364 if let Some(data) = node.data.as_ref() {
365 if last_node_had_data {
366 IdiomTrieContains::Exact(data.clone())
367 } else {
368 IdiomTrieContains::Ancestor(data.clone())
369 }
370 } else {
371 IdiomTrieContains::None
372 }
373 }
374}
375
376pub enum IdiomTrieContains<T> {
378 None,
380 Exact(T),
382 Ancestor(T),
384}
385
386#[cfg(test)]
387mod tests {
388 use rstest::rstest;
389
390 use super::*;
391
392 #[rstest]
393 #[case(Idiom::from(vec![Part::Field(Strand::new_static("name"))]), "name")]
394 #[case(Idiom::from(vec![Part::Field(Strand::new_static("nested")), Part::Field(Strand::new_static("nested")), Part::Field(Strand::new_static("name"))]), "nested.nested.name")]
395 #[case(Idiom::from(vec![Part::Field(Strand::new_static("nested")), Part::Field(Strand::new_static("nested")), Part::Field(Strand::new_static("value"))]), "nested.nested.value")]
396 #[case(Idiom::from(vec![Part::Field(Strand::new_static("value"))]), "`value`")]
397 fn test_idiom_to_string(#[case] idiom: Idiom, #[case] expected: &'static str) {
398 assert_eq!(idiom.to_sql(), expected.to_string());
399 }
400
401 #[rstest]
402 #[case(Idiom::from(vec![Part::Field(Strand::new_static("name"))]), "name")]
403 #[case(Idiom::from(vec![Part::Field(Strand::new_static("nested")), Part::Field(Strand::new_static("nested")), Part::Field(Strand::new_static("name"))]), "nested.nested.name")]
404 #[case(Idiom::from(vec![Part::Field(Strand::new_static("nested")), Part::Field(Strand::new_static("nested")), Part::Field(Strand::new_static("value"))]), "nested.nested.value")]
405 #[case(Idiom::from(vec![Part::Field(Strand::new_static("value"))]), "value")]
406 fn test_idiom_to_raw_string(#[case] idiom: Idiom, #[case] expected: &'static str) {
407 assert_eq!(idiom.to_raw_string(), expected.to_string());
408 }
409
410 #[rstest]
411 #[case(
413 vec![Idiom::from(vec![Part::Field(Strand::new_static("b"))]), Idiom::from(vec![Part::Field(Strand::new_static("a"))])],
414 vec![Idiom::from(vec![Part::Field(Strand::new_static("a"))]), Idiom::from(vec![Part::Field(Strand::new_static("b"))])]
415 )]
416 #[case(
418 vec![Idiom::from(vec![Part::Field(Strand::new_static("a")), Part::Field(Strand::new_static("b"))]), Idiom::from(vec![Part::Field(Strand::new_static("a"))])],
419 vec![Idiom::from(vec![Part::Field(Strand::new_static("a"))]), Idiom::from(vec![Part::Field(Strand::new_static("a")), Part::Field(Strand::new_static("b"))])]
420 )]
421 #[case(
423 vec![
424 Idiom::from(vec![Part::Field(Strand::new_static("author")), Part::Field(Strand::new_static("company"))]),
425 Idiom::from(vec![Part::Field(Strand::new_static("author"))])
426 ],
427 vec![
428 Idiom::from(vec![Part::Field(Strand::new_static("author"))]),
429 Idiom::from(vec![Part::Field(Strand::new_static("author")), Part::Field(Strand::new_static("company"))])
430 ]
431 )]
432 #[case(
435 vec![
436 Idiom::from(vec![Part::Field(Strand::new_static("author")), Part::Field(Strand::new_static("company")), Part::Field(Strand::new_static("address"))]),
437 Idiom::from(vec![Part::Field(Strand::new_static("author"))]),
438 Idiom::from(vec![Part::Field(Strand::new_static("author")), Part::Field(Strand::new_static("company"))])
439 ],
440 vec![
441 Idiom::from(vec![Part::Field(Strand::new_static("author"))]),
442 Idiom::from(vec![Part::Field(Strand::new_static("author")), Part::Field(Strand::new_static("company"))]),
443 Idiom::from(vec![Part::Field(Strand::new_static("author")), Part::Field(Strand::new_static("company")), Part::Field(Strand::new_static("address"))])
444 ]
445 )]
446 #[case(
448 vec![
449 Idiom::from(vec![Part::Field(Strand::new_static("d"))]),
450 Idiom::from(vec![Part::Field(Strand::new_static("a")), Part::Field(Strand::new_static("b")), Part::Field(Strand::new_static("c"))]),
451 Idiom::from(vec![Part::Field(Strand::new_static("b"))]),
452 Idiom::from(vec![Part::Field(Strand::new_static("a"))]),
453 Idiom::from(vec![Part::Field(Strand::new_static("a")), Part::Field(Strand::new_static("b"))])
454 ],
455 vec![
456 Idiom::from(vec![Part::Field(Strand::new_static("a"))]),
457 Idiom::from(vec![Part::Field(Strand::new_static("a")), Part::Field(Strand::new_static("b"))]),
458 Idiom::from(vec![Part::Field(Strand::new_static("a")), Part::Field(Strand::new_static("b")), Part::Field(Strand::new_static("c"))]),
459 Idiom::from(vec![Part::Field(Strand::new_static("b"))]),
460 Idiom::from(vec![Part::Field(Strand::new_static("d"))])
461 ]
462 )]
463 #[case(
465 vec![
466 Idiom::from(vec![Part::Field(Strand::new_static("a")), Part::All]),
467 Idiom::from(vec![Part::Field(Strand::new_static("a")), Part::Field(Strand::new_static("b"))])
468 ],
469 vec![
470 Idiom::from(vec![Part::Field(Strand::new_static("a")), Part::Field(Strand::new_static("b"))]),
471 Idiom::from(vec![Part::Field(Strand::new_static("a")), Part::All])
472 ]
473 )]
474 fn test_idiom_sorting(#[case] mut idioms: Vec<Idiom>, #[case] expected: Vec<Idiom>) {
475 idioms.sort();
476 assert_eq!(idioms, expected);
477 }
478
479 fn field(name: &'static str) -> Part {
482 Part::Field(Strand::new_static(name))
483 }
484 fn lit_int(n: i64) -> Part {
485 Part::Value(Expr::Literal(Literal::Integer(n)))
486 }
487 fn lit_str(s: &'static str) -> Part {
488 Part::Value(Expr::Literal(Literal::String(Strand::new_static(s))))
489 }
490 fn lit_bool(b: bool) -> Part {
491 Part::Value(Expr::Literal(Literal::Bool(b)))
492 }
493
494 fn lit_float(f: f64) -> Part {
495 Part::Value(Expr::Literal(Literal::Float(f)))
496 }
497
498 #[rstest]
499 #[case(Idiom::from(vec![field("foo")]), true)]
501 #[case(Idiom::from(vec![field("foo"), field("bar")]), true)]
503 #[case(Idiom::from(vec![field("foo"), lit_int(0)]), true)]
505 #[case(Idiom::from(vec![field("foo"), lit_str("city")]), true)]
507 #[case(Idiom::from(vec![field("foo"), Part::All]), true)]
509 #[case(Idiom::from(vec![field("foo"), Part::Flatten]), true)]
511 #[case(Idiom::from(vec![field("foo"), Part::First]), true)]
512 #[case(Idiom::from(vec![field("foo"), Part::Last]), true)]
513 #[case(Idiom::from(vec![field("foo"), Part::Optional]), false)]
515 #[case(Idiom::from(vec![field("foo"), lit_float(1.5)]), false)]
517 #[case(Idiom::from(vec![field("foo"), lit_bool(true)]), false)]
519 #[case(Idiom::from(vec![field("foo"), Part::Value(Expr::Param(crate::expr::Param::from("n".to_owned())))]), false)]
521 #[case(Idiom::from(vec![field("foo"), Part::Where(Expr::Literal(Literal::Bool(true)))]), false)]
523 #[case(Idiom::from(vec![field("foo"), Part::Method(Strand::new_static("bar"), vec![])]), false)]
525 fn test_idiom_validate_local(#[case] idiom: Idiom, #[case] expected: bool) {
526 assert_eq!(idiom.validate_local("field name").is_ok(), expected);
527 }
528
529 #[test]
530 fn test_validate_local_error_message_for_bracket() {
531 let idiom = Idiom::from(vec![field("foo"), lit_bool(true)]);
532 let err = idiom.validate_local("field name").unwrap_err().to_string();
533 assert!(
534 err.contains("Field path index must evaluate to an integer"),
535 "unexpected error: {err}"
536 );
537 }
538
539 #[test]
540 fn test_validate_local_error_message_for_disallowed_part() {
541 let idiom =
542 Idiom::from(vec![field("foo"), Part::Method(Strand::new_static("bar"), vec![])]);
543 let err = idiom.validate_local("field name").unwrap_err().to_string();
544 assert!(err.contains("not allowed in a field path"), "unexpected error: {err}");
545 }
546
547 #[test]
548 fn idiom_with_bytes_matches_serialize() {
549 use revision::{SerializeRevisioned, WalkRevisioned};
550
551 let idiom = Idiom::from(vec![Part::Field(Strand::new_static("foo"))]);
552 let mut bytes = Vec::new();
553 idiom.serialize_revisioned(&mut bytes).unwrap();
554 let expected_raw = idiom.to_raw_string();
555 let mut r = bytes.as_slice();
556 let walker = Idiom::walk_revisioned(&mut r).unwrap();
557 let observed = walker.with_bytes(|raw| raw.to_vec()).unwrap();
558 assert_eq!(observed.as_slice(), expected_raw.as_bytes());
559 assert!(r.is_empty());
560 }
561
562 #[rstest]
570 #[case::field(Idiom::from(vec![field("foo")]))]
571 #[case::nested_field(Idiom::from(vec![field("foo"), field("bar")]))]
572 #[case::int_index(Idiom::from(vec![field("data"), lit_int(0)]))]
573 #[case::all(Idiom::from(vec![field("foo"), Part::All]))]
574 #[case::last(Idiom::from(vec![field("foo"), Part::Last]))]
575 #[case::flatten(Idiom::from(vec![field("foo"), Part::Flatten]))]
576 #[case::method(Idiom::from(vec![field("id"), Part::Method(Strand::new_static("id"), vec![]), field("val")]))]
577 #[case::destructure(Idiom::from(vec![
578 field("addr"),
579 Part::Destructure(vec![
580 crate::expr::part::DestructurePart::Field(Strand::new_static("city")),
581 crate::expr::part::DestructurePart::Field(Strand::new_static("zip")),
582 ]),
583 ]))]
584 #[case::keyword_field(Idiom::from(vec![field("function")]))]
590 #[case::keyword_field_select(Idiom::from(vec![field("select")]))]
591 #[case::keyword_field_nested(Idiom::from(vec![field("function"), field("sub")]))]
592 #[case::keyword_field_tail(Idiom::from(vec![field("meta"), field("function")]))]
593 #[case::keyword_field_index(Idiom::from(vec![field("select"), lit_int(0)]))]
594 fn idiom_revisioned_roundtrip(#[case] idiom: Idiom) {
595 let mut bytes = Vec::new();
596 idiom.serialize_revisioned(&mut bytes).unwrap();
597 let parsed = Idiom::deserialize_revisioned(&mut bytes.as_slice())
598 .unwrap_or_else(|e| panic!("round-trip failed for {idiom:?}: {e}"));
599 assert_eq!(idiom, parsed, "round-trip mismatch for {idiom:?}");
600 }
601
602 #[test]
609 fn every_reserved_keyword_round_trips_as_a_field_name() {
610 for kw in common::keywords::RESERVED_KEYWORD.iter() {
611 let kw = kw.into_inner();
612 for idiom in [
613 Idiom::from(vec![field(kw)]),
614 Idiom::from(vec![field(kw), field("tail")]),
615 Idiom::from(vec![field("head"), field(kw)]),
616 ] {
617 let mut bytes = Vec::new();
618 idiom.serialize_revisioned(&mut bytes).unwrap();
619 let parsed =
620 Idiom::deserialize_revisioned(&mut bytes.as_slice()).unwrap_or_else(|e| {
621 panic!("keyword `{kw}` did not survive the wire round-trip: {e}")
622 });
623 assert_eq!(idiom, parsed, "round-trip mismatch for keyword `{kw}`");
624 }
625 }
626 }
627
628 #[test]
638 fn an_expression_rooted_idiom_is_not_a_field_path() {
639 let idiom = Idiom::from(vec![
640 Part::Start(Expr::Param(crate::expr::Param::from("test".to_string()))),
641 field("temporary"),
642 ]);
643 assert_eq!(idiom.to_raw_string(), "$test.temporary");
644 assert!(
645 Idiom::from_str(&idiom.to_raw_string()).is_err(),
646 "an expression-rooted idiom should not parse as a field path"
647 );
648 }
649
650 #[test]
651 fn trailing_content_after_an_idiom_is_rejected() {
652 for s in ["foo bogus", "function bogus", "foo.bar baz"] {
653 assert!(Idiom::from_str(s).is_err(), "`{s}` should not parse to just its idiom prefix");
654 }
655 }
656}