gqlite 1.5.0 → 1.7.0

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.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/ext/Cargo.toml +12 -6
  3. data/ext/db-index/Cargo.toml +29 -0
  4. data/ext/db-index/src/hnsw/error.rs +66 -0
  5. data/ext/db-index/src/hnsw/graph_store.rs +272 -0
  6. data/ext/db-index/src/hnsw/id.rs +36 -0
  7. data/ext/db-index/src/hnsw/implementation.rs +1517 -0
  8. data/ext/db-index/src/hnsw/kernels.rs +1228 -0
  9. data/ext/db-index/src/hnsw/metric.rs +244 -0
  10. data/ext/db-index/src/hnsw/scalar.rs +72 -0
  11. data/ext/db-index/src/hnsw/simple_store.rs +140 -0
  12. data/ext/db-index/src/hnsw/store.rs +472 -0
  13. data/ext/db-index/src/hnsw/vector.rs +105 -0
  14. data/ext/db-index/src/hnsw/vectors.rs +568 -0
  15. data/ext/db-index/src/hnsw.rs +42 -0
  16. data/ext/db-index/src/lib.rs +3 -0
  17. data/ext/gqlitedb/Cargo.toml +19 -8
  18. data/ext/gqlitedb/benches/common/pokec.rs +62 -2
  19. data/ext/gqlitedb/benches/pokec_divan.rs +66 -2
  20. data/ext/gqlitedb/benches/pokec_iai.rs +60 -3
  21. data/ext/gqlitedb/release.toml +2 -2
  22. data/ext/gqlitedb/src/aggregators/arithmetic.rs +3 -1
  23. data/ext/gqlitedb/src/aggregators/containers.rs +1 -1
  24. data/ext/gqlitedb/src/aggregators/stats.rs +21 -12
  25. data/ext/gqlitedb/src/capi.rs +20 -24
  26. data/ext/gqlitedb/src/compiler/expression_analyser.rs +28 -20
  27. data/ext/gqlitedb/src/compiler/variables_manager.rs +104 -38
  28. data/ext/gqlitedb/src/compiler.rs +506 -227
  29. data/ext/gqlitedb/src/connection.rs +151 -11
  30. data/ext/gqlitedb/src/consts.rs +1 -2
  31. data/ext/gqlitedb/src/error.rs +123 -61
  32. data/ext/gqlitedb/src/functions/common.rs +64 -0
  33. data/ext/gqlitedb/src/functions/containers.rs +2 -46
  34. data/ext/gqlitedb/src/functions/edge.rs +1 -1
  35. data/ext/gqlitedb/src/functions/math.rs +87 -15
  36. data/ext/gqlitedb/src/functions/node.rs +1 -1
  37. data/ext/gqlitedb/src/functions/path.rs +48 -11
  38. data/ext/gqlitedb/src/functions/scalar.rs +47 -4
  39. data/ext/gqlitedb/src/functions/string.rs +123 -1
  40. data/ext/gqlitedb/src/functions/value.rs +1 -1
  41. data/ext/gqlitedb/src/functions.rs +165 -28
  42. data/ext/gqlitedb/src/graph.rs +1 -9
  43. data/ext/gqlitedb/src/interpreter/evaluators.rs +968 -130
  44. data/ext/gqlitedb/src/interpreter/instructions.rs +39 -2
  45. data/ext/gqlitedb/src/lib.rs +5 -4
  46. data/ext/gqlitedb/src/parser/gql.pest +1 -1
  47. data/ext/gqlitedb/src/planner.rs +329 -0
  48. data/ext/gqlitedb/src/prelude.rs +3 -3
  49. data/ext/gqlitedb/src/store/pgrx.rs +1 -1
  50. data/ext/gqlitedb/src/store/postgres.rs +742 -16
  51. data/ext/gqlitedb/src/store/redb/hnsw_store.rs +702 -0
  52. data/ext/gqlitedb/src/store/redb/index.rs +274 -0
  53. data/ext/gqlitedb/src/store/redb.rs +1268 -113
  54. data/ext/gqlitedb/src/store/sqlbase/sqlmetadata.rs +28 -0
  55. data/ext/gqlitedb/src/store/sqlbase/sqlstore.rs +103 -0
  56. data/ext/gqlitedb/src/store/sqlbase.rs +146 -16
  57. data/ext/gqlitedb/src/store/sqlite.rs +576 -14
  58. data/ext/gqlitedb/src/store/vector_extract.rs +56 -0
  59. data/ext/gqlitedb/src/store.rs +140 -29
  60. data/ext/gqlitedb/src/tests/compiler.rs +207 -10
  61. data/ext/gqlitedb/src/tests/connection/postgres.rs +38 -3
  62. data/ext/gqlitedb/src/tests/connection/redb.rs +23 -0
  63. data/ext/gqlitedb/src/tests/connection/sqlite.rs +31 -0
  64. data/ext/gqlitedb/src/tests/connection.rs +61 -1
  65. data/ext/gqlitedb/src/tests/evaluators.rs +162 -7
  66. data/ext/gqlitedb/src/tests/parser.rs +54 -23
  67. data/ext/gqlitedb/src/tests/planner.rs +511 -0
  68. data/ext/gqlitedb/src/tests/store/postgres.rs +7 -0
  69. data/ext/gqlitedb/src/tests/store/redb.rs +8 -0
  70. data/ext/gqlitedb/src/tests/store/sqlite.rs +8 -0
  71. data/ext/gqlitedb/src/tests/store/vector_index/postgres.rs +182 -0
  72. data/ext/gqlitedb/src/tests/store/vector_index/redb.rs +386 -0
  73. data/ext/gqlitedb/src/tests/store/vector_index/sqlite.rs +166 -0
  74. data/ext/gqlitedb/src/tests/store/vector_index.rs +2313 -0
  75. data/ext/gqlitedb/src/tests/store.rs +78 -14
  76. data/ext/gqlitedb/src/tests/templates/ast.rs +92 -16
  77. data/ext/gqlitedb/src/tests/templates/programs.rs +14 -7
  78. data/ext/gqlitedb/src/tests.rs +15 -9
  79. data/ext/gqlitedb/src/utils.rs +6 -1
  80. data/ext/gqlitedb/src/value/compare.rs +61 -3
  81. data/ext/gqlitedb/src/value.rs +136 -7
  82. data/ext/gqlitedb/templates/sql/postgres/metadata_delete.sql +1 -0
  83. data/ext/gqlitedb/templates/sql/postgres/node_select.sql +1 -1
  84. data/ext/gqlitedb/templates/sql/sqlite/metadata_delete.sql +1 -0
  85. data/ext/gqliterb/src/lib.rs +53 -8
  86. data/ext/gqlparser/Cargo.toml +25 -0
  87. data/ext/gqlparser/README.MD +9 -0
  88. data/ext/gqlparser/benches/pokec_divan.rs +34 -0
  89. data/ext/gqlparser/src/common.rs +69 -0
  90. data/ext/gqlparser/src/gqls/ast.rs +69 -0
  91. data/ext/gqlparser/src/gqls/constraint.rs +79 -0
  92. data/ext/gqlparser/src/gqls/error.rs +22 -0
  93. data/ext/gqlparser/src/gqls/parser.rs +813 -0
  94. data/ext/gqlparser/src/gqls/prelude.rs +8 -0
  95. data/ext/gqlparser/src/gqls/properties.rs +115 -0
  96. data/ext/gqlparser/src/gqls/resolve.rs +207 -0
  97. data/ext/gqlparser/src/gqls.rs +265 -0
  98. data/ext/gqlparser/src/lib.rs +7 -0
  99. data/ext/gqlparser/src/oc/ast.rs +680 -0
  100. data/ext/gqlparser/src/oc/error.rs +172 -0
  101. data/ext/gqlparser/src/oc/lexer.rs +429 -0
  102. data/ext/gqlparser/src/oc/parser/tests.rs +2284 -0
  103. data/ext/gqlparser/src/oc/parser.rs +2005 -0
  104. data/ext/gqlparser/src/oc.rs +33 -0
  105. data/ext/gqlparser/src/prelude.rs +3 -0
  106. data/ext/graphcore/Cargo.toml +1 -0
  107. data/ext/graphcore/src/error.rs +26 -0
  108. data/ext/graphcore/src/graph.rs +177 -51
  109. data/ext/graphcore/src/lib.rs +4 -2
  110. data/ext/graphcore/src/open_cypher.rs +12 -0
  111. data/ext/graphcore/src/table.rs +50 -2
  112. data/ext/graphcore/src/timestamp.rs +127 -104
  113. data/ext/graphcore/src/value/tensor.rs +739 -0
  114. data/ext/graphcore/src/value/value_map.rs +1 -1
  115. data/ext/graphcore/src/value.rs +343 -19
  116. metadata +93 -28
  117. data/ext/gqlitedb/src/parser/ast.rs +0 -604
  118. data/ext/gqlitedb/src/parser/parser_impl.rs +0 -1213
  119. data/ext/gqlitedb/src/parser.rs +0 -4
@@ -0,0 +1,813 @@
1
+ use graphcore::Value;
2
+ use indexmap::IndexMap;
3
+ use nom::{
4
+ Parser,
5
+ branch::alt,
6
+ bytes::complete::{escaped_transform, is_not, tag, take_while1},
7
+ character::complete::{char, digit1, multispace0, one_of},
8
+ combinator::{cut, map, opt, recognize, value},
9
+ multi::{many0, separated_list0, separated_list1},
10
+ sequence::{delimited, pair, preceded, terminated},
11
+ };
12
+
13
+ use crate::{
14
+ common::*,
15
+ gqls::{constraint::Constraint, prelude::*},
16
+ };
17
+
18
+ #[derive(Debug)]
19
+ pub(crate) enum ParseTreeElement
20
+ {
21
+ Property
22
+ {
23
+ label: String,
24
+ properties: IndexMap<String, Property>,
25
+ parent: Option<String>,
26
+ },
27
+ Node
28
+ {
29
+ label: String
30
+ },
31
+ Edge
32
+ {
33
+ source: String,
34
+ label: String,
35
+ destination: String,
36
+ },
37
+ ConstrainedType
38
+ {
39
+ name: String,
40
+ def: crate::gqls::ast::ConstrainedTypeDef,
41
+ },
42
+ }
43
+
44
+ #[inline]
45
+ fn is_label(chr: char) -> bool
46
+ {
47
+ chr.is_alphanumeric() || chr == '_'
48
+ }
49
+
50
+ fn parse_label(input: &str) -> VResult<&str, &str>
51
+ {
52
+ take_while1(is_label).parse(input)
53
+ }
54
+
55
+ #[inline]
56
+ fn is_identifier(chr: char) -> bool
57
+ {
58
+ chr.is_alphanumeric() || chr == '_'
59
+ }
60
+
61
+ fn parse_identifier(input: &str) -> VResult<&str, &str>
62
+ {
63
+ take_while1(is_identifier).parse(input)
64
+ }
65
+
66
+ fn parse_property(input: &str) -> VResult<&str, Property>
67
+ {
68
+ map(
69
+ (
70
+ alt((
71
+ map(tag("STRING"), |_| {
72
+ Property::Literal(LiteralBaseType::String.into())
73
+ }),
74
+ map(tag("FLOAT"), |_| {
75
+ Property::Literal(LiteralBaseType::Float.into())
76
+ }),
77
+ map(tag("INTEGER"), |_| {
78
+ Property::Literal(LiteralBaseType::Integer.into())
79
+ }),
80
+ map(tag("BOOLEAN"), |_| {
81
+ Property::Literal(LiteralBaseType::Boolean.into())
82
+ }),
83
+ map(tag("TIMESTAMP"), |_| {
84
+ Property::Literal(LiteralBaseType::TimeStamp.into())
85
+ }),
86
+ map(parse_property_map, |property_map| {
87
+ Property::Map(property_map)
88
+ }),
89
+ map((tag("["), parse_property, tag("]")), |(_, property, _)| {
90
+ Property::Array(Box::new(property))
91
+ }),
92
+ map(parse_identifier, |name: &str| {
93
+ Property::Named(name.to_string())
94
+ }),
95
+ )),
96
+ opt(ws0(tag("?"))),
97
+ ),
98
+ |(p, q)| match q
99
+ {
100
+ Some(_) => Property::Optional(Box::new(p)),
101
+ None => p,
102
+ },
103
+ )
104
+ .parse(input)
105
+ }
106
+
107
+ fn parse_property_map(input: &str) -> VResult<&str, IndexMap<String, Property>>
108
+ {
109
+ map(
110
+ (
111
+ tag("{"),
112
+ opt((
113
+ separated_list1(
114
+ ws0(tag(",")),
115
+ map(
116
+ (ws0(parse_identifier), ws0(tag(":")), ws0(parse_property)),
117
+ |(name, _, property)| (name.to_string(), property),
118
+ ),
119
+ ),
120
+ opt(ws0(tag(","))),
121
+ )),
122
+ opt(ws0(tag(","))),
123
+ ws0(tag("}")),
124
+ ),
125
+ |(_, properties, _, _)| {
126
+ properties
127
+ .map(|(x, _)| x.into_iter().collect())
128
+ .unwrap_or_default()
129
+ },
130
+ )
131
+ .parse(input)
132
+ }
133
+
134
+ /// parse a property definition
135
+ fn parse_property_definition(input: &str) -> VResult<&str, ParseTreeElement>
136
+ {
137
+ alt((
138
+ map(
139
+ (
140
+ parse_label,
141
+ ws0(tag("<:")),
142
+ ws0(parse_label),
143
+ ws0(parse_property_map),
144
+ ),
145
+ |(label, _, parent, properties)| ParseTreeElement::Property {
146
+ label: label.to_string(),
147
+ properties,
148
+ parent: Some(parent.to_string()),
149
+ },
150
+ ),
151
+ map(
152
+ (parse_label, ws0(parse_property_map)),
153
+ |(label, properties)| ParseTreeElement::Property {
154
+ label: label.to_string(),
155
+ properties,
156
+ parent: None,
157
+ },
158
+ ),
159
+ ))
160
+ .parse(input)
161
+ }
162
+
163
+ fn parse_node_definition(input: &str) -> VResult<&str, ParseTreeElement>
164
+ {
165
+ map(
166
+ (tag("("), ws0(parse_label), ws0(tag(")"))),
167
+ |(_, label, _)| ParseTreeElement::Node {
168
+ label: label.to_string(),
169
+ },
170
+ )
171
+ .parse(input)
172
+ }
173
+ fn parse_edge_definition(input: &str) -> VResult<&str, ParseTreeElement>
174
+ {
175
+ map(
176
+ (
177
+ tag("("),
178
+ ws0(parse_label),
179
+ ws0(tag(")-[")),
180
+ ws0(parse_label),
181
+ ws0(tag("]->(")),
182
+ ws0(parse_label),
183
+ ws0(tag(")")),
184
+ ),
185
+ |(_, source, _, label, _, destination, _)| ParseTreeElement::Edge {
186
+ source: source.to_string(),
187
+ label: label.to_string(),
188
+ destination: destination.to_string(),
189
+ },
190
+ )
191
+ .parse(input)
192
+ }
193
+
194
+ fn single_line_comment(input: &str) -> VResult<&str, ()>
195
+ {
196
+ value((), (multispace0, tag("//"), is_not("\n\r"), multispace0)).parse(input)
197
+ }
198
+
199
+ /// parse the schema
200
+ pub(crate) fn parse_schema(input: &str) -> VResult<&str, Vec<ParseTreeElement>>
201
+ {
202
+ terminated(
203
+ separated_list0(
204
+ tag(","),
205
+ cut(ws0(delimited(
206
+ many0(single_line_comment),
207
+ alt((
208
+ parse_constrained_type_definition,
209
+ parse_property_definition,
210
+ parse_edge_definition,
211
+ parse_node_definition,
212
+ )),
213
+ many0(single_line_comment),
214
+ ))),
215
+ ),
216
+ multispace0,
217
+ )
218
+ .parse(input)
219
+ }
220
+
221
+ fn parse_integer(input: &str) -> VResult<&str, Value>
222
+ {
223
+ map_res_failure(recognize(pair(opt(char('-')), digit1)), |s: &str| {
224
+ s.parse::<i64>().map(Value::Integer)
225
+ })
226
+ .parse(input)
227
+ }
228
+
229
+ fn parse_float(input: &str) -> VResult<&str, Value>
230
+ {
231
+ map_res_failure(
232
+ recognize((
233
+ opt(char('-')),
234
+ digit1,
235
+ char('.'),
236
+ digit1,
237
+ opt((one_of("eE"), opt(one_of("+-")), digit1)),
238
+ )),
239
+ |s: &str| s.parse::<f64>().map(Value::Float),
240
+ )
241
+ .parse(input)
242
+ }
243
+
244
+ fn parse_boolean(input: &str) -> VResult<&str, Value>
245
+ {
246
+ alt((
247
+ value(Value::Boolean(true), tag("true")),
248
+ value(Value::Boolean(false), tag("false")),
249
+ ))
250
+ .parse(input)
251
+ }
252
+
253
+ #[inline]
254
+ fn is_label_char(chr: char) -> bool
255
+ {
256
+ chr.is_alphanumeric() || chr == '_'
257
+ }
258
+
259
+ fn parse_quoted_string(input: &str) -> VResult<&str, String>
260
+ {
261
+ delimited(
262
+ char('"'),
263
+ map(
264
+ opt(escaped_transform(
265
+ is_not("\"\\"),
266
+ '\\',
267
+ alt((value("\"", tag("\"")), value("\\", tag("\\")))),
268
+ )),
269
+ |s: Option<String>| s.unwrap_or_default(),
270
+ ),
271
+ cut(char('"')),
272
+ )
273
+ .parse(input)
274
+ }
275
+
276
+ fn parse_string_value(input: &str) -> VResult<&str, Value>
277
+ {
278
+ alt((
279
+ // Try quoted string with potential timestamp
280
+ |input| {
281
+ parse_quoted_string(input).map(|(rest, s)| match graphcore::TimeStamp::parse(&s)
282
+ {
283
+ Ok(ts) => (rest, Value::TimeStamp(ts)),
284
+ Err(_) => (rest, Value::String(s)),
285
+ })
286
+ },
287
+ // Try unquoted label strings
288
+ map(take_while1(is_label_char), |s: &str| {
289
+ Value::String(s.to_string())
290
+ }),
291
+ ))
292
+ .parse(input)
293
+ }
294
+
295
+ fn iso_date(input: &str) -> VResult<&str, &str>
296
+ {
297
+ recognize((
298
+ digit1,
299
+ char('-'),
300
+ fixed_digit::<2>,
301
+ char('-'),
302
+ fixed_digit::<2>,
303
+ ))
304
+ .parse(input)
305
+ }
306
+
307
+ fn iso_time(input: &str) -> VResult<&str, &str>
308
+ {
309
+ recognize((
310
+ fixed_digit::<2>,
311
+ char(':'),
312
+ fixed_digit::<2>,
313
+ char(':'),
314
+ fixed_digit::<2>,
315
+ opt(pair(char('.'), digit1)),
316
+ ))
317
+ .parse(input)
318
+ }
319
+
320
+ fn iso_offset(input: &str) -> VResult<&str, &str>
321
+ {
322
+ alt((
323
+ tag("Z"),
324
+ recognize((one_of("+-"), digit1, char(':'), digit1)),
325
+ ))
326
+ .parse(input)
327
+ }
328
+
329
+ fn iso_zone(input: &str) -> VResult<&str, &str>
330
+ {
331
+ recognize(delimited(char('['), take_while1(|c| c != ']'), char(']'))).parse(input)
332
+ }
333
+
334
+ fn iso_timestamp(input: &str) -> VResult<&str, &str>
335
+ {
336
+ recognize((iso_date, char('T'), iso_time, iso_offset, opt(iso_zone))).parse(input)
337
+ }
338
+
339
+ fn parse_timestamp_value(input: &str) -> VResult<&str, Value>
340
+ {
341
+ map_res_failure(iso_timestamp, |s: &str| {
342
+ graphcore::TimeStamp::parse(s)
343
+ .map(Value::TimeStamp)
344
+ .map_err(|e| format!("invalid ISO 8601 timestamp '{s}': {e}"))
345
+ })
346
+ .parse(input)
347
+ }
348
+
349
+ pub(crate) fn parse_value(input: &str) -> VResult<&str, Value>
350
+ {
351
+ alt((
352
+ parse_timestamp_value,
353
+ parse_float,
354
+ parse_integer,
355
+ parse_boolean,
356
+ parse_string_value,
357
+ ))
358
+ .parse(input)
359
+ }
360
+
361
+ fn parse_in_list(input: &str) -> VResult<&str, Vec<Value>>
362
+ {
363
+ delimited(
364
+ ws0(char('[')),
365
+ separated_list1(ws0(char(',')), ws0(parse_value)),
366
+ ws0(cut(char(']'))),
367
+ )
368
+ .parse(input)
369
+ }
370
+
371
+ fn parse_atom(input: &str) -> VResult<&str, Constraint>
372
+ {
373
+ alt((
374
+ delimited(ws0(char('(')), ws0(parse_or_expr), ws0(cut(char(')')))),
375
+ map(preceded(ws0(tag("IN")), ws0(parse_in_list)), Constraint::In),
376
+ map(preceded(ws0(tag("==")), ws0(parse_value)), Constraint::Eq),
377
+ map(preceded(ws0(tag("!=")), ws0(parse_value)), Constraint::Ne),
378
+ map(
379
+ preceded(ws0(tag("<=")), ws0(parse_value)),
380
+ Constraint::LessOrEqual,
381
+ ),
382
+ map(
383
+ preceded(ws0(tag(">=")), ws0(parse_value)),
384
+ Constraint::GreaterOrEqual,
385
+ ),
386
+ map(
387
+ preceded(ws0(char('<')), ws0(parse_value)),
388
+ Constraint::LessThan,
389
+ ),
390
+ map(
391
+ preceded(ws0(char('>')), ws0(parse_value)),
392
+ Constraint::GreaterThan,
393
+ ),
394
+ ))
395
+ .parse(input)
396
+ }
397
+
398
+ fn parse_unary(input: &str) -> VResult<&str, Constraint>
399
+ {
400
+ alt((
401
+ map(preceded(ws0(char('!')), ws0(parse_unary)), |c| {
402
+ Constraint::Not(Box::new(c))
403
+ }),
404
+ parse_atom,
405
+ ))
406
+ .parse(input)
407
+ }
408
+
409
+ fn parse_and_expr(input: &str) -> VResult<&str, Constraint>
410
+ {
411
+ map(
412
+ separated_list1(ws0(tag("&&")), ws0(parse_unary)),
413
+ |mut cs| {
414
+ if cs.len() == 1
415
+ {
416
+ cs.remove(0)
417
+ }
418
+ else
419
+ {
420
+ Constraint::And(cs)
421
+ }
422
+ },
423
+ )
424
+ .parse(input)
425
+ }
426
+
427
+ fn parse_or_expr(input: &str) -> VResult<&str, Constraint>
428
+ {
429
+ map(
430
+ separated_list1(ws0(tag("||")), ws0(parse_and_expr)),
431
+ |mut cs| {
432
+ if cs.len() == 1
433
+ {
434
+ cs.remove(0)
435
+ }
436
+ else
437
+ {
438
+ Constraint::Or(cs)
439
+ }
440
+ },
441
+ )
442
+ .parse(input)
443
+ }
444
+
445
+ pub(crate) fn parse_constraint(input: &str) -> VResult<&str, Constraint>
446
+ {
447
+ parse_or_expr(input)
448
+ }
449
+
450
+ /// Parse a type reference: either a base scalar type or a named type.
451
+ pub(crate) fn parse_type_ref(input: &str) -> VResult<&str, crate::gqls::ast::TypeRef>
452
+ {
453
+ use crate::gqls::ast::TypeRef;
454
+
455
+ alt((
456
+ map(tag("STRING"), |_| TypeRef::Base(LiteralBaseType::String)),
457
+ map(tag("FLOAT"), |_| TypeRef::Base(LiteralBaseType::Float)),
458
+ map(tag("INTEGER"), |_| TypeRef::Base(LiteralBaseType::Integer)),
459
+ map(tag("BOOLEAN"), |_| TypeRef::Base(LiteralBaseType::Boolean)),
460
+ map(tag("TIMESTAMP"), |_| {
461
+ TypeRef::Base(LiteralBaseType::TimeStamp)
462
+ }),
463
+ map(parse_label, |l: &str| TypeRef::Named(l.to_string())),
464
+ ))
465
+ .parse(input)
466
+ }
467
+
468
+ /// Parse a constrained type definition: `type Name = TypeRef: Constraint?,`
469
+ /// or `type Name = TypeRef,`
470
+ pub(crate) fn parse_constrained_type_definition(input: &str) -> VResult<&str, ParseTreeElement>
471
+ {
472
+ use crate::gqls::ast::ConstrainedTypeDef;
473
+
474
+ map(
475
+ (
476
+ preceded(ws0(tag("type")), ws0(parse_label)),
477
+ ws0(char('=')),
478
+ ws0(parse_type_ref),
479
+ opt(preceded(ws0(char(':')), ws0(cut(parse_constraint)))),
480
+ ),
481
+ |(name, _, base, constraint)| ParseTreeElement::ConstrainedType {
482
+ name: name.to_string(),
483
+ def: ConstrainedTypeDef { base, constraint },
484
+ },
485
+ )
486
+ .parse(input)
487
+ }
488
+
489
+ #[cfg(test)]
490
+ mod tests
491
+ {
492
+ use std::assert_matches;
493
+
494
+ use crate::gqls::{ast, constraint::Constraint, parser::ParseTreeElement};
495
+ use graphcore::Value;
496
+
497
+ #[test]
498
+ fn test_single_line_comment()
499
+ {
500
+ assert!(super::single_line_comment("// hello").is_ok());
501
+ assert!(super::single_line_comment("// hello\n").is_ok());
502
+ assert!(super::single_line_comment("/ hello").is_err());
503
+ assert!(super::single_line_comment(" // hello").is_ok());
504
+ assert!(super::single_line_comment("\n // hello").is_ok());
505
+ }
506
+
507
+ #[test]
508
+ fn parses_positive_and_negative_integers()
509
+ {
510
+ assert_eq!(super::parse_value("42").unwrap().1, Value::Integer(42));
511
+ assert_eq!(super::parse_value("-42").unwrap().1, Value::Integer(-42));
512
+ }
513
+
514
+ #[test]
515
+ fn parses_float_with_and_without_exponent()
516
+ {
517
+ assert_eq!(super::parse_value("1.5").unwrap().1, Value::Float(1.5));
518
+ assert_eq!(super::parse_value("1.5e2").unwrap().1, Value::Float(150.0));
519
+ assert_eq!(super::parse_value("-0.5").unwrap().1, Value::Float(-0.5));
520
+ }
521
+
522
+ #[test]
523
+ fn rejects_malformed_float()
524
+ {
525
+ let (rest, v) = super::parse_value("1.2.3").unwrap();
526
+ assert_eq!(v, Value::Float(1.2));
527
+ assert_eq!(rest, ".3");
528
+ }
529
+
530
+ #[test]
531
+ fn parses_booleans()
532
+ {
533
+ assert_eq!(super::parse_value("true").unwrap().1, Value::Boolean(true));
534
+ assert_eq!(
535
+ super::parse_value("false").unwrap().1,
536
+ Value::Boolean(false)
537
+ );
538
+ }
539
+
540
+ #[test]
541
+ fn parses_bare_identifier_as_string()
542
+ {
543
+ assert_eq!(
544
+ super::parse_value("Context").unwrap().1,
545
+ Value::String("Context".into())
546
+ );
547
+ }
548
+
549
+ #[test]
550
+ fn parses_quoted_string_with_escapes()
551
+ {
552
+ assert_eq!(
553
+ super::parse_value(r#""needs a space""#).unwrap().1,
554
+ Value::String("needs a space".into())
555
+ );
556
+ assert_eq!(
557
+ super::parse_value(r#""say \"hi\"""#).unwrap().1,
558
+ Value::String(r#"say "hi""#.into())
559
+ );
560
+ }
561
+
562
+ #[test]
563
+ fn rejects_unterminated_quoted_string()
564
+ {
565
+ assert!(super::parse_value(r#""unterminated"#).is_err());
566
+ }
567
+
568
+ #[test]
569
+ fn parses_quoted_timestamp()
570
+ {
571
+ let (_, v) = super::parse_value(r#""2024-01-01T00:00:00.000000+00:00[UTC]""#).unwrap();
572
+ assert!(matches!(v, Value::TimeStamp(_)));
573
+ }
574
+
575
+ #[test]
576
+ fn parses_timestamp_with_z_offset()
577
+ {
578
+ let (_, v) = super::parse_value(r#"2024-01-01T00:00:00.000000+00:00[UTC]"#).unwrap();
579
+ assert!(matches!(v, Value::TimeStamp(_)));
580
+ }
581
+
582
+ #[test]
583
+ fn parses_timestamp_with_numeric_offset()
584
+ {
585
+ let (_, v) = super::parse_value(r#"2024-06-15T07:00:00.123000-00:00[GMT]"#).unwrap();
586
+ assert!(matches!(v, Value::TimeStamp(_)));
587
+ }
588
+
589
+ #[test]
590
+ fn rejects_timestamp_missing_offset()
591
+ {
592
+ assert!(super::parse_timestamp_value(r#"2024-01-01T00:00:00"#).is_err());
593
+ }
594
+
595
+ #[test]
596
+ fn rejects_malformed_timestamp_string()
597
+ {
598
+ assert!(super::parse_timestamp_value(r#"not-a-date"#).is_err());
599
+ }
600
+
601
+ #[test]
602
+ fn parses_in_operator()
603
+ {
604
+ let c = super::parse_constraint("IN [Context, Critical, Excluded]")
605
+ .unwrap()
606
+ .1;
607
+ assert_eq!(
608
+ c,
609
+ Constraint::In(vec![
610
+ Value::String("Context".into()),
611
+ Value::String("Critical".into()),
612
+ Value::String("Excluded".into()),
613
+ ])
614
+ );
615
+ }
616
+
617
+ #[test]
618
+ fn parses_each_comparison_operator()
619
+ {
620
+ assert_eq!(
621
+ super::parse_constraint("== 5").unwrap().1,
622
+ Constraint::Eq(Value::Integer(5))
623
+ );
624
+ assert_eq!(
625
+ super::parse_constraint("!= 5").unwrap().1,
626
+ Constraint::Ne(Value::Integer(5))
627
+ );
628
+ assert_eq!(
629
+ super::parse_constraint("< 5").unwrap().1,
630
+ Constraint::LessThan(Value::Integer(5))
631
+ );
632
+ assert_eq!(
633
+ super::parse_constraint("<= 5").unwrap().1,
634
+ Constraint::LessOrEqual(Value::Integer(5))
635
+ );
636
+ assert_eq!(
637
+ super::parse_constraint("> 5").unwrap().1,
638
+ Constraint::GreaterThan(Value::Integer(5))
639
+ );
640
+ assert_eq!(
641
+ super::parse_constraint(">= 5").unwrap().1,
642
+ Constraint::GreaterOrEqual(Value::Integer(5))
643
+ );
644
+ }
645
+
646
+ #[test]
647
+ fn two_char_operators_not_mis_parsed_as_prefix()
648
+ {
649
+ let (rest, c) = super::parse_constraint("<= 5").unwrap();
650
+ assert_eq!(rest, "");
651
+ assert_eq!(c, Constraint::LessOrEqual(Value::Integer(5)));
652
+ }
653
+
654
+ #[test]
655
+ fn and_binds_tighter_than_or()
656
+ {
657
+ let c = super::parse_constraint("> 0 || > 1 && < 10").unwrap().1;
658
+ assert_eq!(
659
+ c,
660
+ Constraint::Or(vec![
661
+ Constraint::GreaterThan(Value::Integer(0)),
662
+ Constraint::And(vec![
663
+ Constraint::GreaterThan(Value::Integer(1)),
664
+ Constraint::LessThan(Value::Integer(10)),
665
+ ]),
666
+ ])
667
+ );
668
+ }
669
+
670
+ #[test]
671
+ fn not_binds_tighter_than_and()
672
+ {
673
+ let c = super::parse_constraint("!(> 0) && < 10").unwrap().1;
674
+ assert_eq!(
675
+ c,
676
+ Constraint::And(vec![
677
+ Constraint::Not(Box::new(Constraint::GreaterThan(Value::Integer(0)))),
678
+ Constraint::LessThan(Value::Integer(10)),
679
+ ])
680
+ );
681
+ }
682
+
683
+ #[test]
684
+ fn parens_override_precedence()
685
+ {
686
+ let c = super::parse_constraint("(> 0 || > 1) && < 10").unwrap().1;
687
+ assert_eq!(
688
+ c,
689
+ Constraint::And(vec![
690
+ Constraint::Or(vec![
691
+ Constraint::GreaterThan(Value::Integer(0)),
692
+ Constraint::GreaterThan(Value::Integer(1)),
693
+ ]),
694
+ Constraint::LessThan(Value::Integer(10)),
695
+ ])
696
+ );
697
+ }
698
+
699
+ #[test]
700
+ fn rejects_unmatched_paren()
701
+ {
702
+ assert!(super::parse_constraint("(> 0").is_err());
703
+ }
704
+
705
+ #[test]
706
+ fn rejects_missing_operand()
707
+ {
708
+ assert!(super::parse_constraint(">").is_err());
709
+ }
710
+
711
+ #[test]
712
+ fn rejects_empty_in_list()
713
+ {
714
+ assert!(super::parse_constraint("IN []").is_err());
715
+ }
716
+
717
+ // Tests for parse_type_ref
718
+ #[test]
719
+ fn parses_base_type_string()
720
+ {
721
+ use crate::gqls::properties::LiteralBaseType;
722
+ let (_, tr) = super::parse_type_ref("STRING").unwrap();
723
+ assert_eq!(tr, crate::gqls::ast::TypeRef::Base(LiteralBaseType::String));
724
+ }
725
+
726
+ #[test]
727
+ fn parses_base_type_integer()
728
+ {
729
+ use crate::gqls::properties::LiteralBaseType;
730
+ let (_, tr) = super::parse_type_ref("INTEGER").unwrap();
731
+ assert_eq!(
732
+ tr,
733
+ crate::gqls::ast::TypeRef::Base(LiteralBaseType::Integer)
734
+ );
735
+ }
736
+
737
+ #[test]
738
+ fn parses_named_type_reference()
739
+ {
740
+ let (_, tr) = super::parse_type_ref("MyType").unwrap();
741
+ assert_eq!(tr, crate::gqls::ast::TypeRef::Named("MyType".to_string()));
742
+ }
743
+
744
+ #[test]
745
+ fn parses_declaration_without_constraint()
746
+ {
747
+ use crate::gqls::properties::LiteralBaseType;
748
+ let (_, tree_element) = super::parse_constrained_type_definition("type Age = INTEGER").unwrap();
749
+ assert_matches!(
750
+ tree_element,
751
+ ParseTreeElement::ConstrainedType {
752
+ name,
753
+ def: ast::ConstrainedTypeDef {
754
+ base: crate::gqls::ast::TypeRef::Base(LiteralBaseType::Integer),
755
+ constraint: None
756
+ }
757
+ } if name == "Age"
758
+ );
759
+ }
760
+
761
+ #[test]
762
+ fn parses_declaration_with_base_type_and_constraint()
763
+ {
764
+ use crate::gqls::properties::LiteralBaseType;
765
+ let (_, tree_element) =
766
+ super::parse_constrained_type_definition("type Bob = INTEGER: > 10").unwrap();
767
+ assert_matches!(
768
+ tree_element,
769
+ ParseTreeElement::ConstrainedType {
770
+ name,
771
+ def: ast::ConstrainedTypeDef {
772
+ base: crate::gqls::ast::TypeRef::Base(LiteralBaseType::Integer),
773
+ constraint: Some(Constraint::GreaterThan(Value::Integer(10))),
774
+ }
775
+ } if name == "Bob"
776
+ );
777
+ }
778
+
779
+ #[test]
780
+ fn parses_declaration_referencing_named_type()
781
+ {
782
+ let (_, tree_element) =
783
+ super::parse_constrained_type_definition("type SuperBob = Bob: < 20").unwrap();
784
+ assert_matches!(
785
+ tree_element,
786
+ ParseTreeElement::ConstrainedType {
787
+ name,
788
+ def: ast::ConstrainedTypeDef {
789
+ base: crate::gqls::ast::TypeRef::Named(base_type_name),
790
+ constraint: Some(Constraint::LessThan(Value::Integer(20))),
791
+ }
792
+ } if name == "SuperBob" && base_type_name == "Bob"
793
+ );
794
+ }
795
+
796
+ #[test]
797
+ fn parses_declaration_with_whitespace_variations()
798
+ {
799
+ use crate::gqls::properties::LiteralBaseType;
800
+ let (_, tree_element) =
801
+ super::parse_constrained_type_definition(" type Age = INTEGER : > 10 ").unwrap();
802
+ assert_matches!(
803
+ tree_element,
804
+ ParseTreeElement::ConstrainedType {
805
+ name,
806
+ def: ast::ConstrainedTypeDef {
807
+ base: crate::gqls::ast::TypeRef::Base(LiteralBaseType::Integer),
808
+ constraint: Some(Constraint::GreaterThan(Value::Integer(10))),
809
+ }
810
+ } if name == "Age"
811
+ );
812
+ }
813
+ }