Skip to content

Commit a8eaac6

Browse files
committed
Add 'not' operator to filters
Seaography already supports 'and' and 'or' operators on filters. This commit adds a 'not' operator, which takes a filter object as an argument and maps this onto the corresponding Condition not() method in SeaORM. This is particularly useful for using operators that do not have an inverse version, such as array contains. For example, in one use case, we want to select all entities whose tags field does _not_ contain a specific value: { runs(filters: { not: { tags: { array_contains: ["disabled"] } } }) { nodes { id name status } } }
1 parent ced236b commit a8eaac6

File tree

3 files changed

+115
-0
lines changed

3 files changed

+115
-0
lines changed

examples/sea-draw/src/client_test.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub async fn client_test(
2525

2626
test_permissions(&url, root_account_id).await;
2727
test_entities(&url, root_account_id).await;
28+
test_boolean_operators(&url, root_account_id).await;
2829

2930
tracing::info!("All tests completed successfully!");
3031

@@ -329,6 +330,112 @@ async fn test_permissions(url: &str, root_account_id: Uuid) {
329330
);
330331
}
331332

333+
#[instrument(skip_all)]
334+
async fn test_boolean_operators(url: &str, root_account_id: Uuid) {
335+
let account_id = create_account(url, root_account_id, "Account 1").await;
336+
let client = Client::new(url, account_id);
337+
let project_id = client.create_project("Project 1").await.unwrap();
338+
339+
for (name, width, height) in [
340+
("Drawing 0", 300, 100),
341+
("Drawing 1", 400, 110),
342+
("Drawing 2", 500, 120),
343+
("Drawing 3", 600, 130),
344+
("Drawing 4", 700, 140),
345+
("Drawing 5", 300, 150),
346+
("Drawing 6", 400, 160),
347+
("Drawing 7", 500, 170),
348+
("Drawing 8", 600, 180),
349+
("Drawing 9", 700, 190),
350+
] {
351+
client
352+
.create_drawing(project_id, name, width, height)
353+
.await
354+
.unwrap();
355+
}
356+
357+
// not
358+
let query = r#"{ drawings(order_by: { name: ASC }, filters: {
359+
not: { width: { gt: 400 } }
360+
}) { nodes { name width height } } }"#;
361+
let result = graphql(url, account_id, query, None).await.unwrap();
362+
let expected1 = json!({
363+
"drawings": {
364+
"nodes": [
365+
{ "name": "Drawing 0", "width": 300, "height": 100},
366+
{ "name": "Drawing 1", "width": 400, "height": 110},
367+
{ "name": "Drawing 5", "width": 300, "height": 150},
368+
{ "name": "Drawing 6", "width": 400, "height": 160}
369+
]
370+
}
371+
});
372+
assert_eq!(result, expected1);
373+
374+
// and
375+
let expected2 = json!({
376+
"drawings": {
377+
"nodes": [
378+
{ "name": "Drawing 2", "width": 500, "height": 120 },
379+
{ "name": "Drawing 3", "width": 600, "height": 130 },
380+
{ "name": "Drawing 4", "width": 700, "height": 140 },
381+
{ "name": "Drawing 7", "width": 500, "height": 170 }
382+
]
383+
}
384+
});
385+
386+
let query = r#"{ drawings(order_by: { name: ASC }, filters: {
387+
and: [
388+
{ width: { gte: 500 } }
389+
{ height: { lte: 170 } }
390+
]
391+
}) { nodes { name width height } } }"#;
392+
let result = graphql(url, account_id, query, None).await.unwrap();
393+
assert_eq!(result, expected2);
394+
395+
// and + not
396+
// Same as above but with conditions inverted
397+
let query = r#"{ drawings(order_by: { name: ASC }, filters: {
398+
and: [
399+
{ not: { width: { lt: 500 } } }
400+
{ not: { height: { gt: 170 } } }
401+
]
402+
}) { nodes { name width height } } }"#;
403+
let result = graphql(url, account_id, query, None).await.unwrap();
404+
assert_eq!(result, expected2);
405+
406+
let expected3 = json!({
407+
"drawings": {
408+
"nodes": [
409+
{ "name": "Drawing 0", "width": 300, "height": 100 },
410+
{ "name": "Drawing 1", "width": 400, "height": 110 },
411+
{ "name": "Drawing 4", "width": 700, "height": 140 },
412+
{ "name": "Drawing 9", "width": 700, "height": 190 }
413+
]
414+
}
415+
});
416+
417+
// or
418+
let query = r#"{ drawings(order_by: { name: ASC }, filters: {
419+
or: [
420+
{ width: { gte: 700 } }
421+
{ height: { lte: 110 } }
422+
]
423+
}) { nodes { name width height } } }"#;
424+
let result = graphql(url, account_id, query, None).await.unwrap();
425+
assert_eq!(result, expected3);
426+
427+
// or + not
428+
// Same as above but with conditions inverted
429+
let query = r#"{ drawings(order_by: { name: ASC }, filters: {
430+
or: [
431+
{ not: { width: { lt: 700 } } }
432+
{ not: { height: { gt: 110 } } }
433+
]
434+
}) { nodes { name width height } } }"#;
435+
let result = graphql(url, account_id, query, None).await.unwrap();
436+
assert_eq!(result, expected3);
437+
}
438+
332439
pub async fn create_account(url: &str, root_account_id: Uuid, name: &str) -> Uuid {
333440
let result = graphql(
334441
url,

src/inputs/filter_input.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,6 @@ impl FilterInputBuilder {
5757
object
5858
.field(InputValue::new("and", TypeRef::named_nn_list(&filter_name)))
5959
.field(InputValue::new("or", TypeRef::named_nn_list(&filter_name)))
60+
.field(InputValue::new("not", TypeRef::named_nn(&filter_name)))
6061
}
6162
}

src/query/filtering.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,12 @@ where
8181
condition
8282
};
8383

84+
let condition = if let Some(not) = filters.get("not") {
85+
let nested_condition = recursive_prepare_condition::<T>(context, not.object()?)?;
86+
condition.add(nested_condition.not())
87+
} else {
88+
condition
89+
};
90+
8491
Ok(condition)
8592
}

0 commit comments

Comments
 (0)