Queries

Basic

Let's start with a simple struct definition:

use structsy_derive::Persistent;

#[derive(Persistent)]
struct WebPage {
	title:String,
	size:u32,
}

Now we can define a simple trait for basic conditions:

use structsy_derive::queries;

#[queries(WebPage)]
trait WebPageQueries {
	fn filter_by_title(self, title:String) -> Self;
	fn sized(self, size:u32) -> Self;
}

The name of the trait or the names of the methods do not really matter, anything can be used here what matters are the methods parameters names and types that have to match the struct fields names and types, also the signature of the function has to take self as input and return Self as output, this allow to chain calls and add multiple filters to a query.

Let's see how use this in a query:

// ... code that initialize a structsy context call structsy.
let query = structsy.query::<WebPage>().filter_by_title("Home".to_string()).sized(200);
for (id, page) in query.into_iter() {
	// access to the found pages.
}

This simple code find all the WebPage with exact title Home and the exact size 200, the condition written in this way will be all evaluate as using AND operator between them.

Operators

Often though are needed more complex conditions than the exact value, you may want to find anything with a field less than a value, more than a value or between a range, here structsy use a native interface of Rust for solve this problem, the RangeBounds, let's write an example filter for the case of WebPage.

use structsy_derive::queries;

#[queries(WebPage)]
trait WebPageRangeQueries {
	fn sizes<R:RangeBound<u32>>(self, size:R)-> Self;
}

here we just add a filter on the size where the value can be defined using rust ranges.
here an example how to use it:

let query = structsy.query::<WebPage>().sizes(10..300);
for (id, page) in query.into_iter() {
	// access to the found pages.
}

This find all the pages with a size between 10 and 300, obviously are supported all the possible rust range formats.

Boolean Grouping Operators

Is not finished here though, exact match and ranges can bring us very far but often we have more complex use cases that need to check alternative or negate conditions, for this cases structsy support the possibility to use some and,or and not operators, this are not defined by the traits that we define but come from a structsy trait called Operators, let's see some examples, using the previous defined WebPage struct and relative query traits:

use structsy::Operators;

let query = structsy.query::<WebPage>().or(|or| { 
                or.sizes(10..300).sizes(10000..)
            });
for (id, page) in query.into_iter() {
	// access to the found pages.
}

This example find all the pages that have the size between 10 and 300 or more that 10000

Now let's do a more complex example with all the operators:

use structsy::Operators;

let query = structsy.query::<WebPage>().or(|or| { 
            or.and(|and| {
                and.filter_by_title("Home".to_string()).sizes(10..300)
            }).not(|not| {
                not.filter_by_title("Home".to_string())
            })
        });
for (id, page) in query.into_iter() {
	// access to the found pages.
}

This query find all the pages with title "Home" and a size between 10 and 300 or all the pages with ad title not "Home".

Orders

For order a query result by a field is possible to use the Order type as a condition type for your field.

use structsy_derive::queries;
use structsy::Order;

#[queries(WebPage)]
trait WebPageOrderQueries {
	fn order_by_size(self, size:Order)-> Self;
}

let query = structsy.query::<WebPage>().order_by_size(Order::Asc);
for (id, page) in query.into_iter() {
	// access to the found pages ordered by size.
}

as shown in the example is sufficient to add a method with a parameter with the name of one of the field and as type the structsy::Order type, for then in the query use Order::Asc or Order:Desc to choose the kind of order.

Projections

In some case we are not interested to extract all the field of our domain entity, but we want to have only a subset of fields for this is possible to user projections, an example is

#[derive(Projection)]
#[projection = "WebPage"]
struct PageTitle {
    title:String,
}

When defined a projection in this way, is possible to use it in a query after applied the filters with the projection method

let query = structsy.query::<WebPage>().sizes(10..300).projection::<PageTitle>();
for (id, page) in query.into_iter() {
	// access to the found pages.
}

Embedded

For basic types I would say the example so far cover all the cases, but structsy support also another case, the struct with a struct embedded in it, let's start to define a example domain:

use structsy_derive::{Persistent, PersistentEmbedded};

#[derive(Persistent)]
struct House {
	address:Address,
	owner_name:String,
}

#[derive(PersistentEmbedded)]
struct Address {
	street:String,
	number:u32,
}

Now let's write some query traits on this domain considering the embedded struct

use structsy_derive::{queries,queries_embedded};
use structsy::Filter;

#[queries(House)]
trait HouseQuery {
	fn by_address(self, address:Filter<Address>)-> Self;
}

#[queries_embedded(Address)]
trait AddressQuery {
	fn by_street_and_number(self, street:&str, number:u32)->Self;
}

to highlight here the are two things, first to implement queries on top of embedded structs is needed to use queries_embedded macro instead of queries, second we have a condition on the address receiving a structsy type: Filter this type is the one used for define condition on embedded structs, so is the type that we also use as condition value to match only structs with embedded fields matching some defined conditions for the embedded struct.

Let's see an example on how to write a query using this filters:

let address_filter = Filter::<Address>::new().by_street_and_number("Rue de Paris", 10);
// we assume the variable structsy here is an instance of structsy defined ahead.
let query = structsy.query::<HouseQuery>().by_address(address_filter);
let res_iter = query.into_iter();

as we see here we are using Filter to define our new filter on our embedded type and adding conditions on top of it, than passing that filter to a query that will use it to match the conditions.