The Query type

All load functions need a query, but how is this build?

The recommended way is to use the query! macro.

Alternatives are

  • to create a new Query<T> object and use its builder methods
  • or to parse a string

This chapter does not explain the Toql query language itself, see here to learn about that.

The query! macro

The query! macro will compile the provided string into Rust code. Any syntax mistakes, wrong path or field names show up as compiler errors!

Queries are typesafe, so query! takes a type and a query expression. See here:


# #![allow(unused_variables)]
#fn main() {
use toql::prelude::{query, Toql};

#[derive(Toql)]
struct User {
    #[toql(key)]
    id: u64,
    name: String
}

let user_id = 5;
let q = query!(User, "*, id eq ?",  user_id);
#}

To include query parameters just insert a question mark in the query string and provide the parameter after the string.

In the example above it would also be possible to put the number 5 directly into the query string, since it's a constant. The resulting SQL would be the same as Toql extracts the parameter in either case to prevent SQL injections.

The Toql query only works with numbers and strings, see SqlArg. However this is not be a problem: Since database columns have a type, the database is able convert a string or number into its column type.

It's also possible to include other queries into a query. Consider this:


# #![allow(unused_variables)]
#fn main() {
#   use toql::prelude::Toql;
#   #[derive(Toql)]
#   struct User {
#       #[toql(key)]
#       id: u64,
#       name: String
#   }
use toql::prelude::query;
let q1 = query!(User, "id eq ?", 5);
let q = query!(User, "*, {}", q1);
#}

Here we include the query q1 into q. Since queries are typesafe, so you can only include queries of the same type.

Working with keys

When entities have composite keys or you want to write generic code it's easier to work with keys. Key structs are automatically derived from the Toql derive and are located where the struct is. Keys contain all fields from the struct that are marked with #[toql(key)].

With a single key this is possible


# #![allow(unused_variables)]
#fn main() {
use toql::prelude::{query, Query};
# use toql::prelude::Toql;
#   #[derive(Toql)]
#   struct User {
#       #[toql(key)]
#       id: u64,
#       name: String
#   }

let k = UserKey::from(5); // Easier than UserKey{id:5};
let q1 = query!(User, "id eq ?", &k);
let q2 = query!(User, "*, {}", Query::from(&k));
let q3 = query!(User, "*, {}", k);
#}

With multiple keys you can do this:


# #![allow(unused_variables)]
#fn main() {
use toql::prelude::{query, Query};
#   use toql::prelude::Toql;
#   #[derive(Toql)]
#   struct User {
#       #[toql(key)]
#       id: u64,
#       name: String
#   }

let ks = vec![UserKey::from(1), UserKey::from(2)];

let q4 = query!(User, "*, id in ?", &ks);

let qk = ks.iter().collect::<Query<_>>();
let q5 = query!(User, "*, {}", qk);
#}

The query q4 only works for a simple key, not a composite key, whereas qk works for any type of key.

If you deal with entities you can get their keys from them (notice the Keyed trait):


# #![allow(unused_variables)]
#fn main() {
use toql::prelude::{query, Keyed, Query};
#   use toql::prelude::Toql;
#   #[derive(Toql)]
#   struct User {
#       #[toql(key)]
#       id: u64,
#       name: String
#   }
let e = User{id:1, name: "User 1".to_string()};

let q5 =  query!(User, "{}", e.key());
let q6 =  Query::from(e.key());
#}

Both q5 andq6 end up the same.

Or with mutiple entities:


# #![allow(unused_variables)]
#fn main() {
use toql::prelude::{query, MapKey, Query};
#   use toql::prelude::Toql;
#   #[derive(Toql)]
#   struct User {
#       #[toql(key)]
#       id: u64,
#       name: String
#   }

let es = vec![ User{ id:1, name:"User 1".to_string()}, 
                User{ id:2, name: "User 2".to_string()}];

let qk = es.iter().map_key().collect::<Query<_>>();
let q7 = query!(User, "*, {}", qk);
#}

Do you like the collect style? There is a nifty implementation detail: If you collect keys, they will always be concatenated with OR, queries however will be concatenated with AND.

Compare q8 and q10 here:


# #![allow(unused_variables)]
#fn main() {
use toql::prelude::{query, Query};
#   use toql::prelude::Toql;
#   #[derive(Toql)]
#   struct User {
#       #[toql(key)]
#       id: u64,
#       name: String
#   }

let ks = vec![UserKey{id:5}, UserKey{id:6}];
let q8 :Query<User> = ks.into_iter().collect(); 
assert_eq!(q8.to_string(), "(id EQ 5;id EQ 6)");

let q9 = query!(User, "name");
let q10 :Query<User> = vec![q9, q8].into_iter().collect(); 
assert_eq!(q10.to_string(), "name,(id EQ 5;id EQ 6)");

#}

The Into<Query> trait

In the example above the query q3 is build with a UserKey. This is possible because UserKey implements Into<Query<User>>. You can also implement this trait for you own types. Let's assume a book category.

Example 1: Adding an enum filter to the query


# #![allow(unused_variables)]
#fn main() {
use toql::prelude::{query, Query, Toql};

#[derive(Toql)]
struct Book {
    #[toql(key)]
    id: u64,
    category: Option<String>
}

enum BookCategory {
    Novel,
    Cartoon
}
impl Into<Query<Book>> for BookCategory {
    fn into(self) -> Query<Book> {
       query!(Book, "category EQ ?", 
       match self {
        Novel => "NOVEL",
        Cartoon => "CARTOON"    
       })
    }
}

// Now use it like so
let q = query!(Book, "*, {}", BookCategory::Novel);
assert_eq!(q.to_string(), "*,category EQ 'NOVEL'");
#}

Example 2: Adding an authorization filter to the query


# #![allow(unused_variables)]
#fn main() {
use toql::prelude::{QueryWith, Query, Field, QueryFields, Toql};
   
#[derive(Toql)]
struct Book {
    #[toql(key)]
    id: u64,
    category: Option<String>,
    #[toql(join)]
    author: Option<User>
}
#[derive(Toql)]
struct User {
    #[toql(key)]
    id: u64,
    name: Option<String>,
}

struct Auth {
    user_id: u64
}
impl Into<Query<Book>> for Auth {
    fn into(self) -> Query<Book> {

        // This time with the builder methods for educational reasons
        // In production do `query!(User, "author_id eq ?", self.user_id)`
        Query::from(Book::fields().author().id().eq(self.user_id))
    }
}
#}

You may want trade typesafety for more flexibility. See the example above again, this time with the Field type.


# #![allow(unused_variables)]
#fn main() {
use toql::prelude::{Query, Field};

struct Auth {
    author_id: u64
}

impl<T> Into<Query<T>> for Auth {
    fn into(self) -> Query<T>{
        Query::from(Field::from("authorId").eq(self.author_id))
    }
}
#}

Wrong field names in Field::from do not show up at compile time, but at runtime.

You can use both examples like so:


# #![allow(unused_variables)]
#fn main() {
use toql::prelude::query;
#   use toql::prelude::{Query, Field, Toql};
#   enum BookCategory {
#       Novel,
#       Cartoon
#   }
#   #[derive(Toql)]
#   struct Book {
#       #[toql(key)]
#       id: u64,
#       category: Option<String>
#   }
#   struct Auth {
#       author_id: u64
#   }
#   impl<T> Into<Query<T>> for Auth {
#       fn into(self) -> Query<T>{
#           Query::from(Field::from("authorId").eq(self.author_id))
#       }
#   }

let auth = Auth {author_id: 5};
let q = query!(Book, "*, {}", auth);
assert_eq!(q.to_string(), "*,authorId EQ 5");
#}

The QueryWith trait

The query! macro produces a Query type and can further be altered using all methods from that type. One interesting method is with. It takes a QueryWith trait that can be implemented for any custom type to enhance the query. This is more powerful than Into<Query> because you can also access auxiliary parameters.

Aux params can be used in SQL expressions. See here more information.


# #![allow(unused_variables)]
#fn main() {
use toql::prelude::{QueryWith, Query};

struct Config {
    limit_pages: u64
}
impl<T> QueryWith<T> for Config {
    fn with(&self, query: Query<T>) -> Query<T> {
        query.aux_param("limit_pages", self.limit_pages)
    }
}
#}

This can now be used like so:


# #![allow(unused_variables)]
#fn main() {
use toql::prelude::{query, SqlArg};
#   use toql::prelude::{Toql, Query, QueryWith};
#   #[derive(Toql)]
#   struct User {
#       #[toql(key)]
#       id: u64,
#       name: String
#   }
#   struct Config {
#       limit_pages: u64
#   }
#   impl<T> QueryWith<T> for Config {
#       fn with(&self, query: Query<T>) -> Query<T> {
#           query.aux_param("limit_pages", self.limit_pages)
#       }
#   }

let config = Config {limit_pages: 200};
let k = UserKey::from(5);
let q = query!(User, "*, {}", k).with(config);
assert_eq!(q.to_string(), "*,id EQ 5");
assert_eq!(q.aux_params.get("limit_pages"), Some(&SqlArg::U64(200)));
#}

Parsing queries

Use the query parser to turn a string into a Query type. Only syntax errors will returns as errors, wrong field names or paths will be rejected later when using the query.


# #![allow(unused_variables)]
#fn main() {
use toql::prelude::QueryParser;
#   use toql::prelude::Toql;
#   #[derive(Toql)]
#   struct User {
#       #[toql(key)]
#       id: u64,
#       name: String
#   }
let s = "*, id eq 5";

let q = QueryParser::parse::<User>(s).unwrap();
assert_eq!(q.to_string(), "*,id EQ 5");
#}