use super::*;
use crate::catalog::build_catalog;
use crate::query::parser::parse_query;
use crate::schema::parser::parse_schema;
fn setup() -> Catalog {
let schema = parse_schema(
r#"
node Person {
name: String
age: I32?
}
node Company {
name: String
}
edge Knows: Person -> Person {
since: Date?
}
edge WorksAt: Person -> Company {
title: String?
}
"#,
)
.unwrap();
build_catalog(&schema).unwrap()
}
fn setup_vector() -> Catalog {
let schema = parse_schema(
r#"
node Doc {
id_str: String
embedding: Vector(3)
}
"#,
)
.unwrap();
build_catalog(&schema).unwrap()
}
fn setup_list() -> Catalog {
let schema = parse_schema(
r#"
node Person {
name: String
tags: [String]?
}
"#,
)
.unwrap();
build_catalog(&schema).unwrap()
}
fn setup_embed_vector() -> Catalog {
let schema = parse_schema(
r#"
node Doc {
slug: String
body: String?
embedding: Vector(3) @embed(body)
}
"#,
)
.unwrap();
build_catalog(&schema).unwrap()
}
#[test]
fn test_basic_binding() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match { $p: Person }
return { $p.name }
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert!(ctx.bindings.contains_key("p"));
}
#[test]
fn test_t1_unknown_type() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match { $p: Foo }
return { $p.name }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T1"));
}
#[test]
fn test_t2_unknown_property_match() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match { $p: Person { salary: 100 } }
return { $p.name }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T2"));
}
#[test]
fn test_t3_wrong_type_in_match() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match { $p: Person { age: "old" } }
return { $p.name }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T3"));
}
#[test]
fn test_list_membership_match_accepts_scalar_literal() {
let catalog = setup_list();
let qf = parse_query(
r#"
query q() {
match { $p: Person { tags: "rust" } }
return { $p.name }
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert!(ctx.bindings.contains_key("p"));
}
#[test]
fn test_list_membership_match_accepts_scalar_param() {
let catalog = setup_list();
let qf = parse_query(
r#"
query q($tag: String) {
match { $p: Person { tags: $tag } }
return { $p.name }
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert!(ctx.bindings.contains_key("p"));
}
#[test]
fn test_list_equality_match_is_rejected() {
let catalog = setup_list();
let qf = parse_query(
r#"
query q() {
match { $p: Person { tags: ["rust"] } }
return { $p.name }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
let msg = err.to_string();
assert!(msg.contains("list equality is not supported"));
assert!(msg.contains("membership"));
}
#[test]
fn test_contains_filter_accepts_list_membership() {
let catalog = setup_list();
let qf = parse_query(
r#"
query q($tag: String) {
match {
$p: Person
$p.tags contains $tag
}
return { $p.name }
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert!(ctx.bindings.contains_key("p"));
}
#[test]
fn test_declared_list_params_typecheck() {
let catalog = setup_list();
let qf = parse_query(
r#"
query q($tags: [String], $days: [Date]?) {
match {
$p: Person
$p.tags contains "friend"
}
return { $p.tags, $tags, $days }
}
"#,
)
.unwrap();
assert!(typecheck_query(&catalog, &qf.queries[0]).is_ok());
}
#[test]
fn test_contains_filter_requires_list_left_operand() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match {
$p: Person
$p.name contains "Al"
}
return { $p.name }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(
err.to_string()
.contains("contains requires a list property on the left")
);
}
#[test]
fn test_contains_filter_rejects_list_right_operand() {
let catalog = setup_list();
let qf = parse_query(
r#"
query q() {
match {
$p: Person
$p.tags contains ["rust"]
}
return { $p.name }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(
err.to_string()
.contains("contains requires a scalar right operand")
);
}
#[test]
fn test_t4_unknown_edge() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match {
$p: Person
$p likes $f
}
return { $p.name }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T4"));
}
#[test]
fn test_t5_bad_endpoints() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match {
$c: Company
$c knows $f
}
return { $c.name }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T5"));
}
#[test]
fn test_t6_bad_property() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match {
$p: Person
$p.salary > 100
}
return { $p.name }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T6"));
}
#[test]
fn test_t7_bad_comparison() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match {
$p: Person
$p.age > "old"
}
return { $p.name }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T7"));
}
#[test]
fn test_t7_rejects_non_scalar_comparison() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match {
$p: Person
$p != 5
}
return { $p.name }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("scalar operands"));
}
#[test]
fn test_nearest_requires_limit() {
let catalog = setup_vector();
let qf = parse_query(
r#"
query q($q: Vector(3)) {
match { $d: Doc }
return { $d.id_str }
order { nearest($d.embedding, $q) }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T17"));
}
#[test]
fn test_nearest_vector_dim_mismatch() {
let catalog = setup_vector();
let qf = parse_query(
r#"
query q($q: Vector(2)) {
match { $d: Doc }
return { $d.id_str }
order { nearest($d.embedding, $q) }
limit 3
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T15"));
}
#[test]
fn test_nearest_vector_param_ok() {
let catalog = setup_vector();
let qf = parse_query(
r#"
query q($q: Vector(3)) {
match { $d: Doc }
return { $d.id_str }
order { nearest($d.embedding, $q) }
limit 3
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert!(ctx.bindings.contains_key("d"));
}
#[test]
fn test_nearest_string_param_ok() {
let catalog = setup_vector();
let qf = parse_query(
r#"
query q($q: String) {
match { $d: Doc }
return { $d.id_str }
order { nearest($d.embedding, $q) }
limit 3
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert!(ctx.bindings.contains_key("d"));
}
#[test]
fn test_search_string_param_ok() {
let catalog = setup();
let qf = parse_query(
r#"
query q($q: String) {
match {
$p: Person
search($p.name, $q)
}
return { $p.name }
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert!(ctx.bindings.contains_key("p"));
}
#[test]
fn test_fuzzy_max_edits_param_ok() {
let catalog = setup();
let qf = parse_query(
r#"
query q($q: String, $m: I64) {
match {
$p: Person
fuzzy($p.name, $q, $m)
}
return { $p.name }
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert!(ctx.bindings.contains_key("p"));
}
#[test]
fn test_fuzzy_rejects_non_integer_max_edits() {
let catalog = setup();
let qf = parse_query(
r#"
query q($q: String, $m: F64) {
match {
$p: Person
fuzzy($p.name, $q, $m)
}
return { $p.name }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T19"));
}
#[test]
fn test_match_text_string_param_ok() {
let catalog = setup();
let qf = parse_query(
r#"
query q($q: String) {
match {
$p: Person
match_text($p.name, $q)
}
return { $p.name }
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert!(ctx.bindings.contains_key("p"));
}
#[test]
fn test_bm25_string_param_ok() {
let catalog = setup();
let qf = parse_query(
r#"
query q($q: String) {
match { $p: Person }
return { $p.name, bm25($p.name, $q) as score }
order { bm25($p.name, $q) desc }
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert!(ctx.bindings.contains_key("p"));
}
#[test]
fn test_bm25_rejects_non_string_query() {
let catalog = setup();
let qf = parse_query(
r#"
query q($q: I64) {
match { $p: Person }
return { bm25($p.name, $q) as score }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T20"));
}
#[test]
fn test_rrf_requires_limit_in_order() {
let catalog = setup_vector();
let qf = parse_query(
r#"
query q($vq: Vector(3), $tq: String) {
match { $d: Doc }
return { $d.id_str }
order { rrf(nearest($d.embedding, $vq), bm25($d.id_str, $tq), 60) desc }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T21"));
}
#[test]
fn test_rrf_ordering_ok_with_limit() {
let catalog = setup_vector();
let qf = parse_query(
r#"
query q($vq: Vector(3), $tq: String) {
match { $d: Doc }
return { $d.id_str }
order { rrf(nearest($d.embedding, $vq), bm25($d.id_str, $tq), 60) desc }
limit 5
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert!(ctx.bindings.contains_key("d"));
}
#[test]
fn test_rrf_ordering_ok_with_string_nearest_limit() {
let catalog = setup_vector();
let qf = parse_query(
r#"
query q($vq: String, $tq: String) {
match { $d: Doc }
return { $d.id_str }
order { rrf(nearest($d.embedding, $vq), bm25($d.id_str, $tq), 60) desc }
limit 5
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert!(ctx.bindings.contains_key("d"));
}
#[test]
fn test_rrf_with_nearest_allows_alias_ordering() {
let catalog = setup_vector();
let qf = parse_query(
r#"
query q($vq: Vector(3), $tq: String) {
match { $d: Doc }
return {
$d.id_str,
rrf(nearest($d.embedding, $vq), bm25($d.id_str, $tq), 60) as score
}
order {
rrf(nearest($d.embedding, $vq), bm25($d.id_str, $tq), 60) desc,
score desc
}
limit 5
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert!(ctx.bindings.contains_key("d"));
}
#[test]
fn test_rrf_alias_ordering_requires_limit() {
let catalog = setup_vector();
let qf = parse_query(
r#"
query q($vq: Vector(3), $tq: String) {
match { $d: Doc }
return {
$d.id_str,
rrf(nearest($d.embedding, $vq), bm25($d.id_str, $tq), 60) as score
}
order { score desc }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T21"));
}
#[test]
fn test_rrf_alias_ordering_with_limit_is_valid() {
let catalog = setup_vector();
let qf = parse_query(
r#"
query q($vq: Vector(3), $tq: String) {
match { $d: Doc }
return {
$d.id_str,
rrf(nearest($d.embedding, $vq), bm25($d.id_str, $tq), 60) as score
}
order { score desc }
limit 5
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert!(ctx.bindings.contains_key("d"));
}
#[test]
fn test_standalone_nearest_with_alias_ordering_still_rejected() {
let catalog = setup_vector();
let qf = parse_query(
r#"
query q($vq: Vector(3)) {
match { $d: Doc }
return {
$d.id_str as score
}
order {
nearest($d.embedding, $vq),
score desc
}
limit 5
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T18"));
}
#[test]
fn test_rrf_rejects_non_rank_expression_argument() {
let parse = parse_query(
r#"
query q($q: String) {
match { $d: Doc }
return { $d.id_str }
order { rrf(bm25($d.id_str, $q), search($d.id_str, $q), 60) desc }
limit 5
}
"#,
);
assert!(parse.is_err());
}
#[test]
fn test_rrf_rejects_non_positive_k_literal() {
let catalog = setup_vector();
let qf = parse_query(
r#"
query q($vq: Vector(3), $tq: String) {
match { $d: Doc }
return { $d.id_str }
order { rrf(nearest($d.embedding, $vq), bm25($d.id_str, $tq), 0) desc }
limit 5
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T21"));
}
#[test]
fn test_t8_sum_on_string() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match { $p: Person }
return { sum($p.name) as s }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T8"));
}
#[test]
fn test_traversal_direction_out() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match {
$p: Person { name: "Alice" }
$p knows $f
}
return { $f.name }
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert_eq!(ctx.traversals[0].direction, Direction::Out);
assert_eq!(ctx.bindings["f"].type_name, "Person");
}
#[test]
fn test_traversal_direction_in() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match {
$c: Company { name: "Acme" }
$p worksAt $c
}
return { $p.name }
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert_eq!(ctx.traversals[0].direction, Direction::Out);
}
#[test]
fn test_bounded_traversal_typecheck() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match {
$p: Person
$p knows{1,3} $f
}
return { $f.name }
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert_eq!(ctx.traversals[0].min_hops, 1);
assert_eq!(ctx.traversals[0].max_hops, Some(3));
}
#[test]
fn test_bounded_traversal_invalid_bounds() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match {
$p: Person
$p knows{3,1} $f
}
return { $f.name }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T15"));
}
#[test]
fn test_unbounded_traversal_is_disabled() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match {
$p: Person
$p knows{1,} $f
}
return { $f.name }
}
"#,
)
.unwrap();
let err = typecheck_query(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("unbounded traversal is disabled"));
}
#[test]
fn test_negation_typecheck() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match {
$p: Person
not { $p worksAt $_ }
}
return { $p.name }
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert!(ctx.bindings.contains_key("p"));
}
#[test]
fn test_aggregation_typecheck() {
let catalog = setup();
let qf = parse_query(
r#"
query q() {
match {
$p: Person
$p knows $f
}
return {
$p.name
count($f) as friends
}
}
"#,
)
.unwrap();
typecheck_query(&catalog, &qf.queries[0]).unwrap();
}
#[test]
fn test_valid_two_hop() {
let catalog = setup();
let qf = parse_query(
r#"
query q($name: String) {
match {
$p: Person { name: $name }
$p knows $mid
$mid knows $fof
}
return { $fof.name }
}
"#,
)
.unwrap();
let ctx = typecheck_query(&catalog, &qf.queries[0]).unwrap();
assert!(ctx.bindings.contains_key("mid"));
assert!(ctx.bindings.contains_key("fof"));
}
#[test]
fn test_mutation_insert_typecheck_ok() {
let catalog = setup();
let qf = parse_query(
r#"
query add_person($name: String, $age: I32) {
insert Person {
name: $name
age: $age
}
}
"#,
)
.unwrap();
let checked = typecheck_query_decl(&catalog, &qf.queries[0]).unwrap();
match checked {
CheckedQuery::Mutation(ctx) => assert_eq!(ctx.target_types[0], "Person"),
_ => panic!("expected mutation typecheck result"),
}
}
#[test]
fn test_mutation_insert_missing_required_property() {
let catalog = setup();
let qf = parse_query(
r#"
query add_person($age: I32) {
insert Person { age: $age }
}
"#,
)
.unwrap();
let err = typecheck_query_decl(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T12"));
}
#[test]
fn test_mutation_insert_allows_embed_target_omission_when_source_present() {
let catalog = setup_embed_vector();
let qf = parse_query(
r#"
query add_doc($slug: String, $body: String) {
insert Doc {
slug: $slug
body: $body
}
}
"#,
)
.unwrap();
let checked = typecheck_query_decl(&catalog, &qf.queries[0]).unwrap();
match checked {
CheckedQuery::Mutation(ctx) => assert_eq!(ctx.target_types[0], "Doc"),
_ => panic!("expected mutation typecheck result"),
}
}
#[test]
fn test_mutation_insert_requires_embed_source_when_target_omitted() {
let catalog = setup_embed_vector();
let qf = parse_query(
r#"
query add_doc($slug: String) {
insert Doc {
slug: $slug
}
}
"#,
)
.unwrap();
let err = typecheck_query_decl(&catalog, &qf.queries[0]).unwrap_err();
let msg = err.to_string();
assert!(msg.contains("T12"));
assert!(msg.contains("embedding"));
assert!(msg.contains("body"));
}
#[test]
fn test_mutation_update_bad_property() {
let catalog = setup();
let qf = parse_query(
r#"
query update_person($name: String) {
update Person set { salary: 100 } where name = $name
}
"#,
)
.unwrap();
let err = typecheck_query_decl(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T11"));
}
#[test]
fn test_mutation_delete_bad_type() {
let catalog = setup();
let qf = parse_query(
r#"
query del($name: String) {
delete Unknown where name = $name
}
"#,
)
.unwrap();
let err = typecheck_query_decl(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T10"));
}
#[test]
fn test_mutation_insert_edge_typecheck_ok() {
let catalog = setup();
let qf = parse_query(
r#"
query add_knows($from: String, $to: String) {
insert Knows {
from: $from
to: $to
}
}
"#,
)
.unwrap();
let checked = typecheck_query_decl(&catalog, &qf.queries[0]).unwrap();
match checked {
CheckedQuery::Mutation(ctx) => assert_eq!(ctx.target_types[0], "Knows"),
_ => panic!("expected mutation typecheck result"),
}
}
#[test]
fn test_mutation_insert_edge_requires_from_and_to() {
let catalog = setup();
let qf = parse_query(
r#"
query add_knows($from: String) {
insert Knows {
from: $from
}
}
"#,
)
.unwrap();
let err = typecheck_query_decl(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T12"));
}
#[test]
fn test_mutation_delete_edge_typecheck_ok() {
let catalog = setup();
let qf = parse_query(
r#"
query del_knows($from: String) {
delete Knows where from = $from
}
"#,
)
.unwrap();
let checked = typecheck_query_decl(&catalog, &qf.queries[0]).unwrap();
match checked {
CheckedQuery::Mutation(ctx) => assert_eq!(ctx.target_types[0], "Knows"),
_ => panic!("expected mutation typecheck result"),
}
}
#[test]
fn test_mutation_update_edge_not_supported() {
let catalog = setup();
let qf = parse_query(
r#"
query upd_knows($from: String) {
update Knows set { since: 2000 } where from = $from
}
"#,
)
.unwrap();
let err = typecheck_query_decl(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T16"));
}
#[test]
fn test_mutation_multi_insert_typecheck_ok() {
let catalog = setup();
let qf = parse_query(
r#"
query add_and_link($name: String, $age: I32, $friend: String) {
insert Person { name: $name, age: $age }
insert Knows { from: $name, to: $friend }
}
"#,
)
.unwrap();
let checked = typecheck_query_decl(&catalog, &qf.queries[0]).unwrap();
match checked {
CheckedQuery::Mutation(ctx) => {
assert_eq!(ctx.target_types, vec!["Person", "Knows"]);
}
_ => panic!("expected mutation typecheck result"),
}
}
#[test]
fn test_mutation_multi_second_stmt_error() {
let catalog = setup();
let qf = parse_query(
r#"
query bad($name: String, $age: I32) {
insert Person { name: $name, age: $age }
insert Unknown { foo: $name }
}
"#,
)
.unwrap();
let err = typecheck_query_decl(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("T10"));
}
#[test]
fn test_now_expression_typechecks_as_datetime() {
let schema = parse_schema(
r#"
node Event {
slug: String @key
at: DateTime
}
"#,
)
.unwrap();
let catalog = build_catalog(&schema).unwrap();
let qf = parse_query(
r#"
query due() {
match {
$e: Event
$e.at <= now()
}
return { now() as ts }
}
"#,
)
.unwrap();
let checked = typecheck_query_decl(&catalog, &qf.queries[0]).unwrap();
assert!(matches!(checked, CheckedQuery::Read(_)));
}
#[test]
fn test_now_is_rejected_for_non_datetime_mutation_property() {
let schema = parse_schema(
r#"
node Event {
slug: String @key
on: Date
}
"#,
)
.unwrap();
let catalog = build_catalog(&schema).unwrap();
let qf = parse_query(
r#"
query stamp() {
update Event set { on: now() } where slug = "launch"
}
"#,
)
.unwrap();
let err = typecheck_query_decl(&catalog, &qf.queries[0]).unwrap_err();
assert!(err.to_string().contains("DateTime"));
assert!(err.to_string().contains("property `on`"));
}