1use std::collections::BTreeMap;
2use std::env;
3use std::fs;
4use std::path::Path;
5
6use helix_db::dsl::prelude::*;
7use helix_db::{Empty, OnNodes, QueryParamType, ReadOnly, Step, Traversal, WriteEnabled};
8
9struct Fixture {
10 bucket: &'static str,
11 name: String,
12 request: DynamicQueryRequest,
13}
14
15fn main() -> Result<(), Box<dyn std::error::Error>> {
16 let out = env::args()
17 .nth(1)
18 .unwrap_or_else(|| "tests/parity/generated/rust".to_string());
19 let out = Path::new(&out);
20 reset_dir(&out.join("runtime"))?;
21 reset_dir(&out.join("json-only"))?;
22
23 let mut fixtures = runtime_fixtures();
24 fixtures.extend(node_permutation_fixtures());
25 fixtures.extend(json_only_fixtures());
26
27 for fixture in fixtures {
28 let path = out
29 .join(fixture.bucket)
30 .join(format!("{}.json", fixture.name));
31 fs::write(path, fixture.request.to_json_string()?)?;
32 }
33
34 Ok(())
35}
36
37fn reset_dir(path: &Path) -> std::io::Result<()> {
38 if path.exists() {
39 fs::remove_dir_all(path)?;
40 }
41 fs::create_dir_all(path)
42}
43
44fn runtime(name: impl Into<String>, request: DynamicQueryRequest) -> Fixture {
45 Fixture {
46 bucket: "runtime",
47 name: name.into(),
48 request,
49 }
50}
51
52fn json_only(name: impl Into<String>, request: DynamicQueryRequest) -> Fixture {
53 Fixture {
54 bucket: "json-only",
55 name: name.into(),
56 request,
57 }
58}
59
60fn read_request(batch: ReadBatch) -> DynamicQueryRequest {
61 DynamicQueryRequest::read(batch)
62}
63
64fn write_request(batch: WriteBatch) -> DynamicQueryRequest {
65 DynamicQueryRequest::write(batch)
66}
67
68fn object(entries: Vec<(&str, DynamicQueryValue)>) -> DynamicQueryValue {
69 DynamicQueryValue::Object(
70 entries
71 .into_iter()
72 .map(|(key, value)| (key.to_string(), value))
73 .collect::<BTreeMap<_, _>>(),
74 )
75}
76
77fn array(values: Vec<DynamicQueryValue>) -> DynamicQueryValue {
78 DynamicQueryValue::Array(values)
79}
80
81fn string(value: &str) -> DynamicQueryValue {
82 DynamicQueryValue::String(value.to_string())
83}
84
85fn i64_value(value: i64) -> DynamicQueryValue {
86 DynamicQueryValue::I64(value)
87}
88
89fn f64_value(value: f64) -> DynamicQueryValue {
90 DynamicQueryValue::F64(value)
91}
92
93fn with_params(
94 mut request: DynamicQueryRequest,
95 values: Vec<(&str, DynamicQueryValue)>,
96 types: Vec<(&str, QueryParamType)>,
97) -> DynamicQueryRequest {
98 for (name, value) in values {
99 request.insert_parameter_value(name, value);
100 }
101 for (name, ty) in types {
102 request.insert_parameter_type(name, ty);
103 }
104 request
105}
106
107fn user_props(
108 external_id: &'static str,
109 name: &'static str,
110 age: i64,
111 score: f64,
112 status: &'static str,
113 city: &'static str,
114 bio: &'static str,
115 embedding: Vec<f32>,
116) -> Vec<(&'static str, PropertyInput)> {
117 vec![
118 ("externalId", PropertyInput::from(external_id)),
119 ("name", PropertyInput::from(name)),
120 ("age", PropertyInput::from(age)),
121 ("score", PropertyInput::from(score)),
122 ("status", PropertyInput::from(status)),
123 ("tenantId", PropertyInput::from("tenant-a")),
124 ("city", PropertyInput::from(city)),
125 ("bio", PropertyInput::from(bio)),
126 (
127 "createdAt",
128 PropertyInput::from(DateTime::from_millis(1_776_000_000_000)),
129 ),
130 (
131 "embedding",
132 PropertyInput::from(PropertyValue::from(embedding)),
133 ),
134 ]
135}
136
137fn nested_metadata_property(external_id: &str, score: i64) -> PropertyValue {
138 PropertyValue::object(vec![
139 ("externalID", PropertyValue::from(external_id)),
140 ("score", PropertyValue::from(score)),
141 (
142 "tags",
143 PropertyValue::array(vec![
144 PropertyValue::from("alpha"),
145 PropertyValue::from(7i64),
146 ]),
147 ),
148 ])
149}
150
151fn nested_metadata_param(external_id: &str, score: i64) -> DynamicQueryValue {
152 object(vec![
153 ("externalID", string(external_id)),
154 ("score", i64_value(score)),
155 ("tags", array(vec![string("alpha"), i64_value(7)])),
156 ])
157}
158
159fn runtime_fixtures() -> Vec<Fixture> {
160 vec![
161 runtime(
162 "001-write-seed-core",
163 write_request(
164 write_batch()
165 .var_as(
166 "alice",
167 g().add_n(
168 "ParityUser",
169 user_props(
170 "user-alice",
171 "Alice",
172 31,
173 90.5,
174 "active",
175 "London",
176 "Alice writes graph database tests",
177 vec![1.0, 0.0, 0.0],
178 ),
179 ),
180 )
181 .var_as(
182 "bob",
183 g().add_n(
184 "ParityUser",
185 user_props(
186 "user-bob",
187 "Bob",
188 27,
189 72.25,
190 "active",
191 "Paris",
192 "Bob likes traversal testing",
193 vec![0.9, 0.1, 0.0],
194 ),
195 ),
196 )
197 .var_as(
198 "carol",
199 g().add_n(
200 "ParityUser",
201 user_props(
202 "user-carol",
203 "Carol",
204 42,
205 64.0,
206 "inactive",
207 "Berlin",
208 "Carol archives old records",
209 vec![0.0, 1.0, 0.0],
210 ),
211 ),
212 )
213 .var_as(
214 "alice_follows_bob",
215 g().n(NodeRef::var("alice")).add_e(
216 "FOLLOWS",
217 NodeRef::var("bob"),
218 vec![
219 ("weight", PropertyInput::from(1.0f64)),
220 ("since", PropertyInput::from("2024-01-01")),
221 ("note", PropertyInput::from("Alice follows Bob")),
222 (
223 "embedding",
224 PropertyInput::from(PropertyValue::from(vec![1.0f32, 0.0])),
225 ),
226 ],
227 ),
228 )
229 .var_as(
230 "bob_follows_carol",
231 g().n(NodeRef::var("bob")).add_e(
232 "FOLLOWS",
233 NodeRef::var("carol"),
234 vec![
235 ("weight", PropertyInput::from(0.5f64)),
236 ("since", PropertyInput::from("2024-02-01")),
237 ("note", PropertyInput::from("Bob follows Carol")),
238 (
239 "embedding",
240 PropertyInput::from(PropertyValue::from(vec![0.0f32, 1.0])),
241 ),
242 ],
243 ),
244 )
245 .returning([
246 "alice",
247 "bob",
248 "carol",
249 "alice_follows_bob",
250 "bob_follows_carol",
251 ]),
252 ),
253 ),
254 runtime(
255 "002-read-count-all-users",
256 read_request(
257 read_batch()
258 .var_as("user_count", g().n_with_label("ParityUser").count())
259 .returning(["user_count"]),
260 ),
261 ),
262 runtime(
263 "003-read-source-predicate-and-count",
264 read_request(
265 read_batch()
266 .var_as(
267 "active_adults",
268 g().n_with_label_where(
269 "ParityUser",
270 SourcePredicate::and(vec![
271 SourcePredicate::eq("status", "active"),
272 SourcePredicate::gte("age", 30i64),
273 ]),
274 )
275 .count(),
276 )
277 .returning(["active_adults"]),
278 ),
279 ),
280 runtime(
281 "004-read-value-map-projection",
282 read_request(
283 read_batch()
284 .var_as(
285 "alice",
286 g().n_with_label("ParityUser")
287 .where_(Predicate::eq("externalId", "user-alice"))
288 .project(vec![
289 Projection::property("externalId", "id"),
290 Projection::property("name", "name"),
291 Projection::expr(
292 "score_plus_one",
293 Expr::prop("score").add(Expr::val(1.0f64)),
294 ),
295 Projection::expr(
296 "status_label",
297 Expr::case(
298 vec![(
299 Predicate::eq("status", "active"),
300 Expr::val("enabled"),
301 )],
302 Some(Expr::val("disabled")),
303 ),
304 ),
305 ]),
306 )
307 .returning(["alice"]),
308 ),
309 ),
310 runtime(
311 "005-read-order-range-values",
312 read_request(
313 read_batch()
314 .var_as(
315 "ordered",
316 g().n_with_label("ParityUser")
317 .order_by_multiple(vec![("status", Order::Asc), ("age", Order::Desc)])
318 .range(0usize, 2usize)
319 .value_map(Some(vec!["externalId", "age", "status"])),
320 )
321 .returning(["ordered"]),
322 ),
323 ),
324 runtime(
325 "006-read-edge-count",
326 read_request(
327 read_batch()
328 .var_as(
329 "edge_count",
330 g().n_with_label("ParityUser")
331 .where_(Predicate::eq("externalId", "user-alice"))
332 .out_e(Some("FOLLOWS"))
333 .count(),
334 )
335 .returning(["edge_count"]),
336 ),
337 ),
338 runtime(
339 "007-read-edge-properties",
340 read_request(
341 read_batch()
342 .var_as(
343 "edges",
344 g().e_with_label("FOLLOWS")
345 .edge_has("weight", PropertyInput::from(1.0f64))
346 .edge_properties(),
347 )
348 .returning(["edges"]),
349 ),
350 ),
351 runtime(
352 "008-read-edge-endpoints",
353 read_request(
354 read_batch()
355 .var_as(
356 "from_nodes",
357 g().e_with_label("FOLLOWS")
358 .edge_has_label("FOLLOWS")
359 .in_n()
360 .value_map(Some(vec!["externalId", "name"])),
361 )
362 .var_as(
363 "to_nodes",
364 g().e_with_label("FOLLOWS")
365 .out_n()
366 .value_map(Some(vec!["externalId", "name"])),
367 )
368 .returning(["from_nodes", "to_nodes"]),
369 ),
370 ),
371 runtime(
372 "009-read-conditional-var-not-empty",
373 read_request(
374 read_batch()
375 .var_as(
376 "alice",
377 g().n_with_label("ParityUser")
378 .where_(Predicate::eq("externalId", "user-alice")),
379 )
380 .var_as_if(
381 "friends",
382 BatchCondition::VarNotEmpty("alice".to_string()),
383 g().n(NodeRef::var("alice"))
384 .out(Some("FOLLOWS"))
385 .value_map(Some(vec!["externalId", "name"])),
386 )
387 .returning(["alice", "friends"]),
388 ),
389 ),
390 runtime(
391 "010-read-conditional-var-empty",
392 read_request(
393 read_batch()
394 .var_as(
395 "missing",
396 g().n_with_label("ParityUser")
397 .where_(Predicate::eq("externalId", "missing-user")),
398 )
399 .var_as_if(
400 "fallback",
401 BatchCondition::VarEmpty("missing".to_string()),
402 g().n_with_label("ParityUser")
403 .limit(1usize)
404 .value_map(Some(vec!["externalId"])),
405 )
406 .returning(["missing", "fallback"]),
407 ),
408 ),
409 runtime(
410 "011-read-conditional-var-min-size-prev",
411 read_request(
412 read_batch()
413 .var_as("users", g().n_with_label("ParityUser").limit(3usize))
414 .var_as_if(
415 "min_two",
416 BatchCondition::VarMinSize("users".to_string(), 2),
417 g().n(NodeRef::var("users")).count(),
418 )
419 .var_as_if(
420 "prev_ok",
421 BatchCondition::PrevNotEmpty,
422 g().n(NodeRef::var("users")).exists(),
423 )
424 .returning(["min_two", "prev_ok"]),
425 ),
426 ),
427 runtime(
428 "012-read-foreach-param",
429 with_params(
430 read_request(
431 read_batch()
432 .for_each_param(
433 "lookups",
434 read_batch().var_as(
435 "matched",
436 g().n_with_label("ParityUser")
437 .where_(Predicate::eq_param("externalId", "externalId"))
438 .value_map(Some(vec!["externalId", "name"])),
439 ),
440 )
441 .returning(["matched"]),
442 ),
443 vec![(
444 "lookups",
445 array(vec![
446 object(vec![("externalId", string("user-alice"))]),
447 object(vec![("externalId", string("user-carol"))]),
448 ]),
449 )],
450 vec![(
451 "lookups",
452 QueryParamType::Array(Box::new(QueryParamType::Object)),
453 )],
454 ),
455 ),
456 runtime(
457 "013-write-foreach-param-create",
458 with_params(
459 write_request(
460 write_batch()
461 .for_each_param(
462 "rows",
463 write_batch().var_as(
464 "created",
465 g().add_n(
466 "ParityEvent",
467 vec![
468 ("eventId", PropertyInput::param("eventId")),
469 ("kind", PropertyInput::param("kind")),
470 ("score", PropertyInput::param("score")),
471 ],
472 ),
473 ),
474 )
475 .returning(["created"]),
476 ),
477 vec![(
478 "rows",
479 array(vec![
480 object(vec![
481 ("eventId", string("event-1")),
482 ("kind", string("click")),
483 ("score", i64_value(10)),
484 ]),
485 object(vec![
486 ("eventId", string("event-2")),
487 ("kind", string("view")),
488 ("score", i64_value(5)),
489 ]),
490 ]),
491 )],
492 vec![(
493 "rows",
494 QueryParamType::Array(Box::new(QueryParamType::Object)),
495 )],
496 ),
497 ),
498 runtime(
499 "014-read-after-foreach-param",
500 read_request(
501 read_batch()
502 .var_as("event_count", g().n_with_label("ParityEvent").count())
503 .returning(["event_count"]),
504 ),
505 ),
506 runtime(
507 "015-write-set-remove-properties",
508 write_request(
509 write_batch()
510 .var_as(
511 "updated",
512 g().n_with_label("ParityUser")
513 .where_(Predicate::eq("externalId", "user-bob"))
514 .set_property("status", PropertyInput::from("inactive"))
515 .set_property(
516 "updatedAt",
517 PropertyInput::from(DateTime::from_millis(1_777_000_000_000)),
518 )
519 .remove_property("city")
520 .count(),
521 )
522 .returning(["updated"]),
523 ),
524 ),
525 runtime(
526 "016-read-updated-properties",
527 read_request(
528 read_batch()
529 .var_as(
530 "bob",
531 g().n_with_label("ParityUser")
532 .where_(Predicate::eq("externalId", "user-bob"))
533 .value_map(Some(vec!["externalId", "status", "updatedAt", "city"])),
534 )
535 .returning(["bob"]),
536 ),
537 ),
538 runtime(
539 "017-read-repeat-union",
540 read_request(
541 read_batch()
542 .var_as(
543 "walked",
544 g().n_with_label("ParityUser")
545 .where_(Predicate::eq("externalId", "user-alice"))
546 .repeat(
547 RepeatConfig::new(sub().out(Some("FOLLOWS")))
548 .times(2)
549 .emit_all()
550 .max_depth(4),
551 )
552 .union(vec![sub().out(Some("FOLLOWS")), sub().in_(Some("FOLLOWS"))])
553 .dedup()
554 .value_map(Some(vec!["externalId", "name"])),
555 )
556 .returning(["walked"]),
557 ),
558 ),
559 runtime(
560 "018-read-choose-coalesce-optional",
561 read_request(
562 read_batch()
563 .var_as(
564 "branched",
565 g().n_with_label("ParityUser")
566 .where_(Predicate::eq("externalId", "user-alice"))
567 .choose(
568 Predicate::eq("status", "active"),
569 sub().out(Some("FOLLOWS")),
570 Some(sub().in_(Some("FOLLOWS"))),
571 )
572 .coalesce(vec![sub().out(Some("FOLLOWS")), sub().in_(Some("FOLLOWS"))])
573 .optional(sub().out(Some("FOLLOWS")))
574 .dedup()
575 .value_map(Some(vec!["externalId", "name"])),
576 )
577 .returning(["branched"]),
578 ),
579 ),
580 runtime(
581 "019-read-aggregations",
582 read_request(
583 read_batch()
584 .var_as(
585 "by_status",
586 g().n_with_label("ParityUser").group_count("status"),
587 )
588 .var_as(
589 "mean_score",
590 g().n_with_label("ParityUser")
591 .aggregate_by(AggregateFunction::Mean, "score"),
592 )
593 .var_as(
594 "max_age",
595 g().n_with_label("ParityUser")
596 .aggregate_by(AggregateFunction::Max, "age"),
597 )
598 .returning(["by_status", "mean_score", "max_age"]),
599 ),
600 ),
601 runtime(
602 "020-write-index-create",
603 write_request(
604 write_batch()
605 .var_as(
606 "node_eq",
607 g().create_index_if_not_exists(IndexSpec::node_equality(
608 "ParityUser",
609 "externalId",
610 )),
611 )
612 .var_as(
613 "node_range",
614 g().create_index_if_not_exists(IndexSpec::node_range("ParityUser", "age")),
615 )
616 .var_as(
617 "edge_eq",
618 g().create_index_if_not_exists(IndexSpec::edge_equality(
619 "FOLLOWS", "since",
620 )),
621 )
622 .var_as(
623 "edge_range",
624 g().create_index_if_not_exists(IndexSpec::edge_range("FOLLOWS", "weight")),
625 )
626 .returning(["node_eq", "node_range", "edge_eq", "edge_range"]),
627 ),
628 ),
629 runtime(
630 "021-read-parameter-types",
631 with_params(
632 read_request(
633 read_batch()
634 .var_as(
635 "matches",
636 g().n_with_label("ParityUser")
637 .where_(Predicate::is_in_param("status", "statuses"))
638 .where_(Predicate::gte_param("createdAt", "created_after"))
639 .limit(Expr::param("limit"))
640 .value_map(Some(vec!["externalId", "status"])),
641 )
642 .returning(["matches"]),
643 ),
644 vec![
645 (
646 "statuses",
647 array(vec![string("active"), string("inactive")]),
648 ),
649 ("created_after", string("2026-01-01T00:00:00.000Z")),
650 ("limit", i64_value(5)),
651 ],
652 vec![
653 (
654 "statuses",
655 QueryParamType::Array(Box::new(QueryParamType::String)),
656 ),
657 ("created_after", QueryParamType::DateTime),
658 ("limit", QueryParamType::I64),
659 ],
660 ),
661 ),
662 runtime(
663 "022-write-property-value-variants",
664 write_request(
665 write_batch()
666 .var_as(
667 "variant_node",
668 g().add_n(
669 "ParityVariant",
670 vec![
671 ("nullValue", PropertyInput::from(PropertyValue::Null)),
672 ("boolValue", PropertyInput::from(true)),
673 (
674 "i64Value",
675 PropertyInput::from(9_223_372_036_854_775_000i64),
676 ),
677 (
678 "dateTimeValue",
679 PropertyInput::from(DateTime::from_millis(-1)),
680 ),
681 ("f64Value", PropertyInput::from(3.25f64)),
682 ("f32Value", PropertyInput::from(1.5f32)),
683 ("stringValue", PropertyInput::from("variant")),
684 (
685 "bytesValue",
686 PropertyInput::from(PropertyValue::from(vec![1u8, 2u8, 3u8])),
687 ),
688 (
689 "i64Array",
690 PropertyInput::from(PropertyValue::from(vec![
691 1i64, 2i64, 3i64,
692 ])),
693 ),
694 (
695 "f64Array",
696 PropertyInput::from(PropertyValue::from(vec![1.0f64, 2.0f64])),
697 ),
698 (
699 "f32Array",
700 PropertyInput::from(PropertyValue::from(vec![1.0f32, 2.0f32])),
701 ),
702 (
703 "stringArray",
704 PropertyInput::from(PropertyValue::from(vec![
705 "a".to_string(),
706 "b".to_string(),
707 ])),
708 ),
709 ],
710 ),
711 )
712 .returning(["variant_node"]),
713 ),
714 ),
715 runtime(
716 "023-read-property-value-variants",
717 read_request(
718 read_batch()
719 .var_as(
720 "variant",
721 g().n_with_label("ParityVariant")
722 .value_map(None::<Vec<&str>>),
723 )
724 .returning(["variant"]),
725 ),
726 ),
727 runtime(
728 "024-write-text-vector-indexes",
729 write_request(
730 write_batch()
731 .var_as(
732 "node_text",
733 g().create_text_index_nodes("ParityUser", "bio", None::<&str>),
734 )
735 .var_as(
736 "node_vector",
737 g().create_vector_index_nodes("ParityUser", "embedding", None::<&str>),
738 )
739 .var_as(
740 "edge_text",
741 g().create_text_index_edges("FOLLOWS", "note", None::<&str>),
742 )
743 .var_as(
744 "edge_vector",
745 g().create_vector_index_edges("FOLLOWS", "embedding", None::<&str>),
746 )
747 .returning(["node_text", "node_vector", "edge_text", "edge_vector"]),
748 ),
749 ),
750 runtime(
751 "025-read-text-search-nodes",
752 read_request(
753 read_batch()
754 .var_as(
755 "text_hits",
756 g().text_search_nodes("ParityUser", "bio", "graph", 5, None)
757 .value_map(Some(vec!["externalId", "bio", "$distance"])),
758 )
759 .returning(["text_hits"]),
760 ),
761 ),
762 runtime(
763 "026-read-vector-search-nodes",
764 read_request(
765 read_batch()
766 .var_as(
767 "vector_hits",
768 g().vector_search_nodes(
769 "ParityUser",
770 "embedding",
771 vec![1.0, 0.0, 0.0],
772 3,
773 None,
774 )
775 .project(vec![
776 Projection::property("externalId", "externalId"),
777 Projection::property("$distance", "distance"),
778 ]),
779 )
780 .returning(["vector_hits"]),
781 ),
782 ),
783 runtime(
784 "027-read-text-search-edges",
785 read_request(
786 read_batch()
787 .var_as(
788 "edge_text_hits",
789 g().text_search_edges("FOLLOWS", "note", "follows", 5, None)
790 .edge_properties(),
791 )
792 .returning(["edge_text_hits"]),
793 ),
794 ),
795 runtime(
796 "028-read-vector-search-edges",
797 read_request(
798 read_batch()
799 .var_as(
800 "edge_vector_hits",
801 g().vector_search_edges("FOLLOWS", "embedding", vec![1.0, 0.0], 5, None)
802 .edge_properties(),
803 )
804 .returning(["edge_vector_hits"]),
805 ),
806 ),
807 runtime(
808 "029-write-drop-temp-node",
809 write_request(
810 write_batch()
811 .var_as(
812 "temp",
813 g().add_n("ParityTemp", vec![("name", PropertyInput::from("temp"))]),
814 )
815 .var_as("dropped", g().n(NodeRef::var("temp")).drop().count())
816 .returning(["dropped"]),
817 ),
818 ),
819 runtime(
820 "030-read-final-counts",
821 read_request(
822 read_batch()
823 .var_as("users", g().n_with_label("ParityUser").count())
824 .var_as("events", g().n_with_label("ParityEvent").count())
825 .var_as("variants", g().n_with_label("ParityVariant").count())
826 .returning(["users", "events", "variants"]),
827 ),
828 ),
829 runtime(
830 "031-read-source-predicate-eq-param",
831 with_params(
832 read_request(
833 read_batch()
834 .var_as(
835 "user",
836 g().n_where(SourcePredicate::and(vec![
837 SourcePredicate::eq("$label", "ParityUser"),
838 SourcePredicate::eq("name", Expr::param("name")),
839 ]))
840 .value_map(Some(vec!["externalId", "name"])),
841 )
842 .returning(["user"]),
843 ),
844 vec![("name", string("Alice"))],
845 vec![("name", QueryParamType::String)],
846 ),
847 ),
848 runtime(
849 "032-read-source-predicate-between-param",
850 with_params(
851 read_request(
852 read_batch()
853 .var_as(
854 "adults",
855 g().n_where(SourcePredicate::and(vec![
856 SourcePredicate::eq("$label", "ParityUser"),
857 SourcePredicate::between("age", Expr::param("min_age"), 65i64),
858 ]))
859 .value_map(Some(vec!["externalId", "age"])),
860 )
861 .returning(["adults"]),
862 ),
863 vec![("min_age", i64_value(30))],
864 vec![("min_age", QueryParamType::I64)],
865 ),
866 ),
867 ]
868}
869
870fn node_permutation_fixtures() -> Vec<Fixture> {
871 let sources = ["label", "where", "all"];
872 let filters = ["none", "has", "logic", "expr"];
873 let bounds = ["none", "limit", "skip", "range"];
874 let terminals = ["count", "exists", "value_map", "project"];
875
876 let mut fixtures = Vec::new();
877 let mut index = 100;
878 for source in sources {
879 for filter in filters {
880 for bound in bounds {
881 for terminal in terminals {
882 let name =
883 format!("{index:03}-combo-node-{source}-{filter}-{bound}-{terminal}");
884 index += 1;
885 fixtures.push(runtime(
886 name,
887 read_request(node_combo_batch(source, filter, bound, terminal)),
888 ));
889 }
890 }
891 }
892 }
893 fixtures
894}
895
896fn node_combo_batch(source: &str, filter: &str, bound: &str, terminal: &str) -> ReadBatch {
897 let traversal = apply_node_bound(apply_node_filter(node_source(source), filter), bound)
898 .order_by("externalId", Order::Asc);
899 let traversal = match terminal {
900 "count" => traversal.count(),
901 "exists" => traversal.exists(),
902 "value_map" => traversal.value_map(Some(vec!["externalId", "name", "age", "status"])),
903 "project" => traversal.project(vec![
904 Projection::property("externalId", "externalId"),
905 Projection::property("status", "status"),
906 Projection::expr("age_plus_two", Expr::prop("age").add(Expr::val(2i64))),
907 ]),
908 other => panic!("unknown terminal {other}"),
909 };
910 read_batch()
911 .var_as("result", traversal)
912 .returning(["result"])
913}
914
915fn node_source(source: &str) -> Traversal<OnNodes, ReadOnly> {
916 match source {
917 "label" => g().n_with_label("ParityUser"),
918 "where" => g().n_where(SourcePredicate::eq("$label", "ParityUser")),
919 "all" => g().n(NodeRef::all()).has_label("ParityUser"),
920 other => panic!("unknown source {other}"),
921 }
922}
923
924fn apply_node_filter(
925 traversal: Traversal<OnNodes, ReadOnly>,
926 filter: &str,
927) -> Traversal<OnNodes, ReadOnly> {
928 match filter {
929 "none" => traversal,
930 "has" => traversal.has("status", "active"),
931 "logic" => traversal.where_(Predicate::and(vec![
932 Predicate::has_key("externalId"),
933 Predicate::or(vec![
934 Predicate::starts_with("name", "A"),
935 Predicate::ends_with("name", "b"),
936 ]),
937 Predicate::not(Predicate::is_null("age")),
938 ])),
939 "expr" => traversal.where_(Predicate::compare(
940 Expr::prop("score").add(Expr::val(1.0f64)),
941 CompareOp::Gt,
942 Expr::val(65.0f64),
943 )),
944 other => panic!("unknown filter {other}"),
945 }
946}
947
948fn apply_node_bound(
949 traversal: Traversal<OnNodes, ReadOnly>,
950 bound: &str,
951) -> Traversal<OnNodes, ReadOnly> {
952 match bound {
953 "none" => traversal,
954 "limit" => traversal.limit(2usize),
955 "skip" => traversal.skip(1usize),
956 "range" => traversal.range(0usize, 2usize),
957 other => panic!("unknown bound {other}"),
958 }
959}
960
961fn json_only_fixtures() -> Vec<Fixture> {
962 vec![
963 json_only(
964 "900-exhaustive-raw-read-steps",
965 with_params(
966 read_request(
967 read_batch()
968 .var_as(
969 "raw_nodes",
970 Traversal::<OnNodes, ReadOnly>::from_steps(vec![
971 Step::N(NodeRef::Param("node_ids".to_string())),
972 Step::Has("name".to_string(), PropertyValue::from("Alice")),
973 Step::Where(Predicate::contains_param("bio", "needle")),
974 Step::LimitBy(Expr::param("limit")),
975 Step::SkipBy(Expr::param("skip")),
976 Step::RangeBy(
977 StreamBound::literal(0),
978 StreamBound::expr(Expr::param("end")),
979 ),
980 Step::As("a".to_string()),
981 Step::Store("stored".to_string()),
982 Step::Select("stored".to_string()),
983 Step::Dedup,
984 Step::Within("stored".to_string()),
985 Step::Without("missing".to_string()),
986 Step::Fold,
987 Step::Unfold,
988 Step::Path,
989 Step::SimplePath,
990 Step::WithSack(PropertyValue::from(0i64)),
991 Step::SackSet("score".to_string()),
992 Step::SackAdd("score".to_string()),
993 Step::SackGet,
994 Step::Project(vec![
995 Projection::property("externalId", "externalId"),
996 Projection::expr("neg_age", Expr::prop("age").neg()),
997 ]),
998 ]),
999 )
1000 .var_as(
1001 "raw_edges",
1002 Traversal::<helix_db::OnEdges, ReadOnly>::from_steps(vec![
1003 Step::E(EdgeRef::Param("edge_ids".to_string())),
1004 Step::EWhere(SourcePredicate::or(vec![
1005 SourcePredicate::has_key("since"),
1006 SourcePredicate::starts_with("note", "Alice"),
1007 ])),
1008 Step::OutN,
1009 Step::InN,
1010 Step::OtherN,
1011 Step::EdgeHas("weight".to_string(), PropertyInput::from(1.0f64)),
1012 Step::EdgeHasLabel("FOLLOWS".to_string()),
1013 Step::OrderBy("weight".to_string(), Order::Desc),
1014 Step::EdgeProperties,
1015 ]),
1016 )
1017 .returning(["raw_nodes", "raw_edges"]),
1018 ),
1019 vec![
1020 ("node_ids", array(vec![i64_value(1), i64_value(2)])),
1021 ("edge_ids", array(vec![i64_value(1)])),
1022 ("needle", string("graph")),
1023 ("limit", i64_value(10)),
1024 ("skip", i64_value(0)),
1025 ("end", i64_value(10)),
1026 ],
1027 vec![
1028 (
1029 "node_ids",
1030 QueryParamType::Array(Box::new(QueryParamType::I64)),
1031 ),
1032 (
1033 "edge_ids",
1034 QueryParamType::Array(Box::new(QueryParamType::I64)),
1035 ),
1036 ("needle", QueryParamType::String),
1037 ("limit", QueryParamType::I64),
1038 ("skip", QueryParamType::I64),
1039 ("end", QueryParamType::I64),
1040 ],
1041 ),
1042 ),
1043 json_only(
1044 "901-exhaustive-raw-write-steps",
1045 write_request(
1046 write_batch()
1047 .var_as(
1048 "raw_indexes",
1049 Traversal::<helix_db::Terminal, WriteEnabled>::from_steps(vec![
1050 Step::CreateIndex {
1051 spec: IndexSpec::node_unique_equality("ParityUser", "externalId"),
1052 if_not_exists: true,
1053 },
1054 Step::DropIndex {
1055 spec: IndexSpec::node_range("ParityUser", "age"),
1056 },
1057 Step::CreateVectorIndexNodes {
1058 label: "ParityUser".to_string(),
1059 property: "embedding".to_string(),
1060 tenant_property: Some("tenantId".to_string()),
1061 },
1062 Step::CreateVectorIndexEdges {
1063 label: "FOLLOWS".to_string(),
1064 property: "embedding".to_string(),
1065 tenant_property: Some("tenantId".to_string()),
1066 },
1067 Step::CreateTextIndexNodes {
1068 label: "ParityUser".to_string(),
1069 property: "bio".to_string(),
1070 tenant_property: Some("tenantId".to_string()),
1071 },
1072 Step::CreateTextIndexEdges {
1073 label: "FOLLOWS".to_string(),
1074 property: "note".to_string(),
1075 tenant_property: Some("tenantId".to_string()),
1076 },
1077 ]),
1078 )
1079 .var_as(
1080 "raw_mutations",
1081 Traversal::<OnNodes, WriteEnabled>::from_steps(vec![
1082 Step::AddN {
1083 label: "RawNode".to_string(),
1084 properties: vec![("name".to_string(), PropertyInput::from("raw"))],
1085 },
1086 Step::AddE {
1087 label: "RAW_EDGE".to_string(),
1088 to: NodeRef::Var("raw_mutations".to_string()),
1089 properties: vec![("weight".to_string(), PropertyInput::from(1i64))],
1090 },
1091 Step::SetProperty(
1092 "name".to_string(),
1093 PropertyInput::Expr(Expr::param("name")),
1094 ),
1095 Step::RemoveProperty("old".to_string()),
1096 Step::DropEdge(NodeRef::Ids(vec![999_999])),
1097 Step::DropEdgeLabeled {
1098 to: NodeRef::Ids(vec![999_999]),
1099 label: "RAW_EDGE".to_string(),
1100 },
1101 Step::DropEdgeById(EdgeRef::Ids(vec![999_999])),
1102 Step::Drop,
1103 ]),
1104 )
1105 .returning(["raw_indexes", "raw_mutations"]),
1106 ),
1107 ),
1108 json_only(
1109 "902-dynamic-value-and-param-type-shapes",
1110 with_params(
1111 read_request(
1112 read_batch()
1113 .var_as("empty", g().n_with_label("Missing").count())
1114 .returning(["empty"]),
1115 ),
1116 vec![
1117 ("null", DynamicQueryValue::Null),
1118 ("bool", DynamicQueryValue::Bool(true)),
1119 ("i64", DynamicQueryValue::I64(i64::MAX)),
1120 ("f64", DynamicQueryValue::F64(1.25)),
1121 ("f32", DynamicQueryValue::F32(1.5)),
1122 ("string", string("value")),
1123 ("array", array(vec![i64_value(1), string("two")])),
1124 (
1125 "object",
1126 object(vec![("nested", DynamicQueryValue::Bool(true))]),
1127 ),
1128 ],
1129 vec![
1130 ("null", QueryParamType::Value),
1131 ("bool", QueryParamType::Bool),
1132 ("i64", QueryParamType::I64),
1133 ("f64", QueryParamType::F64),
1134 ("f32", QueryParamType::F32),
1135 ("string", QueryParamType::String),
1136 (
1137 "array",
1138 QueryParamType::Array(Box::new(QueryParamType::Value)),
1139 ),
1140 ("object", QueryParamType::Object),
1141 ],
1142 ),
1143 ),
1144 json_only(
1145 "903-empty-source-vector-text-runtime-inputs",
1146 with_params(
1147 read_request(
1148 read_batch()
1149 .var_as(
1150 "vector_nodes",
1151 g().vector_search_nodes_with(
1152 "ParityUser",
1153 "embedding",
1154 PropertyInput::param("query_vector"),
1155 Expr::param("limit"),
1156 Some(PropertyInput::param("tenant")),
1157 ),
1158 )
1159 .var_as(
1160 "text_nodes",
1161 g().text_search_nodes_with(
1162 "ParityUser",
1163 "bio",
1164 PropertyInput::param("query_text"),
1165 Expr::param("limit"),
1166 Some(PropertyInput::param("tenant")),
1167 ),
1168 )
1169 .returning(["vector_nodes", "text_nodes"]),
1170 ),
1171 vec![
1172 (
1173 "query_vector",
1174 array(vec![f64_value(1.0), f64_value(0.0), f64_value(0.0)]),
1175 ),
1176 ("query_text", string("graph")),
1177 ("limit", i64_value(5)),
1178 ("tenant", string("tenant-a")),
1179 ],
1180 vec![
1181 (
1182 "query_vector",
1183 QueryParamType::Array(Box::new(QueryParamType::F64)),
1184 ),
1185 ("query_text", QueryParamType::String),
1186 ("limit", QueryParamType::I64),
1187 ("tenant", QueryParamType::String),
1188 ],
1189 ),
1190 ),
1191 json_only(
1192 "904-empty-query-and-node-edge-ref-shapes",
1193 read_request(
1194 read_batch()
1195 .var_as(
1196 "all_nodes",
1197 Traversal::<OnNodes, ReadOnly>::from_steps(vec![
1198 Step::N(NodeRef::All),
1199 Step::Count,
1200 ]),
1201 )
1202 .var_as(
1203 "node_ids",
1204 Traversal::<OnNodes, ReadOnly>::from_steps(vec![
1205 Step::N(NodeRef::ids([1, 2])),
1206 Step::Id,
1207 ]),
1208 )
1209 .var_as(
1210 "node_var",
1211 Traversal::<OnNodes, ReadOnly>::from_steps(vec![
1212 Step::N(NodeRef::Var("all_nodes".to_string())),
1213 Step::Label,
1214 ]),
1215 )
1216 .var_as(
1217 "edge_ids",
1218 Traversal::<helix_db::OnEdges, ReadOnly>::from_steps(vec![
1219 Step::E(EdgeRef::ids([1, 2])),
1220 Step::Id,
1221 ]),
1222 )
1223 .var_as(
1224 "edge_var",
1225 Traversal::<helix_db::OnEdges, ReadOnly>::from_steps(vec![
1226 Step::E(EdgeRef::Var("edge_ids".to_string())),
1227 Step::Label,
1228 ]),
1229 )
1230 .returning(["all_nodes", "node_ids", "node_var", "edge_ids", "edge_var"]),
1231 ),
1232 ),
1233 json_only(
1234 "905-empty-traversal-source-mutators",
1235 write_request(
1236 write_batch()
1237 .var_as(
1238 "inject",
1239 Traversal::<Empty, ReadOnly>::new()
1240 .inject("some_var")
1241 .count(),
1242 )
1243 .var_as(
1244 "drop_edge_by_id",
1245 g().drop_edge_by_id(EdgeRef::id(123_456)).count(),
1246 )
1247 .returning(["inject", "drop_edge_by_id"]),
1248 ),
1249 ),
1250 json_only(
1251 "906-nested-dynamic-property-write-shapes",
1252 with_params(
1253 write_request(
1254 write_batch()
1255 .var_as(
1256 "created",
1257 g().add_n(
1258 "ParityNested",
1259 vec![
1260 ("name", PropertyInput::from("nested")),
1261 (
1262 "metadata",
1263 PropertyInput::from(nested_metadata_property(
1264 "some_id", 20,
1265 )),
1266 ),
1267 ],
1268 ),
1269 )
1270 .var_as(
1271 "updated",
1272 g().n(NodeRef::var("created"))
1273 .set_property("metadata", PropertyInput::param("metadata"))
1274 .value_map(Some(vec!["metadata.externalID"])),
1275 )
1276 .var_as(
1277 "target",
1278 g().add_n(
1279 "ParityNestedTarget",
1280 vec![("name", PropertyInput::from("target"))],
1281 ),
1282 )
1283 .var_as(
1284 "edge",
1285 g().n(NodeRef::var("created"))
1286 .add_e(
1287 "NESTED_LINK",
1288 NodeRef::var("target"),
1289 vec![(
1290 "metadata",
1291 PropertyInput::from(nested_metadata_property("edge_id", 5)),
1292 )],
1293 )
1294 .count(),
1295 )
1296 .returning(["created", "updated", "edge"]),
1297 ),
1298 vec![("metadata", nested_metadata_param("param_id", 22))],
1299 vec![("metadata", QueryParamType::Object)],
1300 ),
1301 ),
1302 json_only(
1303 "907-nested-dynamic-property-read-shapes",
1304 with_params(
1305 read_request(
1306 read_batch()
1307 .var_as(
1308 "nested_users",
1309 g().n_where(SourcePredicate::and(vec![
1310 SourcePredicate::eq("$label", "ParityNested"),
1311 SourcePredicate::eq(
1312 "metadata.externalID",
1313 Expr::param("external_id"),
1314 ),
1315 ]))
1316 .where_(Predicate::compare(
1317 Expr::prop("metadata.score"),
1318 CompareOp::Gt,
1319 Expr::val(10i64),
1320 ))
1321 .order_by_multiple(vec![
1322 ("metadata.score", Order::Desc),
1323 ("name", Order::Asc),
1324 ])
1325 .project(vec![
1326 Projection::property("metadata.externalID", "external_id"),
1327 Projection::expr("score_copy", Expr::prop("metadata.score")),
1328 ]),
1329 )
1330 .var_as(
1331 "nested_values",
1332 g().n_with_label("ParityNested")
1333 .values(vec!["metadata.externalID"]),
1334 )
1335 .var_as(
1336 "nested_map",
1337 g().n_with_label("ParityNested")
1338 .value_map(Some(vec!["metadata.externalID", "metadata.score"])),
1339 )
1340 .var_as(
1341 "nested_edges",
1342 g().e_where(SourcePredicate::and(vec![
1343 SourcePredicate::eq("$label", "NESTED_LINK"),
1344 SourcePredicate::eq("metadata.externalID", "edge_id"),
1345 ]))
1346 .edge_has("metadata.externalID", PropertyInput::from("edge_id"))
1347 .edge_properties(),
1348 )
1349 .returning([
1350 "nested_users",
1351 "nested_values",
1352 "nested_map",
1353 "nested_edges",
1354 ]),
1355 ),
1356 vec![("external_id", string("param_id"))],
1357 vec![("external_id", QueryParamType::String)],
1358 ),
1359 ),
1360 json_only(
1361 "908-edge-endpoint-projection",
1362 read_request(
1363 read_batch()
1364 .var_as(
1365 "endpoints",
1366 g().e_with_label("FOLLOWS").project(vec![
1367 Projection::from_endpoint("externalId", "from_id"),
1368 Projection::to_endpoint("externalId", "to_id"),
1369 Projection::property("$id", "edge_id"),
1370 ]),
1371 )
1372 .returning(["endpoints"]),
1373 ),
1374 ),
1375 json_only(
1376 "909-row-binding-basic-projection",
1377 read_request(
1378 read_batch()
1379 .var_as(
1380 "bindings",
1381 g().n_with_label("ParityService")
1382 .bind("service")
1383 .project_bindings(vec![
1384 BindingProjection::binding("service", "$id", "service_id"),
1385 BindingProjection::current("metadata.name", "current_name"),
1386 BindingProjection::binding(
1387 "missing_binding",
1388 "externalId",
1389 "missing_external_id",
1390 ),
1391 ]),
1392 )
1393 .returning(["bindings"]),
1394 ),
1395 ),
1396 json_only(
1397 "910-row-binding-branch-distinct-projection",
1398 read_request(
1399 read_batch()
1400 .var_as(
1401 "workloads",
1402 g().n_with_label("ParityService")
1403 .bind("service")
1404 .out(Some("ROUTES_TO"))
1405 .bind("pod")
1406 .optional(sub().in_(Some("CREATES")).bind("deployment"))
1407 .union(vec![
1408 sub().in_(Some("MANAGES")).bind("owner"),
1409 sub().out(Some("ROUTES_TO")).bind("workload"),
1410 ])
1411 .project_distinct_bindings(vec![
1412 BindingProjection::binding("service", "$id", "service_id"),
1413 BindingProjection::coalesce(
1414 vec![
1415 BindingValueRef::binding("deployment", "$id"),
1416 BindingValueRef::binding("owner", "$id"),
1417 BindingValueRef::binding("workload", "$id"),
1418 ],
1419 "workload_id",
1420 ),
1421 ]),
1422 )
1423 .returning(["workloads"]),
1424 ),
1425 ),
1426 json_only(
1427 "911-range-index-direction",
1428 write_request(
1429 write_batch()
1430 .var_as(
1431 "node_desc",
1432 g().create_index_if_not_exists(IndexSpec::node_range_desc(
1433 "ParityUser",
1434 "age",
1435 )),
1436 )
1437 .var_as(
1438 "edge_desc",
1439 g().create_index_if_not_exists(IndexSpec::edge_range_desc(
1440 "FOLLOWS", "weight",
1441 )),
1442 )
1443 .var_as(
1444 "node_asc",
1445 g().create_index_if_not_exists(IndexSpec::node_range(
1446 "ParityUser",
1447 "score",
1448 )),
1449 )
1450 .returning(["node_desc", "edge_desc", "node_asc"]),
1451 ),
1452 ),
1453 ]
1454}