Skip to main content

generate_parity_fixtures/
generate_parity_fixtures.rs

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}