gqlite 1.7.0 → 1.8.1

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.
@@ -313,7 +313,7 @@ fn parameter_name(input: Input<'_>) -> OCPResult<Input<'_>, &str>
313
313
  }
314
314
  // Literals
315
315
 
316
- fn string_literal(input: Input<'_>) -> OCPResult<Input<'_>, &str>
316
+ fn string_literal(input: Input<'_>) -> OCPResult<Input<'_>, String>
317
317
  {
318
318
  match input.0
319
319
  {
@@ -327,7 +327,10 @@ fn string_literal(input: Input<'_>) -> OCPResult<Input<'_>, &str>
327
327
  {
328
328
  let lit = input.1.extract_text(span);
329
329
 
330
- Ok((Input(rest, input.1), &lit[1..lit.len() - 1]))
330
+ Ok((
331
+ Input(rest, input.1),
332
+ super::string::unescape_cypher(&lit[1..lit.len() - 1]),
333
+ ))
331
334
  }
332
335
  _ => Err(nom::Err::Error(Error::new(
333
336
  ErrorKind::ExpectedTokens(vec![Token::String].into()),
@@ -440,11 +443,21 @@ fn function_call(input: Input) -> OCPResult<Input, ast::Expression>
440
443
  to_string(ident),
441
444
  delimited(
442
445
  token(Token::StartParenthesis),
443
- separated_list0(token(Token::Comma), expression),
446
+ pair(
447
+ map(opt(token(Token::Distinct)), |d| d.is_some()),
448
+ separated_list0(token(Token::Comma), expression),
449
+ ),
444
450
  token(Token::EndParenthesis),
445
451
  ),
446
452
  ),
447
- |(name, arguments)| ast::FunctionCall { name, arguments }.into(),
453
+ |(name, (distinct, arguments))| {
454
+ ast::FunctionCall {
455
+ name,
456
+ arguments,
457
+ distinct,
458
+ }
459
+ .into()
460
+ },
448
461
  )
449
462
  .parse(input)
450
463
  }
@@ -483,9 +496,9 @@ fn primary(input: Input) -> OCPResult<Input, ast::Expression>
483
496
  .into()
484
497
  }),
485
498
  // String
486
- map(string_literal, |s: &str| {
499
+ map(string_literal, |s| {
487
500
  ast::Value {
488
- value: graphcore::Value::String(s.to_string()),
501
+ value: graphcore::Value::String(s),
489
502
  }
490
503
  .into()
491
504
  }),
@@ -522,7 +535,7 @@ fn member_access(input: Input) -> OCPResult<Input, ast::Expression>
522
535
  primary,
523
536
  many0(preceded(
524
537
  token(Token::Dot),
525
- to_string(alt((ident, string_literal))),
538
+ alt((to_string(ident), string_literal)),
526
539
  )),
527
540
  ),
528
541
  |(left, path)| {
@@ -617,6 +630,7 @@ fn label_check(input: Input) -> OCPResult<Input, ast::Expression>
617
630
  .into()
618
631
  }))
619
632
  .collect(),
633
+ distinct: false,
620
634
  }
621
635
  .into()
622
636
  }
@@ -625,6 +639,57 @@ fn label_check(input: Input) -> OCPResult<Input, ast::Expression>
625
639
  .parse(input)
626
640
  }
627
641
 
642
+ #[derive(Clone, Copy)]
643
+ enum StringOp
644
+ {
645
+ StartsWith,
646
+ EndsWith,
647
+ Contains,
648
+ }
649
+
650
+ fn string_operators(input: Input) -> OCPResult<Input, ast::Expression>
651
+ {
652
+ map(
653
+ (
654
+ label_check,
655
+ opt(
656
+ alt((
657
+ value(
658
+ StringOp::StartsWith,
659
+ (token(Token::Starts), token(Token::With)),
660
+ ),
661
+ value(StringOp::EndsWith, (token(Token::Ends), token(Token::With))),
662
+ value(StringOp::Contains, token(Token::Contains)),
663
+ ))
664
+ .and(label_check),
665
+ ),
666
+ ),
667
+ |(lhs, op)| match op
668
+ {
669
+ None => lhs,
670
+ Some(op) => match op.0
671
+ {
672
+ StringOp::StartsWith => ast::StartsWith {
673
+ left: lhs,
674
+ right: op.1,
675
+ }
676
+ .into(),
677
+ StringOp::EndsWith => ast::EndsWith {
678
+ left: lhs,
679
+ right: op.1,
680
+ }
681
+ .into(),
682
+ StringOp::Contains => ast::Contains {
683
+ left: lhs,
684
+ right: op.1,
685
+ }
686
+ .into(),
687
+ },
688
+ },
689
+ )
690
+ .parse(input)
691
+ }
692
+
628
693
  fn unary(input: Input) -> OCPResult<Input, ast::Expression>
629
694
  {
630
695
  alt((
@@ -671,9 +736,6 @@ fn unary(input: Input) -> OCPResult<Input, ast::Expression>
671
736
  )
672
737
  },
673
738
  ),
674
- map(preceded(token(Token::Not), unary), |e| {
675
- ast::LogicalNegation { value: e }.into()
676
- }),
677
739
  map(preceded(token(Token::Minus), unary), |e| match e
678
740
  {
679
741
  ast::Expression::Value(ast::Value {
@@ -691,7 +753,7 @@ fn unary(input: Input) -> OCPResult<Input, ast::Expression>
691
753
  _ => ast::Negation { value: e }.into(),
692
754
  }),
693
755
  preceded(token(Token::Plus), unary),
694
- label_check,
756
+ string_operators,
695
757
  ))
696
758
  .parse(input)
697
759
  }
@@ -811,17 +873,42 @@ fn relational(input: Input) -> OCPResult<Input, ast::Expression>
811
873
  }
812
874
  else
813
875
  {
876
+ // Check for NOT IN pattern
877
+ if let Ok((after_not, _)) = token(Token::Not).parse(input.clone())
878
+ && let Ok((after_in, _)) = token(Token::In).parse(after_not)
879
+ {
880
+ let (final_input, right) = is_not_null(after_in)?;
881
+ let in_expr = ast::RelationalIn { left, right }.into();
882
+ let negated = ast::LogicalNegation { value: in_expr }.into();
883
+ return Ok((final_input, negated));
884
+ }
814
885
  Ok((input, left))
815
886
  }
816
887
  }
817
888
 
889
+ fn not_expression(input: Input) -> OCPResult<Input, ast::Expression>
890
+ {
891
+ map(
892
+ pair(many0(token(Token::Not)), relational),
893
+ |(nots, expr)| {
894
+ nots
895
+ .into_iter()
896
+ .fold(expr, |e, _| ast::LogicalNegation { value: e }.into())
897
+ },
898
+ )
899
+ .parse(input)
900
+ }
901
+
818
902
  fn logical_and(input: Input) -> OCPResult<Input, ast::Expression>
819
903
  {
820
- map(separated_list1(token(Token::And), relational), |mut v| {
821
- let first = v.remove(0);
822
- v.into_iter()
823
- .fold(first, |l, r| ast::LogicalAnd { left: l, right: r }.into())
824
- })
904
+ map(
905
+ separated_list1(token(Token::And), not_expression),
906
+ |mut v| {
907
+ let first = v.remove(0);
908
+ v.into_iter()
909
+ .fold(first, |l, r| ast::LogicalAnd { left: l, right: r }.into())
910
+ },
911
+ )
825
912
  .parse(input)
826
913
  }
827
914
 
@@ -907,7 +994,7 @@ fn labels(input: Input) -> OCPResult<Input, ast::LabelExpression>
907
994
  fn map_pair(input: Input) -> OCPResult<Input, (String, ast::Expression)>
908
995
  {
909
996
  separated_pair(
910
- to_string(alt((ident, string_literal))),
997
+ alt((to_string(ident), string_literal)),
911
998
  token(Token::Colon),
912
999
  expression,
913
1000
  )
@@ -1656,15 +1743,16 @@ fn return_statement(input: Input) -> OCPResult<Input, ast::Statement>
1656
1743
  map(
1657
1744
  (
1658
1745
  token(Token::Return),
1746
+ opt(token(Token::Distinct)),
1659
1747
  with_return_expression,
1660
1748
  modifiers,
1661
1749
  opt(preceded(token(Token::Where), expression)),
1662
1750
  ),
1663
- |(_, (all, expressions), modifiers, where_expression)| {
1751
+ |(_, distinct, (all, expressions), modifiers, where_expression)| {
1664
1752
  ast::Return {
1665
1753
  all,
1666
1754
  expressions,
1667
- modifiers,
1755
+ modifiers: modifiers.set_distinct(distinct.is_some()),
1668
1756
  where_expression,
1669
1757
  }
1670
1758
  .into()
@@ -1678,15 +1766,16 @@ fn with_statement(input: Input) -> OCPResult<Input, ast::Statement>
1678
1766
  map(
1679
1767
  (
1680
1768
  token(Token::With),
1769
+ opt(token(Token::Distinct)),
1681
1770
  with_return_expression,
1682
1771
  modifiers,
1683
1772
  opt(preceded(token(Token::Where), expression)),
1684
1773
  ),
1685
- |(_, (all, expressions), modifiers, where_expression)| {
1774
+ |(_, distinct, (all, expressions), modifiers, where_expression)| {
1686
1775
  ast::With {
1687
1776
  all,
1688
1777
  expressions,
1689
- modifiers,
1778
+ modifiers: modifiers.set_distinct(distinct.is_some()),
1690
1779
  where_expression,
1691
1780
  }
1692
1781
  .into()
@@ -1747,7 +1836,7 @@ fn parse_set_actions(input: Input) -> OCPResult<Input, Vec<ast::OneUpdate>>
1747
1836
  )),
1748
1837
  many0(preceded(
1749
1838
  token(Token::Dot),
1750
- to_string(alt((ident, string_literal))),
1839
+ alt((to_string(ident), string_literal)),
1751
1840
  )),
1752
1841
  token(Token::Equal),
1753
1842
  expression,
@@ -1772,7 +1861,7 @@ fn parse_set_actions(input: Input) -> OCPResult<Input, Vec<ast::OneUpdate>>
1772
1861
  )),
1773
1862
  many0(preceded(
1774
1863
  token(Token::Dot),
1775
- to_string(alt((ident, string_literal))),
1864
+ alt((to_string(ident), string_literal)),
1776
1865
  )),
1777
1866
  token(Token::PlusEqual),
1778
1867
  expression,
@@ -1824,7 +1913,7 @@ fn remove_statement(input: Input) -> OCPResult<Input, ast::Statement>
1824
1913
  ident,
1825
1914
  many1(preceded(
1826
1915
  token(Token::Dot),
1827
- to_string(alt((ident, string_literal))),
1916
+ alt((to_string(ident), string_literal)),
1828
1917
  )),
1829
1918
  ),
1830
1919
  |(ident, path)| {
@@ -1884,13 +1973,7 @@ fn call_statement(input: Input) -> OCPResult<Input, ast::Statement>
1884
1973
  fn use_statement(input: Input) -> OCPResult<Input, ast::Statement>
1885
1974
  {
1886
1975
  map(
1887
- (
1888
- token(Token::Use),
1889
- alt((
1890
- map(string_literal, |s| s.to_string()),
1891
- map(ident, |s| s.to_string()),
1892
- )),
1893
- ),
1976
+ (token(Token::Use), alt((string_literal, to_string(ident)))),
1894
1977
  |(_, name)| ast::UseGraph { name }.into(),
1895
1978
  )
1896
1979
  .parse(input)
@@ -1903,10 +1986,7 @@ fn create_graph_statement(input: Input) -> OCPResult<Input, ast::Statement>
1903
1986
  token(Token::Create),
1904
1987
  token(Token::Graph),
1905
1988
  opt((token(Token::If), token(Token::Not), token(Token::Exists))),
1906
- alt((
1907
- map(string_literal, |s| s.to_string()),
1908
- map(ident, |s| s.to_string()),
1909
- )),
1989
+ alt((string_literal, to_string(ident))),
1910
1990
  ),
1911
1991
  |(_, _, if_not_exists, name)| {
1912
1992
  ast::CreateGraph {
@@ -1926,10 +2006,7 @@ fn drop_graph_statement(input: Input) -> OCPResult<Input, ast::Statement>
1926
2006
  token(Token::Drop),
1927
2007
  token(Token::Graph),
1928
2008
  opt((token(Token::If), token(Token::Exists))),
1929
- alt((
1930
- map(string_literal, |s| s.to_string()),
1931
- map(ident, |s| s.to_string()),
1932
- )),
2009
+ alt((string_literal, to_string(ident))),
1933
2010
  ),
1934
2011
  |(_, _, if_exists, name)| {
1935
2012
  ast::DropGraph {
@@ -0,0 +1,172 @@
1
+ use logos::Logos;
2
+
3
+ #[derive(Logos, Debug, PartialEq)]
4
+ enum CypherEscapeToken<'a>
5
+ {
6
+ #[regex(r#"[^\\]+"#, |lex| lex.slice())]
7
+ Text(&'a str),
8
+
9
+ #[token(r"\t")]
10
+ Tab,
11
+
12
+ #[token(r"\b")]
13
+ Backspace,
14
+
15
+ #[token(r"\n")]
16
+ Newline,
17
+
18
+ #[token(r"\r")]
19
+ CarriageReturn,
20
+
21
+ #[token(r"\f")]
22
+ FormFeed,
23
+
24
+ #[token(r"\'")]
25
+ SingleQuote,
26
+
27
+ #[token(r#"\""#)]
28
+ DoubleQuote,
29
+
30
+ #[token(r"\\")]
31
+ Backslash,
32
+
33
+ #[regex(r"\\u[0-9a-fA-F]{4}", |lex| lex.slice())]
34
+ Unicode16(&'a str),
35
+
36
+ #[regex(r"\\U[0-9a-fA-F]{8}", |lex| lex.slice())]
37
+ Unicode32(&'a str),
38
+ }
39
+
40
+ pub(crate) fn unescape_cypher(input: &str) -> String
41
+ {
42
+ let mut output = String::with_capacity(input.len());
43
+
44
+ for token in CypherEscapeToken::lexer(input)
45
+ {
46
+ match token.expect("invalid Cypher escape")
47
+ {
48
+ CypherEscapeToken::Text(s) => output.push_str(s),
49
+ CypherEscapeToken::Tab => output.push('\t'),
50
+ CypherEscapeToken::Backspace => output.push('\u{0008}'),
51
+ CypherEscapeToken::Newline => output.push('\n'),
52
+ CypherEscapeToken::CarriageReturn => output.push('\r'),
53
+ CypherEscapeToken::FormFeed => output.push('\u{000C}'),
54
+ CypherEscapeToken::SingleQuote => output.push('\''),
55
+ CypherEscapeToken::DoubleQuote => output.push('"'),
56
+ CypherEscapeToken::Backslash => output.push('\\'),
57
+
58
+ CypherEscapeToken::Unicode16(s) =>
59
+ {
60
+ let value = u16::from_str_radix(&s[2..], 16).expect("invalid UTF-16 escape");
61
+
62
+ let ch = char::from_u32(value as u32).expect("invalid Unicode scalar");
63
+
64
+ output.push(ch);
65
+ }
66
+
67
+ CypherEscapeToken::Unicode32(s) =>
68
+ {
69
+ let value = u32::from_str_radix(&s[2..], 16).expect("invalid UTF-32 escape");
70
+
71
+ output.push(char::from_u32(value).expect("invalid Unicode scalar"));
72
+ }
73
+ }
74
+ }
75
+
76
+ output
77
+ }
78
+
79
+ #[cfg(test)]
80
+ mod tests
81
+ {
82
+ use super::unescape_cypher;
83
+
84
+ #[test]
85
+ fn plain_text()
86
+ {
87
+ assert_eq!(unescape_cypher("hello world"), "hello world");
88
+ }
89
+
90
+ #[test]
91
+ fn tab()
92
+ {
93
+ assert_eq!(unescape_cypher(r"foo\tbar"), "foo\tbar");
94
+ }
95
+
96
+ #[test]
97
+ fn backspace()
98
+ {
99
+ assert_eq!(unescape_cypher(r"foo\bbar"), "foo\u{0008}bar");
100
+ }
101
+
102
+ #[test]
103
+ fn newline()
104
+ {
105
+ assert_eq!(unescape_cypher(r"foo\nbar"), "foo\nbar");
106
+ }
107
+
108
+ #[test]
109
+ fn carriage_return()
110
+ {
111
+ assert_eq!(unescape_cypher(r"foo\rbar"), "foo\rbar");
112
+ }
113
+
114
+ #[test]
115
+ fn form_feed()
116
+ {
117
+ assert_eq!(unescape_cypher(r"foo\fbar"), "foo\u{000C}bar");
118
+ }
119
+
120
+ #[test]
121
+ fn quotes()
122
+ {
123
+ assert_eq!(unescape_cypher(r#"\'hello\""#), "'hello\"");
124
+ }
125
+
126
+ #[test]
127
+ fn backslash()
128
+ {
129
+ assert_eq!(unescape_cypher(r"foo\\bar"), r"foo\bar");
130
+ }
131
+
132
+ #[test]
133
+ fn unicode_16()
134
+ {
135
+ assert_eq!(unescape_cypher(r"\u0041"), "A");
136
+
137
+ assert_eq!(unescape_cypher(r"\u03BB"), "λ");
138
+ }
139
+
140
+ #[test]
141
+ fn unicode_32()
142
+ {
143
+ assert_eq!(unescape_cypher(r"\U0001F600"), "😀");
144
+ }
145
+
146
+ #[test]
147
+ fn mixed_sequences()
148
+ {
149
+ assert_eq!(
150
+ unescape_cypher(r"Hello\nWorld\t\u0021\U0001F600"),
151
+ "Hello\nWorld\t!😀"
152
+ );
153
+ }
154
+
155
+ #[test]
156
+ fn multiple_text_chunks()
157
+ {
158
+ assert_eq!(unescape_cypher(r"abc\n123\txyz"), "abc\n123\txyz");
159
+ }
160
+
161
+ #[test]
162
+ fn empty_string()
163
+ {
164
+ assert_eq!(unescape_cypher(""), "");
165
+ }
166
+
167
+ #[test]
168
+ fn only_escape_sequences()
169
+ {
170
+ assert_eq!(unescape_cypher("\n\t\r"), "\n\t\r");
171
+ }
172
+ }
@@ -6,6 +6,7 @@ pub mod ast;
6
6
  mod error;
7
7
  mod lexer;
8
8
  mod parser;
9
+ mod string;
9
10
 
10
11
  pub use error::{Error, ErrorKind};
11
12
 
@@ -7,7 +7,7 @@ use std::{
7
7
 
8
8
  use crate::{Value, prelude::*};
9
9
 
10
- type ValueMapInner = std::collections::HashMap<String, Value>;
10
+ type ValueMapInner = std::collections::BTreeMap<String, Value>;
11
11
 
12
12
  /// A map of values.
13
13
  #[derive(Debug, PartialEq, Default, Clone, Deserialize, Serialize)]
@@ -249,7 +249,7 @@ impl FromIterator<(String, Value)> for ValueMap
249
249
  /// all but one of the corresponding values will be dropped.
250
250
  fn from_iter<T: IntoIterator<Item = (String, Value)>>(iter: T) -> ValueMap
251
251
  {
252
- let mut map = ValueMapInner::with_hasher(Default::default());
252
+ let mut map = ValueMapInner::new();
253
253
  map.extend(iter);
254
254
  ValueMap(map)
255
255
  }
@@ -122,6 +122,7 @@ impl Hash for Value
122
122
  {
123
123
  fn hash<H: std::hash::Hasher>(&self, state: &mut H)
124
124
  {
125
+ std::mem::discriminant(self).hash(state);
125
126
  match self
126
127
  {
127
128
  Value::Null =>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gqlite
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyrille Berger
@@ -233,6 +233,7 @@ files:
233
233
  - ext/gqlparser/src/oc/lexer.rs
234
234
  - ext/gqlparser/src/oc/parser.rs
235
235
  - ext/gqlparser/src/oc/parser/tests.rs
236
+ - ext/gqlparser/src/oc/string.rs
236
237
  - ext/gqlparser/src/prelude.rs
237
238
  - ext/graphcore/Cargo.toml
238
239
  - ext/graphcore/README.MD