├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── 0-querybuilder-basics.rs ├── 1-model-basics.rs ├── 2-model-foreign-nodes.rs ├── 3-model-edges.rs ├── 4-querybuilder-conditional.rs ├── 5-model-serializer.rs ├── 6-queries-and-params.rs └── README.md ├── model-proc-macro ├── Cargo.toml ├── README.md ├── make-parser.sh └── src │ ├── ast.rs │ ├── ast │ ├── field.rs │ ├── identifier.rs │ ├── model.rs │ └── model_options.rs │ ├── lib.rs │ ├── parser.lalrpop │ └── parser.rs ├── rustfmt.toml ├── src ├── foreign_key │ ├── foreign_key.rs │ ├── into_key.rs │ ├── key_ser_control.rs │ ├── loaded_value.rs │ └── mod.rs ├── lib.rs ├── model │ ├── mod.rs │ ├── origin_holder.rs │ ├── relation_node.rs │ ├── schema_field.rs │ ├── serialize_error.rs │ └── serializer.rs ├── node_builder.rs ├── prelude.rs ├── queries │ ├── create.rs │ ├── delete.rs │ ├── impls.rs │ ├── mod.rs │ ├── select.rs │ └── update.rs ├── querybuilder.rs └── types │ ├── also.rs │ ├── and.rs │ ├── bind.rs │ ├── build.rs │ ├── cmp.rs │ ├── create.rs │ ├── delete.rs │ ├── equal.rs │ ├── ext.rs │ ├── fetch.rs │ ├── filter.rs │ ├── from.rs │ ├── greater.rs │ ├── limit.rs │ ├── lower.rs │ ├── mod.rs │ ├── on.rs │ ├── or.rs │ ├── order_by.rs │ ├── pagination.rs │ ├── plus_equal.rs │ ├── select.rs │ ├── set.rs │ ├── sql.rs │ └── update.rs └── tests ├── foreign.rs ├── model.rs ├── query_params.rs ├── querybuilder.rs └── surrealdb_client.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "surreal-simple-querybuilder" 3 | version = "0.8.1" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "A query-building & utility crate for SurrealDB and its SQL querying language that aims to be simple" 7 | readme = "README.md" 8 | keywords = ["surrealdb", "querybuilder", "query", "builder"] 9 | categories = ["database"] 10 | repository = "https://github.com/Aelto/surreal-simple-querybuilder" 11 | 12 | [workspace] 13 | members = [ 14 | "model-proc-macro" 15 | ] 16 | 17 | [features] 18 | default = ["querybuilder"] 19 | querybuilder = [] 20 | queries = ["dep:flatten-json-object"] 21 | model = ["dep:surreal-simple-querybuilder-proc-macro"] 22 | foreign = [] 23 | 24 | all = ["querybuilder", "queries", "model", "foreign"] 25 | 26 | [dependencies] 27 | serde = { version = "1.0.144", features = ["derive"] } 28 | serde_json = "1.0.91" 29 | once_cell = "1.17.1" 30 | 31 | surreal-simple-querybuilder-proc-macro = { path = "model-proc-macro", version = "0.8.0", optional = true } 32 | flatten-json-object = { version ="0.6.1", optional = true } 33 | 34 | [dev-dependencies] 35 | serde_json = "1.0.91" 36 | surrealdb = { version = "1.1.1", default-features = false, features = ["kv-mem"] } 37 | tokio = { version = "1.21.2", features = ["full"] } 38 | serde = { version = "1.0.152", features = ["serde_derive"] } 39 | 40 | [[example]] 41 | name = "querybuilder-basics" 42 | path = "examples/0-querybuilder-basics.rs" 43 | required-features = ["querybuilder"] 44 | 45 | [[example]] 46 | name = "model-basics" 47 | path = "examples/1-model-basics.rs" 48 | required-features = ["querybuilder", "model"] 49 | 50 | [[example]] 51 | name = "model-foreign-nodes" 52 | path = "examples/2-model-foreign-nodes.rs" 53 | required-features = ["querybuilder", "model", "foreign"] 54 | 55 | [[example]] 56 | name = "model-edges" 57 | path = "examples/3-model-edges.rs" 58 | required-features = ["querybuilder", "model", "foreign"] 59 | 60 | [[example]] 61 | name = "querybuilder-conditional" 62 | path = "examples/4-querybuilder-conditional.rs" 63 | required-features = ["querybuilder", "model"] 64 | 65 | [[example]] 66 | name = "model-serializer" 67 | path = "examples/5-model-serializer.rs" 68 | required-features = ["querybuilder", "model"] 69 | 70 | [[example]] 71 | name = "queries-and-params" 72 | path = "examples/6-queries-and-params.rs" 73 | required-features = ["model", "queries"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Thibault H 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Surreal simple querybuilder 2 | A simple query-builder for the Surreal Query Language, for [SurrealDB](https://surrealdb.com/). 3 | Aims at being simple to use and not too verbose first. 4 | 5 | ```rs 6 | #[derive(Debug, Serialize, Deserialize, Default)] 7 | pub struct IUser { 8 | #[serde(skip_serializing_if = "Option::is_none")] 9 | pub id: Option, 10 | pub handle: String, 11 | pub messages: ForeignVec, 12 | } 13 | 14 | model!(User { 15 | id, 16 | pub handle, 17 | pub messages 18 | }); 19 | 20 | impl IUser { 21 | pub fn find_by_handle(handle: &str) -> ApiResult> { 22 | use surreal_simple_querybuilder::queries::select; 23 | use schema::model as user; 24 | 25 | let (query, params) = select("*", user, (Where(user.handle, handle), Fetch([&*user.messages]))?; 26 | let items = DB.query(query).bind(params).await?.take(0)?; 27 | 28 | items 29 | } 30 | } 31 | ``` 32 | 33 | # Summary 34 | - [Surreal simple querybuilder](#surreal-simple-querybuilder) 35 | - [Summary](#summary) 36 | - [Why a query-builder](#why-a-query-builder) 37 | - [SQL injections](#sql-injections) 38 | - [Compiler requirements/features](#compiler-requirementsfeatures) 39 | - [Examples](#examples) 40 | - [Premade queries with dynamic parameters (`queries` feature)](#premade-queries-with-dynamic-parameters-queries-feature) 41 | - [Why dynamic parameters](#why-dynamic-parameters) 42 | - [Limitations \& recommandations for premade queries \& params](#limitations--recommandations-for-premade-queries--params) 43 | - [The `model` macro (`model` feature)](#the-model-macro-model-feature) 44 | - [public \& private fields in models](#public--private-fields-in-models) 45 | - [Relations between your models](#relations-between-your-models) 46 | - [Partials builder generation](#partials-builder-generation) 47 | - [The `NodeBuilder` traits (`querybuilder` feature)](#the-nodebuilder-traits-querybuilder-feature) 48 | - [The `QueryBuilder` type (`querybuilder` feature)](#the-querybuilder-type-querybuilder-feature) 49 | - [The `ForeignKey` and `Foreign` types (`foreign` feature)](#the-foreignkey-and-foreign-types-foreign-feature) 50 | - [`ForeignKey` and loaded data during serialization](#foreignkey-and-loaded-data-during-serialization) 51 | - [Using the querybuilder in combination of the official SurrealDB client](#using-the-querybuilder-in-combination-of-the-official-surrealdb-client) 52 | 53 | # Why a query-builder 54 | Query builders allow you to dynamically build your queries with some compile time 55 | checks to ensure they result in valid SQL queries. Unlike ORMs, query-builders are 56 | built to be lightweight and easy to use, meaning you decide when and where to use 57 | one. You could stick to hard coded string for the simple queries but use a builder 58 | for complex ones that require parameters & variables and may change based on these 59 | variables for example. 60 | 61 | While the crate is first meant as a query-building utility, it also comes with 62 | macros and generic types that may help you while managing you SQL models in your rust code. 63 | Refer to the [node macro](#the-node-macro) and the [Foreign type](#the-foreignkey-and-foreign-type) example 64 | 65 | # SQL injections 66 | The strings you pass to the query builder are not sanitized in any way. Please use 67 | parameters in your queries like `SET username = $username` with surrealdb parameters to avoid injection issues. 68 | However the crate comes with utility functions to easily create parameterized fields, refer to the [`NodeBuilder`](src/node_builder.rs) trait. 69 | 70 | # Compiler requirements/features 71 | The crate uses const expressions for its [model creation macros](#the-model-macro) 72 | in order to use stack based arrays with sizes deduced by the compiler. For this reason 73 | any program using the crate has to add the following at the root of the main file: 74 | ``` 75 | #![allow(incomplete_features)] 76 | #![feature(generic_const_exprs)] 77 | ``` 78 | 79 | # Examples 80 | 81 | > Keep in mind all of the demonstrated features can be used independently of the 82 | > rest. They can all be combined if you want to, but if you prefer a lightweight 83 | > solution then it is possible as well. 84 | > 85 | > By default only the querybuilder is available, other modules require you to 86 | > enable their respective crate features. 87 | 88 | - A series of [examples are available](/examples/) to offer a **guided introduction** to the core features of the crate 89 | - An all-in-one example can be found in the alternate [surrealdb-architecture](https://github.com/Aelto/surrealdb-architecture) repository 90 | - For an explanation of what each component in the crate does, refer to the chapters below. 91 | 92 | ## Premade queries with dynamic parameters (`queries` feature) 93 | The crate offers a set of premade queries you can access in [`surreal_simple_querybuilder::queries::*;`](src/queries) or 94 | in the prelude for easier access. 95 | ```rust 96 | use surreal_simple_querybuilder::prelude::*; 97 | 98 | fn main() { 99 | let (query, _bindings) = select("*", "user", ()); 100 | 101 | assert_eq!(query, "SELECT * FROM user"); 102 | } 103 | ``` 104 | 105 | these pre-made query functions accept all types of parameters to further extend 106 | the queries. If dynamic values (variables) are passed among these parameters then 107 | the functions will automatically add them to the list of bindings: 108 | ```rust 109 | use surreal_simple_querybuilder::prelude::*; 110 | use serde_json::json; 111 | 112 | fn main() { 113 | let (query, bindings) = select("*", "user", Where(json!({ "name": "John" }))); 114 | 115 | assert_eq!(query, "SELECT * FROM user WHERE name = $name"); 116 | 117 | // 👇 the bindings were updated with the $name variable 118 | assert_eq!(bindings.get("name"), Some("John".to_owned())); 119 | } 120 | ``` 121 | 122 | --- 123 | ### Why dynamic parameters 124 | 125 | At a first glance these pre-made queries offer nothing the querybuilder doesn't, 126 | but in reality they allow you to easily make functions in your backends (for example) 127 | that you can extend if need be. 128 | 129 | The first scenario that comes to mind is a standard function to retrieve books 130 | by the author: 131 | ```rust 132 | impl Book { 133 | fn find_by_author_id(id: &str) -> Vec { 134 | // ... 135 | } 136 | } 137 | ``` 138 | 139 | In some cases you'll need the list of books and nothing else, another time you'll need 140 | the results to be paginated, and sometimes you'll want to fetch the author data 141 | on top of the books. Considering you may also want to have the books with both pagination 142 | and fetch this could potentially result in at least 4 different functions & queries 143 | to write. 144 | 145 | With the dynamic parameters you can update your `find` function to accept optional 146 | parameters so that only 1 simple function is needed: 147 | 148 | ```rust 149 | use serde_json::json; 150 | 151 | impl Book { 152 | fn find_by_author_id<'a>(id: &str, params: impl QueryBuilderInjecter<'a> + 'a) -> Vec { 153 | let filter = Where(json!({"author": id})); 154 | let combined_params = (filter, params); 155 | 156 | let (query, params) = select("*", "Book", combined_params).unwrap(); 157 | 158 | DB.query(query) 159 | .bind(params) 160 | .await.unwrap() 161 | .get(..).unwrap() 162 | } 163 | } 164 | ``` 165 | So you can now do: 166 | ```rust 167 | let books = Book::find_by_author_id("User:john", ()); 168 | let paginated_books = Book::find_by_author_id("User:john", Pagination(0..25)); 169 | let paginated_books_with_author_data = Book::find_by_author_id( 170 | "User:john", 171 | ( 172 | Pagination(0..25), 173 | Fetch(["author"]) 174 | ) 175 | ); 176 | ``` 177 | 178 | The dynamic parameters & premade queries are made with the [model](#the-model-macro) macro in mind, 179 | you don't necessarily need it but if you wanted, both systems can be used for some 180 | compile time checks + dynamic parameters to enjoy the extra freedom dynamic parameters 181 | provide while being sure all of the fields & nodes you reference in them are valid 182 | thanks to the models. A complete example on how to combine both system is [available here](examples/6-queries-and-params.rs). 183 | 184 | Alternatively if the use of a generic argument is not your cup of tea, you can use an enum that implements 185 | `QueryBuilderInjecter`. The [surrealdb-architecture](https://github.com/Aelto/surrealdb-architecture#models-queries--params) 186 | repository demonstrates how to setup one. 187 | 188 | ### Limitations & recommandations for premade queries & params 189 | The [short example](examples/6-queries-and-params.rs) and [complete test case](test/../tests/src/surrealdb_client.rs) demonstrate 190 | the premade queries can work in 99% of the cases and can seriously simplify the 191 | code you write. However there are limitations one must be aware of before going 192 | too deep into the premade queries. 193 | 194 | The premade queries and composable parameters are made for those simple cases where 195 | you just want to select/create/etc... elements without complex filtering in the WHERE 196 | clause or anything. For example selecting books by one of their field is perfect 197 | for the premade queries as you can add a fetch clause without having to rewrite 198 | anything. It allows you to have somewhat generic functions in your codebase for the simple cases. 199 | 200 | But as soon as it gets complex, the [`QueryBuilder`](src/querybuilder.rs) type should 201 | be used instead of the pre-made queries. It will offer both better performances & more predictable results (nesting lots of params may yield unexpected queries). Note that you can still use a query-builder and pass it params (aka injecters) 202 | if needed: 203 | ```rust 204 | use surreal_simple_querybuilder::prelude::*; 205 | 206 | let params = ( 207 | Where(("name", "john")), 208 | Fetch(["articles"]) 209 | ); 210 | 211 | let query = QueryBuilder::new() 212 | .select("*") 213 | .from("user") 214 | .injecter(¶ms) // <-- pass the injecter to the builder 215 | .build(); 216 | 217 | let _params = bindings(params); // <-- get the variables so you can bind them 218 | 219 | assert(query, "SELECT * FROM user WHERE name = $name FETCH articles"); 220 | ``` 221 | 222 | And as you can see, even in the more complex cases the params can still be used but the pre-made queries should not however. 223 | 224 | ## The `model` macro (`model` feature) 225 | The `model` macro allows you to quickly create structs (aka models) with fields 226 | that match the nodes of your database. 227 | 228 |
229 | example 230 | 231 | ```rust 232 | use surreal_simple_querybuilder::prelude::*; 233 | 234 | struct Account { 235 | id: Option, 236 | handle: String, 237 | password: String, 238 | email: String, 239 | friends: Foreign> 240 | } 241 | 242 | model!(Account { 243 | id, 244 | handle, 245 | password, 246 | friends> 247 | }); 248 | 249 | fn main() { 250 | // the schema module is created by the macro 251 | use schema::model as account; 252 | 253 | let query = format!("select {} from {account}", account.handle); 254 | assert_eq!("select handle from Account", query); 255 | } 256 | ``` 257 |
258 | 259 | 260 | This allows you to have compile time checked constants for your fields, allowing 261 | you to reference them while building your queries without fearing of making a typo 262 | or using a field you renamed long time ago. 263 | 264 | ### public & private fields in models 265 | 266 | The QueryBuilder type offers a series of methods to quickly list the fields of your 267 | models in SET or UPDATE statements so you don't have to write the fields and the 268 | variable names one by one. Since you may not want to serialize some of the fields 269 | like the `id` for example the model macro has the `pub` keyword to mark a field 270 | as serializable. Any field without the `pub` keyword in front of it will not 271 | be serialized by these methods. 272 | 273 | ```rust 274 | model!(Project { 275 | id, // <- won't be serialized 276 | pub name, // <- will be serialized 277 | }) 278 | 279 | fn example() { 280 | use schema::model as project; 281 | 282 | let query = QueryBuilder::new() 283 | .set_model(project) 284 | .build(); 285 | 286 | assert_eq!(query, "SET name = $name"); 287 | } 288 | ``` 289 | 290 | 291 | ### Relations between your models 292 | If you wish to include relations (aka edges) in your models, the `model` macro 293 | has a special syntax for them: 294 | 295 | ```rust 296 | mod account { 297 | use surreal_simple_querybuilder::prelude::*; 298 | use super::project::schema::Project; 299 | 300 | model!(Account { 301 | id, 302 | 303 | ->manage->Project as managed_projects 304 | }); 305 | } 306 | 307 | mod project { 308 | use surreal_simple_querybuilder::prelude::*; 309 | use super::project::schema::Project; 310 | 311 | model!(Project { 312 | id, 313 | name, 314 | 315 | <-manage<-Account as authors 316 | }); 317 | } 318 | 319 | fn main() { 320 | use account::schema::model as account; 321 | 322 | let query = format!("select {} from {account}", account.managed_projects); 323 | assert_eq!("select ->manage->Project from Account"); 324 | 325 | let query = format!("select {} from {account}", account.managed_projects().name.as_alias("project_names")) 326 | assert_eq!("select ->manage->Project.name as project_names from Account", query); 327 | } 328 | ``` 329 | 330 | ### Partials builder generation 331 | The macro supports condition flags you can pass to generate more code for you. One 332 | of them is the generation of a "Partial" builder. A partial type is a copy of the 333 | model you created where all fields are `Option` set with the serde 334 | flag to skip the fields that are `None` during serialization. 335 | 336 | Such a partial builder can be used like so: 337 | ```rust 338 | // notice the `with(partial)` 339 | model!(Project with(partial) { 340 | id, 341 | pub name 342 | }); 343 | 344 | let partial_user = PartialProject::new() 345 | .name("John Doe"); 346 | ``` 347 | 348 | This partial type comes handy when constructing queries with nested fields thanks 349 | to its `ok()` method: 350 | ```rust 351 | let partial_post = PartialPost::new() 352 | .title("My post title") 353 | .author(PartialUser::new().name("John Doe")) 354 | .ok()?; 355 | ``` 356 | which will output the following flattened json: 357 | ```json 358 | { 359 | "title": "My post title", 360 | "author.name": "John Doe" 361 | } 362 | ``` 363 | If you'd like a normal nested object then you can skip the `ok` call and past the object to the serialize function of your choice. 364 | 365 | You can then use the builder in your queries: 366 | ```rust 367 | let user = DB.update(user_id) 368 | .merge(PartialUser::new() 369 | .name("Jean") 370 | .posts(vec![post1_id, post2_id]) 371 | .ok()? 372 | ).await? 373 | 374 | // ... 375 | 376 | let filter = Where(PartialPost::new() 377 | .title("My post title") 378 | .author(PartialUser::new().name("John Doe")) 379 | .ok()?); 380 | 381 | let posts = queries.select("*", "post", filter).await?; 382 | ``` 383 | 384 | Note that partial builders are an alternative syntax to building the json objects using the `serde_json::json!` macro combined with the models. The above example is the same as the following example, so pick whatever solution you prefer: 385 | ```rust 386 | let user = DB.update(user_id) 387 | .merge(json!({ 388 | model.name: "Jean", 389 | model.posts: vec![post1_id, post2_id] 390 | })).await? 391 | 392 | // ... 393 | 394 | // the wjson! macro is a shortcut to `Where(json!())` 395 | let filter = wjson!({ 396 | model.title: "My post title", 397 | model.author().name: "John Doe" 398 | }); 399 | 400 | let posts = select("*", "post", filter).await?; 401 | ``` 402 | 403 | ## The `NodeBuilder` traits (`querybuilder` feature) 404 | These traits add a few utility functions to the `String` and `str` types that can 405 | be used alongside the querybuilder for even more flexibility. 406 | 407 | ```rust 408 | use surreal_simple_querybuilder::prelude::*; 409 | 410 | let my_label = "John".as_named_label("Account"); 411 | assert_eq!("Account:John", &my_label); 412 | 413 | let my_relation = my_label 414 | .with("FRIEND") 415 | .with("Mark".as_named_label("Account")); 416 | 417 | assert_eq!("Account:John->FRIEND->Account:Mark", my_relation); 418 | ``` 419 | 420 | 421 | ## The `QueryBuilder` type (`querybuilder` feature) 422 | It allows you to dynamically build complex or simple queries out of _segments_ and easy to use 423 | methods. 424 |
425 | Simple example 426 | 427 | ```rust 428 | use surreal_simple_querybuilder::prelude::*; 429 | 430 | let query = QueryBuilder::new() 431 | .select("*") 432 | .from("Account") 433 | .build(); 434 | 435 | assert_eq!("SELECT * FROM Account", &query); 436 | ``` 437 |
438 | 439 |
440 | Complex example 441 | 442 | ```rust 443 | use surreal_simple_querybuilder::prelude::*; 444 | 445 | let should_fetch_authors = false; 446 | let query = QueryBuilder::new() 447 | .select("*") 448 | .from("File") 449 | .if_then(should_fetch_authors, |q| q.fetch("author")) 450 | .build(); 451 | 452 | assert_eq!("SELECT * FROM Account", &query); 453 | 454 | let should_fetch_authors = true; 455 | let query = QueryBuilder::new() 456 | .select("*") 457 | .from("File") 458 | .if_then(should_fetch_authors, |q| q.fetch("author")) 459 | .build(); 460 | 461 | assert_eq!("SELECT * FROM Account FETCH author", &query); 462 | ``` 463 |
464 | 465 | 466 | ## The `ForeignKey` and `Foreign` types (`foreign` feature) 467 | SurrealDB has the ability to fetch the data out of foreign keys. For example: 468 | ```sql 469 | create Author:JussiAdlerOlsen set name = "Jussi Adler-Olsen"; 470 | create File set name = "Journal 64", author = Author:JussiAdlerOlsen; 471 | 472 | select * from File; 473 | select * from File fetch author; 474 | ``` 475 | which gives us 476 | ```json 477 | // without FETCH author 478 | { 479 | "author": "Author:JussiAdlerOlsen", 480 | "id":"File:rg30uybsmrhsf7o6guvi", 481 | "name":"Journal 64" 482 | } 483 | 484 | // with FETCH author 485 | { 486 | "author": { 487 | "id":"Author:JussiAdlerOlsen", 488 | "name":"Jussi Adler-Olsen" 489 | }, 490 | "id":"File:rg30uybsmrhsf7o6guvi", 491 | "name":"Journal 64" 492 | } 493 | ``` 494 | 495 | The "issue" with this functionality is that our results may either contain an ID 496 | to the author, no value, or the fully fetched author with its data depending on 497 | the query and whether it includes `fetch` or not. 498 | 499 | The `ForeignKey` types comes to the rescue. It is an enum with 3 variants: 500 | - The loaded data for when it was fetched 501 | - The key data for when it was just an ID 502 | - The unloaded data when it was null (if you wish to support missing data you must use the `#serde(default)` attribute to the field) 503 | 504 | The type comes with an implementation of the Deserialize and Serialize serde traits 505 | so that it can fallback to whatever data it finds or needs. However any type that 506 | is referenced by a `ForeignKey` must implement the `IntoKey` trait that allows it 507 | to safely serialize it into an ID during serialization. 508 | 509 |
510 | example 511 | 512 | ```rust 513 | /// For the tests, and as an example we are creating what could be an Account in 514 | /// a simple database. 515 | #[derive(Debug, Serialize, Deserialize, Default)] 516 | struct Account { 517 | id: Option, 518 | handle: String, 519 | password: String, 520 | email: String, 521 | } 522 | 523 | impl IntoKey for Account { 524 | fn into_key(&self) -> Result 525 | where 526 | E: serde::ser::Error, 527 | { 528 | self 529 | .id 530 | .as_ref() 531 | .map(String::clone) 532 | .ok_or(serde::ser::Error::custom("The account has no ID")) 533 | } 534 | } 535 | 536 | #[derive(Debug, Serialize, Deserialize)] 537 | struct File { 538 | name: String, 539 | 540 | /// And now we can set the field as a Foreign node 541 | author: Foreign, 542 | } 543 | 544 | fn main() { 545 | // ...imagine `query` is a function to send a query and get the first result... 546 | let file: File = query("SELECT * from File FETCH author"); 547 | 548 | if let Some(user) = file.author.value() { 549 | // the file had an author and it was loaded 550 | dbg!(&user); 551 | } 552 | 553 | // now we could also support cases where we do not want to fetch the authors 554 | // for performance reasons... 555 | let file: File = query("SELECT * from File"); 556 | 557 | if let Some(user_id) = file.author.key() { 558 | // the file had an author ID, but it wasn't fetched 559 | dbg!(&user_id); 560 | } 561 | 562 | // we can also handle the cases where the field was missing 563 | if file.author.is_unloaded { 564 | panic!("Author missing in file {file}"); 565 | } 566 | } 567 | ``` 568 |
569 | 570 | ### `ForeignKey` and loaded data during serialization 571 | 572 | A `ForeignKey` always tries to serialize itself into an ID by default. Meaning that 573 | if the foreign-key holds a value and not an ID, it will call the `IntoKey` trait on 574 | the value in order to get an ID to serialize. 575 | 576 | There are cases where this may pose a problem, for example in an API where you wish 577 | to serialize a struct with `ForeignKey` fields so the users can get all the data 578 | they need in a single request. 579 | 580 | By default if you were to serialize a `File` (from the example above) struct 581 | with a fetched `author`, it would automatically be converted into the author's id. 582 | 583 | The `ForeignKey` struct offers two methods to control this behaviour: 584 | ```rust 585 | // ...imagine `query` is a function to send a query and get the first result... 586 | let file: File = query("SELECT * from File FETCH author"); 587 | 588 | file.author.allow_value_serialize(); 589 | 590 | // ... serializing `file` will now serialize its author field as-is. 591 | 592 | // to go back to the default behaviour 593 | file.author.disallow_value_serialize(); 594 | ``` 595 | 596 | You may note that mutability is not needed, the methods use interior mutability 597 | to work even on immutable ForeignKeys if needed. 598 | 599 | ## Using the querybuilder in combination of the [official SurrealDB client](https://github.com/surrealdb/surrealdb/tree/main/lib) 600 | There is an important thing to keep in mind with this querybuilding crate, it is meant to serve as an utility crate that is completely independant of the client you use. For this reason it does not offer anything to send the queries and getting the responses directly but since you'll rarely want to use this crate without a client, I am maintaining an [external repository as a demo of how to combine the official client & the surreal-simple-querybuilder crate](https://github.com/Aelto/surrealdb-architecture). 601 | 602 | While it is not convenient to have to write these functions yourself it allows you to use a fixed version of the querybuilder crate while still getting the latest breaking updates on your favorite client. 603 | 604 | -------------------------------------------------------------------------------- /examples/0-querybuilder-basics.rs: -------------------------------------------------------------------------------- 1 | use surreal_simple_querybuilder::querybuilder::QueryBuilder; 2 | 3 | fn main() { 4 | let query = QueryBuilder::new() 5 | .select("*") 6 | .from("User") 7 | .filter("age > 10") 8 | .and("name = 'John'") 9 | .build(); 10 | 11 | // SELECT * FROM User WHERE age > 10 AND name = 'John' 12 | println!("query: {query}"); 13 | } 14 | -------------------------------------------------------------------------------- /examples/1-model-basics.rs: -------------------------------------------------------------------------------- 1 | use surreal_simple_querybuilder::prelude::*; 2 | 3 | // Using the `model` macro you can quickly define the schemas from of your 4 | // database using a rust-like syntax. 5 | model!(User { 6 | pub age, 7 | pub name 8 | }); 9 | 10 | fn main() { 11 | // 👇 a "schema" module is created with a static "model" variable you can use 12 | // 👇 anywhere in your code. 13 | use schema::model as user; 14 | 15 | let query = QueryBuilder::new() 16 | .select("*") 17 | // you can pass the entire model directly to reference its name 18 | .from(user) 19 | // or you can access its fields and use the various traits imported from 20 | // the querybuilding crate to form complex queries 21 | .filter(user.age.greater_than("10")) 22 | .and(user.name.equals("'John'")) 23 | .build(); 24 | 25 | // SELECT * FROM User WHERE age > 10 AND name = 'John' 26 | println!("query: {query}"); 27 | } 28 | -------------------------------------------------------------------------------- /examples/2-model-foreign-nodes.rs: -------------------------------------------------------------------------------- 1 | #![allow(incomplete_features)] 2 | #![feature(generic_const_exprs)] 3 | 4 | use surreal_simple_querybuilder::prelude::*; 5 | 6 | model!(User { 7 | pub age, 8 | pub name, 9 | 10 | // 👇 you can define foreign nodes that may represented either by their IDs 11 | // 👇 or the complete type with all of its fields. 12 | best_friend 13 | }); 14 | 15 | fn main() { 16 | use schema::model as user; 17 | 18 | let query = QueryBuilder::new() 19 | // 👇 pass the field to reference the User's `best_friend` field 20 | .select(user.best_friend) 21 | .from(user) 22 | // 👇 call the function with the same name to access the foreign type's fields 23 | .filter(user.best_friend().name.equals("'John'")) 24 | .build(); 25 | 26 | // SELECT best_friend FROM User WHERE best_friend.name = 'John' 27 | println!("query: {query}"); 28 | } 29 | -------------------------------------------------------------------------------- /examples/3-model-edges.rs: -------------------------------------------------------------------------------- 1 | #![allow(incomplete_features)] 2 | #![feature(generic_const_exprs)] 3 | 4 | use surreal_simple_querybuilder::prelude::*; 5 | 6 | model!(User { 7 | pub age, 8 | pub name, 9 | 10 | best_friend, 11 | 12 | // 👇 edges can be defined using a custom syntax similar to the SQL one 13 | ->likes->User as friends 14 | }); 15 | 16 | fn main() { 17 | use schema::model as user; 18 | 19 | let query = QueryBuilder::new() 20 | // 👇 edges can be referenced using an alias 21 | .select(user.friends.as_alias("friends")) 22 | .from(user) 23 | // 👇 but also in queries 24 | .filter(user.friends.filter(&user.age.greater_than("10"))) 25 | .build(); 26 | 27 | // SELECT ->likes->User AS friends FROM User WHERE ->likes->(User WHERE age > 10) 28 | println!("query: {query}"); 29 | } 30 | -------------------------------------------------------------------------------- /examples/4-querybuilder-conditional.rs: -------------------------------------------------------------------------------- 1 | #![allow(incomplete_features)] 2 | #![feature(generic_const_exprs)] 3 | 4 | use surreal_simple_querybuilder::prelude::*; 5 | 6 | model!(User { 7 | pub age, 8 | pub name, 9 | }); 10 | 11 | fn main() { 12 | use schema::model as user; 13 | 14 | let filter_name: Option<&str> = Some("John"); 15 | let filter_age: Option<&str> = None; 16 | 17 | let query = QueryBuilder::new() 18 | .select("*") 19 | .from(user) 20 | .filter("true") 21 | .if_then(filter_name.is_some(), |q| { 22 | q.and(user.name.equals(&filter_name.unwrap().quoted())) 23 | }) 24 | .if_then(filter_age.is_some(), |q| { 25 | q.and(user.age.equals(filter_age.unwrap())) 26 | }) 27 | .build(); 28 | 29 | // SELECT * FROM User WHERE true AND name = "John" 30 | println!("query: {query}"); 31 | } 32 | -------------------------------------------------------------------------------- /examples/5-model-serializer.rs: -------------------------------------------------------------------------------- 1 | #![allow(incomplete_features)] 2 | #![feature(generic_const_exprs)] 3 | 4 | use surreal_simple_querybuilder::prelude::*; 5 | 6 | model!(User { 7 | // 👇 note how id is not `pub` 8 | id, 9 | 10 | // 👇 while these two fields are 11 | pub age, 12 | pub name, 13 | }); 14 | 15 | fn main() -> Result<(), SqlSerializeError> { 16 | use schema::model as user; 17 | 18 | let query = QueryBuilder::new() 19 | .create(user) 20 | // 👇 all `pub` fields will be serialized while the others won't. 21 | .set_model(&user)? 22 | .build(); 23 | 24 | // CREATE User SET age = $age , name = $name 25 | println!("query: {query}"); 26 | 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /examples/6-queries-and-params.rs: -------------------------------------------------------------------------------- 1 | #![allow(incomplete_features)] 2 | #![feature(generic_const_exprs)] 3 | #![allow(dead_code, unused_variables)] 4 | 5 | use std::collections::HashMap; 6 | 7 | use serde_json::json; 8 | use surreal_simple_querybuilder::prelude::*; 9 | 10 | // a fake query function, imagine it calls to the DB client of your choices and 11 | // returns what you expect 12 | fn fake_query(query: String, params: HashMap) -> T { 13 | todo!() 14 | } 15 | 16 | model!(User { 17 | id, 18 | pub age, 19 | pub name, 20 | }); 21 | 22 | // 👇 let's imagine we are building a type for our User nodes 23 | struct User { 24 | id: Option, 25 | age: u8, 26 | name: String, 27 | } 28 | 29 | impl User { 30 | fn new(name: String, age: u8) -> Self { 31 | Self { 32 | id: None, 33 | name, 34 | age, 35 | } 36 | } 37 | 38 | // 👇 then we decide to create a function to fetch a user with a specific name: 39 | // NOTE: errors are ignored for brevity 40 | fn find_by_name(searched_name: &str) -> Option { 41 | use schema::model as user; 42 | 43 | // 👇 we construct our parameters using the `Where` type and we pass it to 44 | // the select function. It generates the SQL query for us and also gives 45 | // us a hashmap of the variables that were used in the query and their 46 | // values so we can pass them to our client. 47 | let params = Where(json!({ user.name: searched_name })); 48 | let (query, params) = select("*", &user, params).unwrap(); 49 | 50 | fake_query(query, params) 51 | } 52 | 53 | // 👇 now let's say we'd want to fetch user but with different filters and 54 | // ideally pagination to avoid fetching thousands of users at once. 55 | // Coding a method for all types of combination we want (Pagination, Where, Fetch) 56 | // can quickly become repetitive and cumbersome, let's make a generic function 57 | // instead. 58 | // 59 | // This is how you'd connect the Surreal Simple Querybuilder crate to the 60 | // client of your choice. 61 | // 62 | // Read the `main` function below to see how to use it now 63 | fn find<'a, T>(params: impl QueryBuilderInjecter<'a> + 'a) -> T { 64 | use schema::model as user; 65 | 66 | let (query, params) = select("*", &user, params).unwrap(); 67 | 68 | fake_query(query, params) 69 | } 70 | } 71 | 72 | fn main() -> Result<(), SqlSerializeError> { 73 | use schema::model as user; 74 | 75 | // 👇 this will select all users named John. 76 | // 77 | // SELECT * FROM User where name = "John" 78 | let _all_johns: Vec = User::find(Where(json!({ user.name: "John" }))); 79 | 80 | // 👇 this will select all users, but only from the 10th to the 35th one 81 | // 82 | // SELECT * FROM User LIMIT 25 START AT 10 83 | let _paginated_users: Vec = User::find(Pagination::from(10..35)); 84 | 85 | // 👇 the combination of both params from above 86 | // 87 | // SELECT * FROM User WHERE name = "John" LIMIT 25 START AT 10 88 | let filter = Where(json!({ user.name: "John" })); 89 | let pagination = Pagination::from(10..35); 90 | // 👇 you can combine them with tuples 91 | let _paginated_johns: Vec = User::find((filter, pagination)); 92 | 93 | Ok(()) 94 | } 95 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | > It is recommended to read the examples from top to bottom, and to read the comments 3 | > starting with the 👇 emoji. 4 | 5 | 6 | | File |Description | Crate features | 7 | |------|------------|----------------| 8 | | [`0-querybuilder-basics.rs`](./0-querybuilder-basics.rs) |displays a basic usage of the `QueryBuilder` type | `querybuilder` (default) | 9 | | [`1-model-basics`](./1-model-basics.rs) | demonstrates how the `model` macro is used and where it can help you | `querybuilder`, `model` | 10 | | [`2-model-foreign-nodes`](./2-model-foreign-nodes.rs) | continues on the `model` macro and explains the foreign nodes | `querybuilder`, `model`, `foreign` | 11 | | [`3-model-edges`](./3-model-edges.rs) | continues on the `model` macro too and explains edges | `querybuilder`, `model` | 12 | | [`4-querybuilder-conditional`](./4-querybuilder-conditional.rs) | shows how the `QueryBuilder` type can be used for dynamic queries based on conditions | `querybuilder`, `model` | 13 | | [`5-model-serializer`](./5-model-serializer.rs) | explains what the `pub` keyword does in the `model` macro and how it can be used in the QueryBuilder as well | `querybuilder`, `model` | 14 | | [`6-queries-and-params`](./6-queries-and-params.rs) | demonstrates how to use the premade queries offered by the crate and now they can be enhanced with the custom param types | `model`, `queries` | 15 | | [6-bis](../tests/src/surrealdb_client.rs) | shows a complete example of the premade queries, the params and how to use them with the official surrealdb client. | `querybuilder`, `model`, `foreign`, `queries` (or `all`) | 16 | -------------------------------------------------------------------------------- /model-proc-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "surreal-simple-querybuilder-proc-macro" 3 | version = "0.8.0" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "The proc macro for a query-building & utility crate for SurrealDB and its SQL querying language that aims to be simple" 7 | readme = "../README.md" 8 | keywords = ["surrealdb", "querybuilder", "query", "builder"] 9 | categories = ["database"] 10 | repository = "https://github.com/Aelto/surreal-simple-querybuilder" 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [build-dependencies] 16 | lalrpop = "0.20.0" 17 | 18 | [dependencies] 19 | lalrpop-util = { version = "0.20.0", features = ["lexer"] } 20 | quote = "1.0" 21 | -------------------------------------------------------------------------------- /model-proc-macro/README.md: -------------------------------------------------------------------------------- 1 | # Building the parser.rs file 2 | In order to generate a `parser.rs` file from the `parser.lalrpop` file, you can install lalrpop using cargo install: 3 | ```bash 4 | cargo install lalrpop 5 | ``` 6 | 7 | then run the following command, or use the `make-parser.sh` script: 8 | ```bash 9 | lalrpop src/parser.lalrpop 10 | ``` 11 | -------------------------------------------------------------------------------- /model-proc-macro/make-parser.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | lalrpop src/parser.lalrpop -------------------------------------------------------------------------------- /model-proc-macro/src/ast.rs: -------------------------------------------------------------------------------- 1 | mod field; 2 | mod model; 3 | mod model_options; 4 | mod identifier; 5 | 6 | pub use field::*; 7 | pub use model::*; 8 | pub use model_options::*; 9 | pub use identifier::*; -------------------------------------------------------------------------------- /model-proc-macro/src/ast/field.rs: -------------------------------------------------------------------------------- 1 | use quote::__private::TokenStream; 2 | use quote::quote; 3 | 4 | use super::Identifier; 5 | 6 | #[derive(Debug, Clone)] 7 | pub enum Field { 8 | Property(FieldProperty), 9 | ForeignNode(FieldForeignNode), 10 | Relation(FieldRelation), 11 | } 12 | 13 | impl Field { 14 | pub fn emit_field(&self) -> TokenStream { 15 | match self { 16 | Field::Property(x) => x.emit_field(), 17 | Field::ForeignNode(x) => x.emit_field(), 18 | Field::Relation(x) => x.emit_field(), 19 | } 20 | } 21 | 22 | pub fn emit_initialization(&self) -> TokenStream { 23 | match self { 24 | Field::Property(x) => x.emit_initialization(), 25 | Field::ForeignNode(x) => x.emit_initialization(), 26 | Field::Relation(x) => x.emit_initialization(), 27 | } 28 | } 29 | 30 | pub fn emit_initialization_with_origin(&self) -> TokenStream { 31 | match self { 32 | Field::Property(x) => x.emit_initialization_with_origin(), 33 | Field::ForeignNode(x) => x.emit_initialization_with_origin(), 34 | Field::Relation(x) => x.emit_initialization_with_origin(), 35 | } 36 | } 37 | 38 | pub fn emit_foreign_field_function(&self) -> TokenStream { 39 | match self { 40 | Field::Property(x) => x.emit_foreign_field_function(), 41 | Field::ForeignNode(x) => x.emit_foreign_field_function(), 42 | Field::Relation(x) => x.emit_foreign_field_function(), 43 | } 44 | } 45 | 46 | pub fn emit_partial_setter_field_function(&self) -> TokenStream { 47 | let field_name = match self { 48 | Field::Property(p) => &p.name, 49 | Field::ForeignNode(f) => &f.name, 50 | Field::Relation(r) => &r.name, 51 | }; 52 | 53 | let name = field_name.to_ident(); 54 | 55 | quote!( 56 | pub fn #name (mut self, value: impl serde::Serialize) -> Self { 57 | self.__insert_value_result(stringify!(#name), value) 58 | } 59 | ) 60 | } 61 | } 62 | 63 | /// A simple property 64 | #[derive(Debug, Clone)] 65 | pub struct FieldProperty { 66 | pub name: Identifier, 67 | 68 | pub is_public: bool, 69 | } 70 | 71 | impl FieldProperty { 72 | fn emit_field(&self) -> TokenStream { 73 | let name = self.name.to_ident(); 74 | let attribute = match self.is_public { 75 | false => emit_skip_serializing_attribute(), 76 | true => quote!(), 77 | }; 78 | 79 | quote!( 80 | #attribute 81 | pub #name: SchemaField 82 | ) 83 | .into() 84 | } 85 | 86 | pub fn emit_initialization(&self) -> TokenStream { 87 | let name = self.name.to_ident(); 88 | let name_str: &str = self.name.as_ref(); 89 | 90 | quote!(#name: SchemaField::new(#name_str, SchemaFieldType::Property)) 91 | } 92 | 93 | pub fn emit_initialization_with_origin(&self) -> TokenStream { 94 | let name = self.name.to_ident(); 95 | let name_str: &str = self.name.as_ref(); 96 | 97 | quote!(#name: SchemaField::with_origin(#name_str, SchemaFieldType::Property, origin.clone())) 98 | } 99 | 100 | pub fn emit_foreign_field_function(&self) -> TokenStream { 101 | quote!() 102 | } 103 | } 104 | 105 | /// A foreign node, like a foreign key that points to another `Model` 106 | #[derive(Debug, Clone)] 107 | pub struct FieldForeignNode { 108 | pub name: Identifier, 109 | pub foreign_type: Identifier, 110 | 111 | pub is_public: bool, 112 | } 113 | 114 | impl FieldForeignNode { 115 | fn emit_field(&self) -> TokenStream { 116 | let name = self.name.to_ident(); 117 | let attribute = match self.is_public { 118 | false => emit_skip_serializing_attribute(), 119 | true => quote!(), 120 | }; 121 | 122 | quote!( 123 | #attribute 124 | pub #name: SchemaField 125 | ) 126 | } 127 | 128 | pub fn emit_initialization(&self) -> TokenStream { 129 | let name = self.name.to_ident(); 130 | let name_str: &str = self.name.as_ref(); 131 | 132 | quote!(#name: SchemaField::new(#name_str, SchemaFieldType::Property)) 133 | } 134 | 135 | pub fn emit_initialization_with_origin(&self) -> TokenStream { 136 | let name = self.name.to_ident(); 137 | let name_str: &str = self.name.as_ref(); 138 | 139 | quote!(#name: SchemaField::with_origin(#name_str, SchemaFieldType::Property, origin.clone())) 140 | } 141 | 142 | pub fn emit_foreign_field_function(&self) -> TokenStream { 143 | let name = self.name.to_ident(); 144 | let foreign_type = self.foreign_type.to_ident(); 145 | 146 | quote!( 147 | pub fn #name (self) -> #foreign_type <{ N + 2 }> { 148 | let origin = self.origin.unwrap_or_else(|| OriginHolder::new([""; N])); 149 | let mut new_origin: [&'static str; N + 2] = [""; N + 2]; 150 | new_origin[..N].clone_from_slice(&origin.segments); 151 | 152 | if (N > 0 && new_origin[N - 1] != ".") { 153 | new_origin[N] = "."; 154 | } 155 | 156 | new_origin[N + 1] = self.#name.identifier; 157 | 158 | #foreign_type::with_origin(OriginHolder::new(new_origin)) 159 | } 160 | ) 161 | } 162 | } 163 | 164 | /// A named relation 165 | #[derive(Debug, Clone)] 166 | pub struct FieldRelation { 167 | pub name: Identifier, 168 | pub foreign_type: Identifier, 169 | pub alias: Identifier, 170 | pub relation_type: FieldRelationType, 171 | pub is_public: bool, 172 | } 173 | 174 | #[derive(Debug, Clone)] 175 | pub enum FieldRelationType { 176 | /// for `->` type of relations/edges 177 | OutgoingEdge, 178 | 179 | /// for `<-` type of relations/edges 180 | IncomingEdge, 181 | } 182 | 183 | impl FieldRelation { 184 | fn emit_field(&self) -> TokenStream { 185 | let alias = self.alias.to_ident(); 186 | let attribute = match self.is_public { 187 | false => emit_skip_serializing_attribute(), 188 | true => quote!(), 189 | }; 190 | 191 | quote!( 192 | #attribute 193 | pub #alias: SchemaField 194 | ) 195 | } 196 | 197 | pub fn emit_initialization(&self) -> TokenStream { 198 | let alias = self.alias.to_ident(); 199 | let name_str = format!("{}{}{}", self.name, self.edge(), self.foreign_type); 200 | let field_type = self.field_type(); 201 | 202 | quote!(#alias: SchemaField::new(#name_str, #field_type)) 203 | } 204 | 205 | pub fn emit_initialization_with_origin(&self) -> TokenStream { 206 | let alias = self.alias.to_ident(); 207 | let name_str = format!("{}{}{}", self.name, self.edge(), self.foreign_type); 208 | let field_type = self.field_type(); 209 | 210 | quote!(#alias: SchemaField::with_origin(#name_str, #field_type, origin.clone())) 211 | } 212 | 213 | pub fn emit_foreign_field_function(&self) -> TokenStream { 214 | let alias = self.alias.to_ident(); 215 | let foreign_type = self.foreign_type.to_ident(); 216 | let edge = self.edge(); 217 | 218 | quote!( 219 | pub fn #alias (self) -> #foreign_type <{N + 2}> { 220 | let origin = self.origin.unwrap_or_else(|| OriginHolder::new([""; N])); 221 | let mut new_nested_origin: [&'static str; N + 2] = [""; N + 2]; 222 | new_nested_origin[..N].clone_from_slice(&origin.segments); 223 | 224 | new_nested_origin[N] = #edge; 225 | new_nested_origin[N + 1] = self.#alias.identifier; 226 | 227 | #foreign_type::with_origin(OriginHolder::new(new_nested_origin)) 228 | } 229 | ) 230 | } 231 | 232 | fn edge(&self) -> &'static str { 233 | match &self.relation_type { 234 | FieldRelationType::OutgoingEdge => "->", 235 | FieldRelationType::IncomingEdge => "<-", 236 | } 237 | } 238 | 239 | fn field_type(&self) -> TokenStream { 240 | match &self.relation_type { 241 | FieldRelationType::OutgoingEdge => quote!(SchemaFieldType::Relation), 242 | FieldRelationType::IncomingEdge => quote!(SchemaFieldType::ForeignRelation), 243 | } 244 | } 245 | } 246 | 247 | fn emit_skip_serializing_attribute() -> TokenStream { 248 | quote!(#[serde(skip_serializing)]) 249 | } 250 | -------------------------------------------------------------------------------- /model-proc-macro/src/ast/identifier.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone)] 2 | pub struct Identifier { 3 | pub value: String, 4 | pub is_raw_literal: bool, 5 | } 6 | 7 | impl Identifier { 8 | pub fn to_ident(&self) -> impl quote::ToTokens + quote::IdentFragment { 9 | match self.is_raw_literal { 10 | true => quote::format_ident!("r#{}", self.value), 11 | false => quote::format_ident!("{}", self.value), 12 | } 13 | } 14 | } 15 | 16 | impl AsRef for Identifier { 17 | fn as_ref(&self) -> &str { 18 | &self.value 19 | } 20 | } 21 | 22 | impl std::fmt::Display for Identifier { 23 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 24 | write!(f, "{}", self.value) 25 | } 26 | } 27 | 28 | impl PartialEq for Identifier { 29 | fn eq(&self, other: &str) -> bool { 30 | self.value == other 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /model-proc-macro/src/ast/model.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::fmt::Display; 3 | 4 | use quote::__private::TokenStream; 5 | use quote::format_ident; 6 | use quote::quote; 7 | 8 | use super::Field; 9 | use super::Identifier; 10 | use super::ModelOptions; 11 | 12 | #[derive(Debug)] 13 | pub struct Model { 14 | pub name: Identifier, 15 | pub fields: Vec, 16 | pub alias: Option, 17 | pub options: ModelOptions, 18 | } 19 | 20 | impl Display for Model { 21 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 22 | let name = self.name.to_ident(); 23 | 24 | let field_declarations: Vec = 25 | self.fields.iter().map(|field| field.emit_field()).collect(); 26 | 27 | let struct_declaration = quote! { 28 | #[derive(serde::Serialize)] 29 | pub struct #name { 30 | #[serde(skip_serializing)] 31 | origin: Option>, 32 | #(#field_declarations),* 33 | } 34 | }; 35 | 36 | let partial_declaration = match self.options.partial { 37 | false => quote! {}, 38 | true => { 39 | let partial_name = format_ident!("Partial{}", self.name.as_ref()); 40 | let partial_declaration = quote! { 41 | #[derive(serde::Serialize, Debug)] 42 | #[serde(transparent)] 43 | pub struct #partial_name ( 44 | surreal_simple_querybuilder::serde_json::Map, 45 | #[serde(skip)] surreal_simple_querybuilder::serde_json::Result<()>, 46 | ); 47 | }; 48 | 49 | let field_setter_functions: Vec = self 50 | .fields 51 | .iter() 52 | .map(|field| field.emit_partial_setter_field_function()) 53 | .collect(); 54 | 55 | quote!( 56 | #partial_declaration 57 | 58 | impl #partial_name { 59 | pub fn new() -> Self { 60 | Self(surreal_simple_querybuilder::serde_json::Map::new(), Ok(())) 61 | } 62 | 63 | fn __insert_value_result(mut self, key: &str, value: impl Serialize) -> Self { 64 | match surreal_simple_querybuilder::types::ser_to_param_value(value) { 65 | Ok(v) => { 66 | self.0.insert(key.to_owned(), v); 67 | } 68 | Err(e) => { 69 | self.1 = self.1.and(Err(e)); 70 | } 71 | }; 72 | 73 | self 74 | } 75 | 76 | 77 | pub fn ok(self) -> std::result::Result { 78 | self.1?; 79 | 80 | surreal_simple_querybuilder::types::flatten_serialize(self.0) 81 | } 82 | 83 | #(#field_setter_functions)* 84 | } 85 | ) 86 | } 87 | }; 88 | 89 | let field_assignments: Vec = self 90 | .fields 91 | .iter() 92 | .map(|field| field.emit_initialization()) 93 | .collect(); 94 | 95 | let field_assignments_with_origin: Vec = self 96 | .fields 97 | .iter() 98 | .map(|field| field.emit_initialization_with_origin()) 99 | .collect(); 100 | 101 | let field_foreign_functions: Vec = self 102 | .fields 103 | .iter() 104 | .map(|field| field.emit_foreign_field_function()) 105 | .collect(); 106 | 107 | let implementations = quote! { 108 | impl #name { 109 | const label: &'static str = stringify!(#name); 110 | pub const fn new() -> Self { 111 | Self { 112 | origin: None, 113 | #(#field_assignments),* 114 | } 115 | } 116 | 117 | pub fn with_origin(origin: OriginHolder) -> Self { 118 | let origin = Some(origin); 119 | 120 | Self { 121 | #(#field_assignments_with_origin),* 122 | ,origin, 123 | } 124 | } 125 | 126 | #(#field_foreign_functions)* 127 | } 128 | 129 | impl std::fmt::Display for #name { 130 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 131 | write!(f, "{}", Self::label) 132 | } 133 | } 134 | 135 | impl Into> for #name { 136 | fn into(self) -> std::borrow::Cow<'static, str> { 137 | std::borrow::Cow::from(Self::label) 138 | } 139 | } 140 | 141 | impl std::ops::Deref for #name { 142 | type Target = str; 143 | 144 | fn deref(&self) -> &Self::Target { 145 | Self::label 146 | } 147 | } 148 | 149 | impl AsRef for #name { 150 | fn as_ref(&self) -> &str { 151 | Self::label 152 | } 153 | } 154 | 155 | impl ToNodeBuilder for #name {} 156 | }; 157 | 158 | let module_name = match &self.alias { 159 | Some(alias) => format_ident!("{alias}"), 160 | None => format_ident!("schema"), 161 | }; 162 | 163 | let output = quote! { 164 | pub mod #module_name { 165 | use super::*; 166 | use surreal_simple_querybuilder::prelude::*; 167 | 168 | #struct_declaration 169 | #implementations 170 | 171 | #partial_declaration 172 | 173 | pub const model: #name<0> = #name::new(); 174 | } 175 | }; 176 | 177 | write!(f, "{output}") 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /model-proc-macro/src/ast/model_options.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Default)] 2 | pub struct ModelOptions { 3 | pub partial: bool, 4 | } 5 | 6 | impl From> for ModelOptions { 7 | fn from(flags: Vec) -> Self { 8 | Self { 9 | partial: flags.iter().any(|s| s == "partial"), 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /model-proc-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use proc_macro::TokenStream; 4 | 5 | mod ast; 6 | mod parser; 7 | 8 | /// The `model` macro allows you to quickly create structs (aka models) with fields 9 | /// that match the nodes of your database. 10 | /// 11 | /// ```rust 12 | /// use surreal_simple_querybuilder::prelude::*; 13 | 14 | /// struct Account { 15 | /// id: Option, 16 | /// handle: String, 17 | /// password: String, 18 | /// email: String, 19 | /// friends: Foreign> 20 | /// } 21 | /// 22 | /// model!(Account { 23 | /// id, 24 | /// handle, 25 | /// password, 26 | /// friends> 27 | /// }); 28 | /// 29 | /// fn main() { 30 | /// // the schema module is created by the macro 31 | /// use schema::model as account; 32 | /// 33 | /// let query = format!("select {} from {account}", account.handle); 34 | /// assert_eq!("select handle from Account", query); 35 | /// } 36 | /// ``` 37 | /// 38 | /// This allows you to have compile time checked constants for your fields, allowing 39 | /// you to reference them while building your queries without fearing of making a typo 40 | /// or using a field you renamed long time ago. 41 | /// 42 | /// If you wish to include relations (aka edges) in your models, the `model` macro 43 | /// has a special syntax for them: 44 | /// 45 | /// ```rust 46 | /// mod account { 47 | /// use surreal_simple_querybuilder::prelude::*; 48 | /// use super::project::schema::Project; 49 | /// 50 | /// model!(Account { 51 | /// id, 52 | /// 53 | /// ->manage->Project as managed_projects 54 | /// }); 55 | /// } 56 | /// 57 | /// mod project { 58 | /// use surreal_simple_querybuilder::prelude::*; 59 | /// use super::project::schema::Project; 60 | /// 61 | /// model!(Project { 62 | /// id, 63 | /// pub name, 64 | /// 65 | /// <-manage<-Account as authors 66 | /// }); 67 | /// } 68 | /// 69 | /// fn main() { 70 | /// use account::schema::model as account; 71 | /// 72 | /// let query = format!("select {} from {account}", account.managed_projects); 73 | /// assert_eq!("select ->manage->Project from Account"); 74 | /// 75 | /// let query = format!("select {} from {account}", account.managed_projects().name.as_alias("project_names")) 76 | /// assert_eq!("select ->manage->Project.name as project_names from Account", query); 77 | /// } 78 | /// ``` 79 | /// 80 | /// ## public & private fields 81 | /// 82 | /// The QueryBuilder type offers a series of methods to quickly list the fields of your 83 | /// models in SET or UPDATE statements so you don't have to write the fields and the 84 | /// variable names one by one. Since you may not want to serialize some of the fields 85 | /// like the `id` for example the model macro has the `pub` keyword to mark a field 86 | /// as serializable. Any field without the `pub` keyword in front of it will not 87 | /// be serialized by these methods. 88 | /// 89 | /// ```rs 90 | /// model!(Project { 91 | /// id, // <- won't be serialized 92 | /// pub name, // <- will be serialized 93 | /// }) 94 | /// 95 | /// fn example() { 96 | /// use schema::model as project; 97 | /// 98 | /// let query = QueryBuilder::new() 99 | /// .set_model(project) 100 | /// .build(); 101 | /// 102 | /// assert_eq!(query, "SET name = $name"); 103 | /// } 104 | /// ``` 105 | /// 106 | /// ## Expected output 107 | /// 108 | /// The macro automatically creates a module named `schema` with two main elements 109 | /// inside: 110 | /// - a struct named the same way as your model 111 | /// - a `model` constant that is an instance of the struct above so you can quickly 112 | /// use it without having to call `Account::new()` everytime. 113 | /// 114 | /// Here is a trimmed down version of what to expect, keep in mind this is an example 115 | /// and not exactly what you will find: 116 | /// ```rs 117 | /// mod schema { 118 | /// #[derive(Serialize)] 119 | /// struct Account { 120 | /// #[serde(skip_serializing)] 121 | /// id: &'static str 122 | /// name: &'static str, 123 | /// // ... 124 | /// } 125 | /// 126 | /// pub const model: Account = Account::new(); 127 | /// } 128 | /// ``` 129 | #[proc_macro] 130 | pub fn model(input: TokenStream) -> TokenStream { 131 | let content = input.to_string(); 132 | let model = parser::ModelParser::new().parse(&content).unwrap(); 133 | 134 | let output = model.to_string(); 135 | TokenStream::from_str(&output).unwrap() 136 | } 137 | -------------------------------------------------------------------------------- /model-proc-macro/src/parser.lalrpop: -------------------------------------------------------------------------------- 1 | 2 | use crate::ast::*; 3 | 4 | grammar(); 5 | 6 | pub Model: Model = { 7 | )?> "{" "}" => 8 | Model { name, fields, alias, options: options.unwrap_or_default() } 9 | } 10 | 11 | ModelOptions: ModelOptions = { 12 | KeywordWith "(" > ")" => ModelOptions::from(flags) 13 | } 14 | 15 | // ----------------------------------------------------------------------------- 16 | 17 | CommaSeparatedFields = TrailingComma; 18 | 19 | Field: Field = { 20 | FieldProperty => Field::Property(<>), 21 | FieldForeignNode => Field::ForeignNode(<>), 22 | FieldRelation => Field::Relation(<>), 23 | FieldForeignRelation => Field::Relation(<>) 24 | } 25 | 26 | FieldProperty: FieldProperty = { 27 | => FieldProperty { name, is_public } 28 | } 29 | 30 | FieldForeignNode: FieldForeignNode = { 31 | "<" ">" => FieldForeignNode { name, foreign_type, is_public } 32 | } 33 | 34 | FieldRelation: FieldRelation = { 35 | KeywordOutgoingEdge KeywordOutgoingEdge KeywordAs => 36 | FieldRelation { name, foreign_type, alias, relation_type: FieldRelationType::OutgoingEdge, is_public } 37 | } 38 | 39 | FieldForeignRelation: FieldRelation = { 40 | KeywordIncomingEdge KeywordIncomingEdge KeywordAs => 41 | FieldRelation { name, foreign_type, alias, relation_type: FieldRelationType::IncomingEdge, is_public } 42 | } 43 | 44 | FieldEncapsulation: bool = { 45 | => is_public.is_some() 46 | } 47 | 48 | // ----------------------------------------------------------------------------- 49 | 50 | Identifier: Identifier = { 51 | => 52 | Identifier { value: String::from(value), is_raw_literal: some_raw_literal.is_some() } 53 | } 54 | 55 | // ----------------------------------------------------------------------------- 56 | 57 | /// A comma separated list of `T` with the possibility of a trailing comma 58 | TrailingComma: Vec = { 59 | ",")*> => match e { 60 | None => v, 61 | Some(e) => { 62 | v.push(e); 63 | v 64 | } 65 | } 66 | }; 67 | 68 | // `match`: Declares the precedence of regular expressions 69 | // relative to one another when synthesizing 70 | // the lexer 71 | match { 72 | // Ignore C++-style comments 73 | r"//[^\n\r]*[\n\r]*" => {}, 74 | r"/\*[^\*]*[^/]*(\*/)[\n\r]*" => {}, 75 | 76 | // These items have highest precedence. 77 | r"[0-9]+", 78 | "as" => KeywordAs, 79 | "->" => KeywordOutgoingEdge, 80 | "<-" => KeywordIncomingEdge, 81 | "r#" => KeywordRawLiteral, 82 | "pub" => KeywordPub, 83 | "with" => KeywordWith, 84 | } else { 85 | // These items have next highest precedence. 86 | 87 | // Given an input like `123`, the number regex above 88 | // will match; but otherwise, given something like 89 | // `123foo` or `foo123`, this will match. 90 | // 91 | // Here, we also renamed the regex to the name `ID`, which we can 92 | // use in the grammar itself. 93 | r"\w+" => IdentifierRegex, 94 | 95 | // Ignore whitespace 96 | // You probably want this to have low precedence 97 | r"\s*" => {}, 98 | 99 | // This `_` means "add in all the other strings and 100 | // regular expressions in the grammer here" (e.g., 101 | // `"("`). 102 | _ 103 | } // you can have more `else` sections if you like -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = false 2 | tab_spaces = 2 3 | fn_params_layout = "Compressed" -------------------------------------------------------------------------------- /src/foreign_key/foreign_key.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::ops::Deref; 3 | use std::ops::DerefMut; 4 | 5 | use once_cell::sync::OnceCell; 6 | use serde::Deserialize; 7 | use serde::Serialize; 8 | 9 | use super::IntoKey; 10 | use super::IntoKeyError; 11 | use super::KeySerializeControl; 12 | use super::LoadedValue; 13 | 14 | /// Represents foreign data, from a foreign node that may need to be fetched 15 | /// during the query or else it won't be loaded or it will simply be the ID to a 16 | /// foreign node. 17 | /// 18 | /// A [ForeignKey] field may be in one of the following forms: 19 | /// - Loaded data, 20 | /// - An ID, 21 | /// - None of the above (`null`) 22 | /// 23 | /// When a field is set as a `ForeignKey` or a `Foreign`, the field will 24 | /// always be serialized into an ID so you can be sure you won't get raw data 25 | /// inserted into your nodes by mistake. 26 | /// 27 | /// Pairs well with objects that store IDs in the surreal DB, that you can also 28 | /// load using the `FETCH` keyword of SurrealQL. 29 | /// 30 | /// Imagining the following structure: 31 | /// ```sql 32 | /// create User:John set name = "John"; 33 | /// create File set name = "John file", author = User:John; 34 | /// ``` 35 | /// 36 | /// which could be represented like so in Rust: 37 | /// ```rs 38 | /// struct User { 39 | /// name: String 40 | /// } 41 | /// 42 | /// struct File { 43 | /// name: String, 44 | /// author: ForeignKey 45 | /// } 46 | /// ``` 47 | /// 48 | /// This will cause the serde_json library to attempt to parse the `File::author` 49 | /// as a `User`, and if it fails will then attempt to parse it as a `String` type 50 | /// (a string in our case since this is how SurrealDB stores IDs). And if the 51 | /// attempt to parse the ID fails as well it will default to the `Unloaded` variant 52 | /// of a ForeignKey 53 | /// 54 | /// You are then free to use the ForeignKey's methods to safely access the foreign 55 | /// data 56 | /// ```rs 57 | /// let file: File; // = query("SELECT * from File FETCH author"); 58 | /// 59 | /// if let Some(user) = file.author.value() { 60 | /// // the file had an author and it was loaded 61 | /// dbg!(&user); 62 | /// } 63 | /// 64 | /// if let Some(user_id) = file.author.key() { 65 | /// // the file had an author ID, but it wasn't loaded 66 | /// dbg!(&user_id); 67 | /// } 68 | /// ``` 69 | /// 70 | /// # ForeignKeys and serialize 71 | /// By default a ForeignKey does not serialize its value if it is in the Loaded 72 | /// state. The value would be transformed into a key using the [IntoKey] 73 | /// trait methods before serializing it. 74 | /// 75 | /// There are cases where this behaviour is not what you wish to happen, calling 76 | /// [`ForeignKey::allow_value_serialize()`] flags the ForeignKey to serialize any 77 | /// potential value it may hold. 78 | /// 79 | /// **Note** that if you plan to use `ForeignKey` (where the second generic 80 | /// type is a string), you can use the `Foreign` type in the same module to 81 | /// shorten the declaration. 82 | #[derive(Deserialize)] 83 | #[serde(from = "LoadedValue")] 84 | pub struct ForeignKey { 85 | inner: LoadedValue, 86 | 87 | #[serde(skip)] 88 | allow_value_serialize: OnceCell, 89 | } 90 | 91 | impl Default for ForeignKey { 92 | fn default() -> Self { 93 | Self { 94 | inner: Default::default(), 95 | allow_value_serialize: OnceCell::new(), 96 | } 97 | } 98 | } 99 | 100 | impl Deref for ForeignKey { 101 | type Target = LoadedValue; 102 | 103 | fn deref(&self) -> &Self::Target { 104 | &self.inner 105 | } 106 | } 107 | 108 | impl DerefMut for ForeignKey { 109 | fn deref_mut(&mut self) -> &mut Self::Target { 110 | &mut self.inner 111 | } 112 | } 113 | 114 | impl Clone for ForeignKey { 115 | fn clone(&self) -> Self { 116 | Self { 117 | inner: self.inner.clone(), 118 | allow_value_serialize: self.allow_value_serialize.clone(), 119 | } 120 | } 121 | } 122 | 123 | impl ForeignKey { 124 | /// Construct a new `ForeignKey` that is in the `Loaded` state holding the 125 | /// supplied value. 126 | /// 127 | /// [`ForeignKey`] implements [`From`] so `ForeignKey::new_value(V)` 128 | /// can be replaced with `ForeignKey::from(V)` or `V.into()` 129 | /// 130 | /// ``` 131 | /// use surreal_simple_querybuilder::foreign_key::ForeignKey; 132 | /// 133 | /// let a: ForeignKey = ForeignKey::new_value("Hello".to_owned()); 134 | /// let b: ForeignKey = "Hello".to_owned().into(); 135 | /// 136 | /// assert_eq!(a, b); 137 | /// assert!(a.is_loaded()); 138 | /// ``` 139 | pub fn new_value(value: V) -> Self { 140 | Self { 141 | inner: LoadedValue::Loaded(value), 142 | ..Default::default() 143 | } 144 | } 145 | 146 | pub fn new_key(key: K) -> Self { 147 | Self { 148 | inner: LoadedValue::Key(key), 149 | ..Default::default() 150 | } 151 | } 152 | 153 | pub fn new() -> Self { 154 | Self { 155 | inner: LoadedValue::Unloaded, 156 | ..Default::default() 157 | } 158 | } 159 | 160 | pub fn into_inner(self) -> LoadedValue { 161 | self.inner 162 | } 163 | 164 | /// Take the owned value from this `ForeignKey`, leaving an `Unloaded` value 165 | /// in its place. 166 | /// 167 | /// - If the foreign key is in the `Loaded(v)` state then `Some(v)` is returned 168 | /// and the foreign key is put into the `Unloaded` state. 169 | /// - If the foreign key is in any other state then it is untouched and `None` 170 | /// is returned instead 171 | pub fn take_value(&mut self) -> Option { 172 | if !self.inner.is_loaded() { 173 | return None; 174 | } 175 | 176 | std::mem::replace(&mut self.inner, LoadedValue::Unloaded).into_value() 177 | } 178 | 179 | /// Take the owned key from this `ForeignKey`, leaving an `Unloaded` value 180 | /// in its place. 181 | /// 182 | /// - If the foreign key is in the `Key(v)` state then `Some(v)` is returned 183 | /// and the foreign key is put into the `Unloaded` state. 184 | /// - If the foreign key is in any other state then it is untouched and `None` 185 | /// is returned instead 186 | pub fn take_key(&mut self) -> Option { 187 | if !self.is_key() { 188 | return None; 189 | } 190 | 191 | std::mem::replace(&mut self.inner, LoadedValue::Unloaded).into_key() 192 | } 193 | 194 | /// Map the current value of type `V` (if it is loaded) into a new value using 195 | /// the provided [`mapper`] function. The resulting value of the mapper 196 | /// function can be of any type and not necessarily `V`. 197 | /// 198 | /// If the foreign key is not in the loaded state then the mapper function 199 | /// will not run. 200 | /// 201 | /// # Example 202 | /// ```rs 203 | /// let foreign_int = ForeignKey::new_value(5); 204 | /// let foreign_str = foreign_int.map(|n| String::from(n)); 205 | /// ``` 206 | pub fn map(self, mapper: F) -> ForeignKey 207 | where 208 | F: FnOnce(V) -> NEWV, 209 | { 210 | match self.inner { 211 | LoadedValue::Loaded(v) => ForeignKey::new_value(mapper(v)), 212 | LoadedValue::Key(k) => ForeignKey::new_key(k), 213 | LoadedValue::Unloaded => ForeignKey::new(), 214 | } 215 | } 216 | 217 | /// Short-hand for `foreignkey.map(NewValueType::from)`. Convert a 218 | /// `ForeignKey` to a `ForeignKey` as long as `NEWV` implements 219 | /// `From`. 220 | /// 221 | /// Refer to the [map](#map) function for more information. 222 | pub fn convert(self) -> ForeignKey 223 | where 224 | NEWV: From, 225 | { 226 | self.map(NEWV::from) 227 | } 228 | } 229 | 230 | impl ForeignKey 231 | where 232 | V: IntoKey, 233 | { 234 | /// Set the foreign key to hold a key if it was currently holding a value. 235 | pub fn as_key(&mut self) -> Result<(), IntoKeyError> { 236 | if let Some(value) = self.value() { 237 | self.inner.set_key(value.into_key()?); 238 | } 239 | 240 | Ok(()) 241 | } 242 | } 243 | 244 | impl KeySerializeControl for ForeignKey { 245 | fn allow_value_serialize(&self) { 246 | if let Err(_) = self.allow_value_serialize.set(true) {} 247 | } 248 | 249 | fn disallow_value_serialize(&mut self) { 250 | self.allow_value_serialize = OnceCell::new(); 251 | } 252 | } 253 | 254 | impl Serialize for ForeignKey 255 | where 256 | V: IntoKey, 257 | K: Serialize, 258 | V: Serialize, 259 | { 260 | fn serialize(&self, serializer: S) -> Result 261 | where 262 | S: serde::Serializer, 263 | { 264 | match ( 265 | &self.inner, 266 | self.allow_value_serialize.get().unwrap_or(&false), 267 | ) { 268 | (LoadedValue::Loaded(v), false) => v 269 | .into_key() 270 | .map_err(|intokeyerr| serde::ser::Error::custom(intokeyerr))? 271 | .serialize(serializer), 272 | (inner, _) => inner.serialize(serializer), 273 | } 274 | } 275 | } 276 | 277 | impl From> for ForeignKey { 278 | fn from(value: LoadedValue) -> Self { 279 | Self { 280 | inner: value, 281 | ..Default::default() 282 | } 283 | } 284 | } 285 | 286 | impl Debug for ForeignKey 287 | where 288 | V: Debug, 289 | K: Debug, 290 | { 291 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 292 | self.inner.fmt(f) 293 | } 294 | } 295 | 296 | /// Custom implementation of PartialEq as the allow_value_serialize flag should 297 | /// NOT be used during the comparison 298 | impl PartialEq for ForeignKey 299 | where 300 | V: PartialEq, 301 | K: PartialEq, 302 | { 303 | fn eq(&self, other: &Self) -> bool { 304 | self.inner == other.inner 305 | } 306 | } 307 | 308 | impl Eq for ForeignKey 309 | where 310 | V: Eq, 311 | K: Eq, 312 | { 313 | } 314 | 315 | impl From for ForeignKey { 316 | fn from(value: V) -> Self { 317 | Self::new_value(value) 318 | } 319 | } 320 | 321 | impl ForeignKey, Vec> { 322 | /// Custom implementation of a `len` function to get the length of the inner 323 | /// vectors. If the ForeignKey is in the `Unloaded` state then 0 is returned. 324 | /// 325 | /// If you wish to know when no length is available then use the `len_loaded()` 326 | /// function 327 | pub fn len(&self) -> usize { 328 | self.len_loaded().unwrap_or_default() 329 | } 330 | 331 | /// Returns the length of the inner vectors if they are loaded, which means that 332 | /// self must be either a vector of keys or a vector of values. If self is in 333 | /// the `Unloaded` state then `None` is returned. 334 | /// 335 | /// If you wish to get a raw `usize` that defaults to `0` when the ForeignVec is 336 | /// unloaded then use the `len()` function 337 | pub fn len_loaded(&self) -> Option { 338 | match (self.key(), self.value()) { 339 | (Some(v), _) => Some(v.len()), 340 | (_, Some(v)) => Some(v.len()), 341 | _ => None, 342 | } 343 | } 344 | 345 | /// Appends the item to the back of the inner collection. 346 | /// - If `Self` is in the [LoadedValue::Unloaded] state then it is changed to 347 | /// [LoadedValue::Loaded] with the supplied `value` in it. 348 | /// - If `Self` is in the [LoadedValue::Key] state then the supplied `value` 349 | /// is pushed to the inner list of keys after it is turned into a key using 350 | /// the [IntoKey] trait. 351 | /// - If `Self` is in the [LoadedValue::Loaded] state then the supplied `value` 352 | /// is directly pushed to the list of values with no prior transformation. 353 | pub fn push(&mut self, value: V) -> Result<(), IntoKeyError> 354 | where 355 | V: IntoKey, 356 | { 357 | if self.is_unloaded() { 358 | self.inner = LoadedValue::Loaded(vec![value]); 359 | } else if let Some(ref mut v) = self.inner.key_mut() { 360 | v.push(value.into_key()?); 361 | } else if let Some(ref mut v) = self.inner.value_mut() { 362 | v.push(value); 363 | } 364 | 365 | Ok(()) 366 | } 367 | 368 | /// Removes the last element from the inner collection. 369 | /// - If `Self` is in the [LoadedValue::Unloaded] state then nothing is done 370 | /// and `Ok(None)` is returned. 371 | /// - If `Self` is in the [LoadedValue::Key] state then the last element is 372 | /// popped and returned. 373 | /// - If `Self` is in the [LoadedValue::Loaded] state then the last element 374 | /// is popped, mapped to a key and returned . 375 | pub fn pop(&mut self) -> Result, IntoKeyError> 376 | where 377 | V: IntoKey, 378 | { 379 | if self.is_unloaded() { 380 | return Ok(None); 381 | } else if let Some(ref mut v) = self.inner.key_mut() { 382 | return Ok(v.pop()); 383 | } else if let Some(ref mut v) = self.inner.value_mut() { 384 | let value = v.pop(); 385 | 386 | return match value { 387 | Some(value) => Ok(Some(value.into_key()?)), 388 | None => Ok(None), 389 | }; 390 | } 391 | 392 | Ok(None) 393 | } 394 | 395 | /// Easily convert a `ForeignVec` of values into a `ForeignVec` 396 | pub fn convert_vec(self) -> ForeignKey, Vec> 397 | where 398 | NEWV: From, 399 | { 400 | self.map(|values| values.into_iter().map(NEWV::from).collect()) 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /src/foreign_key/into_key.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Display, ops::Deref}; 2 | 3 | #[derive(Debug, Clone, Copy)] 4 | pub enum IntoKeyError { 5 | /// Denotes a IntoKey failure as `Self` had no ID to provide. Can happen on 6 | /// types whose IDs are `Option` and when it is currently a `None` 7 | MissingId, 8 | 9 | /// Denotes a IntoKey failure that happened while `Self` was serializing into 10 | /// the ID's type. 11 | TransformError, 12 | 13 | /// A custom error message 14 | Custom(&'static str), 15 | } 16 | 17 | impl Display for IntoKeyError { 18 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 19 | match self { 20 | Self::Custom(message) => write!(f, "IntoKeyError: {message}"), 21 | Self::MissingId => write!(f, "IntoKeyError: MissingId"), 22 | Self::TransformError => write!(f, "IntoKeyError: TransformError"), 23 | } 24 | } 25 | } 26 | 27 | impl std::error::Error for IntoKeyError {} 28 | 29 | /// Any type used inside a [ForeignKey] must implement this trait. It allows you 30 | /// to transform the `I` type into an ID when `I` is serialized. 31 | pub trait IntoKey { 32 | fn into_key(&self) -> Result; 33 | } 34 | 35 | impl IntoKey> for Vec 36 | where 37 | V: IntoKey, 38 | Vec: std::iter::FromIterator, 39 | { 40 | fn into_key(&self) -> Result, IntoKeyError> { 41 | self.iter().map(|c| c.into_key()).collect() 42 | } 43 | } 44 | 45 | impl, K> IntoKey for Box { 46 | fn into_key(&self) -> Result { 47 | self.deref().into_key() 48 | } 49 | } 50 | 51 | /// A blanket implementation for `Option` as long as V implements `IntoKey` 52 | /// so it is easier to implement on types that have a `id: Option` field. 53 | impl, K> IntoKey for Option { 54 | fn into_key(&self) -> Result { 55 | match self { 56 | Some(id) => id.into_key(), 57 | None => Err(IntoKeyError::MissingId), 58 | } 59 | } 60 | } 61 | 62 | impl IntoKey for &str { 63 | fn into_key(&self) -> Result { 64 | Ok(self.to_string()) 65 | } 66 | } 67 | 68 | impl IntoKey for String { 69 | fn into_key(&self) -> Result { 70 | Ok(self.to_owned()) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/foreign_key/key_ser_control.rs: -------------------------------------------------------------------------------- 1 | pub trait KeySerializeControl { 2 | /// By default a ForeignKey does not serialize its value if it is in the `Loaded` 3 | /// state. The value would be transformed into a key using the [IntoKey] trait 4 | /// methods before serializing it. 5 | /// 6 | /// There are cases where this behaviour is not what you wish to happen, calling 7 | /// [`ForeignKey::allow_value_serialize()`] flags the ForeignKey to ignore the 8 | /// default behaviour and serialize any potential value it may hold. 9 | /// 10 | /// The default state of the KeySerializeControl flag is `false` and is stored 11 | /// in a once_cell. That means the value can only be changed once to `true`, 12 | /// any new attempt at updating the value will fail as there is no way to set 13 | /// the value back to `false` without resetting the whole ForeignKey. In which 14 | /// case refer to the [disallow_value_serialize] method but note that it requires 15 | /// a mutable reference. 16 | fn allow_value_serialize(&self); 17 | 18 | /// Perform a [allow_value_serialize()](KeySerializeControl::allow_value_serialize()) and return self 19 | fn with_allowed_value_ser(self) -> Self 20 | where 21 | Self: Sized, 22 | { 23 | self.allow_value_serialize(); 24 | 25 | self 26 | } 27 | 28 | /// By default a ForeignKey does not serialize its value if it is in the `Loaded` 29 | /// state. The value would be transformed into a key using the [IntoKey] trait 30 | /// methods before serializing it. 31 | /// 32 | /// There are cases where this behaviour is not what you wish to happen, calling 33 | /// [`ForeignKey::allow_value_serialize()`] flags the ForeignKey to ignore the 34 | /// default behaviour and serialize any potential value it may hold. 35 | /// 36 | /// And this method reverts any change to the flag so that it comes back to 37 | /// the default behavior of not serializing the value. 38 | /// 39 | /// _Unlike the [allow_value_serialize] method, this one requires a mutable reference._ 40 | fn disallow_value_serialize(&mut self); 41 | 42 | /// Perform a [disallow_value_serialize()](KeySerializeControl::disallow_value_serialize()) and return self 43 | fn with_disallowed_value_ser(self) -> Self 44 | where 45 | Self: Sized, 46 | { 47 | self.allow_value_serialize(); 48 | 49 | self 50 | } 51 | } 52 | 53 | /// Blanket implementation for anything that implements KeySerializeControl and 54 | /// that is in a Vec. 55 | /// 56 | /// This implementation allows calling KeySerializeControl methods directly on 57 | /// the vector itself to mutate every single child element. 58 | impl KeySerializeControl for Vec 59 | where 60 | T: KeySerializeControl, 61 | { 62 | fn allow_value_serialize(&self) { 63 | self 64 | .iter() 65 | .for_each(KeySerializeControl::allow_value_serialize); 66 | } 67 | 68 | fn disallow_value_serialize(&mut self) { 69 | self 70 | .iter_mut() 71 | .for_each(KeySerializeControl::disallow_value_serialize); 72 | } 73 | } 74 | 75 | /// Blanket implementation for anything that implements KeySerializeControl and 76 | /// that is in an Option. 77 | /// 78 | /// This implementation allows calling KeySerializeControl methods directly on 79 | /// the option itself to mutate every single child element. 80 | impl KeySerializeControl for Option 81 | where 82 | T: KeySerializeControl, 83 | { 84 | fn allow_value_serialize(&self) { 85 | self 86 | .iter() 87 | .for_each(KeySerializeControl::allow_value_serialize); 88 | } 89 | 90 | fn disallow_value_serialize(&mut self) { 91 | self 92 | .iter_mut() 93 | .for_each(KeySerializeControl::disallow_value_serialize); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/foreign_key/loaded_value.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use serde::Deserialize; 4 | use serde::Serialize; 5 | 6 | #[derive(Deserialize, Clone, PartialEq, Eq)] 7 | #[serde(untagged)] 8 | pub enum LoadedValue { 9 | Loaded(V), 10 | Key(K), 11 | 12 | Unloaded, 13 | } 14 | 15 | impl Default for LoadedValue { 16 | fn default() -> Self { 17 | Self::Unloaded 18 | } 19 | } 20 | 21 | impl LoadedValue { 22 | /// Access the inner value by checking if it is loaded or not, thus returning 23 | /// an `Option<&T>` that is `Some` if it is loaded and `None` if it isn't. 24 | pub fn value(&self) -> Option<&V> { 25 | match self { 26 | Self::Loaded(v) => Some(v), 27 | _ => None, 28 | } 29 | } 30 | 31 | pub fn value_mut(&mut self) -> Option<&mut V> { 32 | match self { 33 | Self::Loaded(v) => Some(v), 34 | _ => None, 35 | } 36 | } 37 | 38 | /// Consumes `Self` to get the inner value. If the enum is in any other state 39 | /// than `Loaded` then a `None` is returned. 40 | /// 41 | /// Depending on how the [LoadedValue] value is obtained, for example if it is 42 | /// obtained by a Deref from a [ForeignKey](crate::foreign_key::ForeignKey) 43 | /// and if the stored types do not implement `Copy`, then calling `ForeignKey::into_inner()` 44 | /// might be needed: 45 | /// ```rs 46 | /// let foreign = Foreign::new(User::new("John")); 47 | /// let user: Option = foreign.into_inner().into_value(); 48 | /// ``` 49 | pub fn into_value(self) -> Option { 50 | match self { 51 | Self::Loaded(v) => Some(v), 52 | _ => None, 53 | } 54 | } 55 | 56 | /// Consumes `Self` to get the inner key. If the enum is in any other state 57 | /// than `Key` then a `None` is returned. 58 | /// 59 | /// Depending on how the [LoadedValue] value is obtained, for example if it is 60 | /// obtained by a Deref from a [ForeignKey](crate::foreign_key::ForeignKey) 61 | /// and if the stored types do not implement `Copy`, then calling `ForeignKey::into_inner()` 62 | /// might be needed: 63 | /// ```rs 64 | /// let foreign = Foreign::new_key(Id::from("user:john"))); 65 | /// let id: Option = foreign.into_inner().into_key(); 66 | /// ``` 67 | pub fn into_key(self) -> Option { 68 | match self { 69 | Self::Key(k) => Some(k), 70 | _ => None, 71 | } 72 | } 73 | 74 | /// Access the inner key by checking if the foreign key is currently 75 | /// holding the key, thus returning a `Some<&I>` if it is one and `None` 76 | /// if it isn't. 77 | pub fn key(&self) -> Option<&K> { 78 | match self { 79 | Self::Key(i) => Some(i), 80 | _ => None, 81 | } 82 | } 83 | 84 | pub fn key_mut(&mut self) -> Option<&mut K> { 85 | match self { 86 | Self::Key(i) => Some(i), 87 | _ => None, 88 | } 89 | } 90 | 91 | /// Attempt to construct a key from a reference to the inner value. If the 92 | /// foreign key: 93 | /// - is currently holding a key then it is cloned and returned. 94 | /// - is currently holding a value that implements [IntoKey](crate::foreign_key::IntoKey) 95 | /// then it constructs the key and returns the result 96 | /// - is unloaded, then `Ok(None)` is returned. 97 | pub fn to_key(&self) -> Result, super::IntoKeyError> 98 | where 99 | K: Clone, 100 | V: super::IntoKey, 101 | { 102 | match self { 103 | Self::Key(i) => Ok(Some(i.clone())), 104 | Self::Loaded(v) => v.into_key().map(|k| Some(k)), 105 | Self::Unloaded => Ok(None), 106 | } 107 | } 108 | 109 | /// Consumes `Self` to get the inner key. If the enum is in the `Loaded` 110 | /// variant, then the [IntoKey](crate::foreign_key::IntoKey) implementation of the value is silently called 111 | /// and any error during this process will cause a `None` to be returned. 112 | /// 113 | /// Depending on how the [LoadedValue] value is obtained, for example if it is 114 | /// obtained by a Deref from a [ForeignKey](crate::foreign_key::ForeignKey) 115 | /// and if the stored types do not implement `Copy`, then calling `ForeignKey::into_inner()` 116 | /// might be needed: 117 | /// ```rs 118 | /// let foreign = Foreign::new(User::new("John")); 119 | /// let id: Option = foreign.into_inner().unwrap_key(); 120 | /// ``` 121 | pub fn unwrap_key(self) -> Option 122 | where 123 | V: super::IntoKey, 124 | { 125 | match self { 126 | Self::Key(i) => Some(i), 127 | Self::Loaded(v) => v.into_key().ok(), 128 | _ => None, 129 | } 130 | } 131 | 132 | /// Return whether the current ForeignKey is unloaded. Returns `false` if `self` 133 | /// is either a key or a loaded value. 134 | pub fn is_unloaded(&self) -> bool { 135 | match &self { 136 | Self::Unloaded => true, 137 | _ => false, 138 | } 139 | } 140 | 141 | /// Returns `true` if `Self` is in the `Key` state, or `false` otherwise 142 | pub fn is_key(&self) -> bool { 143 | match &self { 144 | Self::Key(_) => true, 145 | _ => false, 146 | } 147 | } 148 | 149 | /// Returns `true` if `Self` is in the `Loaded` state, or `false` otherwise 150 | pub fn is_loaded(&self) -> bool { 151 | match &self { 152 | Self::Loaded(_) => true, 153 | _ => false, 154 | } 155 | } 156 | 157 | /// Drop any data `self` may currently hold and set it to the `Loaded` variant 158 | /// with the given value. 159 | pub fn set_value(&mut self, value: V) { 160 | *self = Self::Loaded(value); 161 | } 162 | 163 | /// Drop any data `self` may currently hold and set it to the `Key` variant 164 | /// with the given identifier. 165 | pub fn set_key(&mut self, identifier: K) { 166 | *self = Self::Key(identifier); 167 | } 168 | 169 | /// Drop the currently held value and set `self` to the `Unloaded` variant. 170 | pub fn unload(&mut self) { 171 | *self = Self::Unloaded; 172 | } 173 | } 174 | 175 | impl Serialize for LoadedValue 176 | where 177 | K: Serialize, 178 | V: Serialize, 179 | { 180 | fn serialize(&self, serializer: S) -> Result 181 | where 182 | S: serde::Serializer, 183 | { 184 | match &self { 185 | Self::Loaded(v) => v.serialize(serializer), 186 | Self::Key(i) => i.serialize(serializer), 187 | Self::Unloaded => Option::::None.serialize(serializer), 188 | } 189 | } 190 | } 191 | 192 | impl Debug for LoadedValue 193 | where 194 | V: Debug, 195 | K: Debug, 196 | { 197 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 198 | match self { 199 | Self::Loaded(arg0) => f.debug_tuple("Loaded").field(arg0).finish(), 200 | Self::Key(arg0) => f.debug_tuple("Key").field(arg0).finish(), 201 | Self::Unloaded => write!(f, "Unloaded"), 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/foreign_key/mod.rs: -------------------------------------------------------------------------------- 1 | mod foreign_key; 2 | mod into_key; 3 | mod key_ser_control; 4 | mod loaded_value; 5 | 6 | use loaded_value::*; 7 | 8 | pub use foreign_key::*; 9 | pub use into_key::*; 10 | pub use key_ser_control::*; 11 | 12 | /// A `ForeignKey` whose `Key` type is set to a `String` by default. 13 | pub type Foreign = ForeignKey; 14 | 15 | /// A `ForeignKey` whose `Key` type is set to a `Vec` by default, and whose 16 | /// `Value` type is set to be a `Vec` 17 | pub type ForeignVec = ForeignKey, Vec>; 18 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(incomplete_features)] 2 | #![feature(generic_const_exprs)] 3 | 4 | /// A module for the various types used & generated by the [`model!()`] proc-macro. 5 | #[cfg(feature = "model")] 6 | pub mod model; 7 | 8 | #[cfg(feature = "model")] 9 | pub use surreal_simple_querybuilder_proc_macro::model; 10 | 11 | /// Contains a trait for simplifying the building of relationships between nodes 12 | #[cfg(feature = "querybuilder")] 13 | pub mod node_builder; 14 | 15 | /// Contains the query builder for simplifying the building of Surreal QL queries. 16 | /// Particularely useful when composing variables and conditional queries 17 | #[cfg(feature = "querybuilder")] 18 | pub mod querybuilder; 19 | 20 | /// Contains the `Foreign` type used to represent fields that may or may not be 21 | /// loaded. 22 | #[cfg(feature = "foreign")] 23 | pub mod foreign_key; 24 | 25 | #[cfg(feature = "queries")] 26 | pub mod types; 27 | 28 | #[cfg(feature = "queries")] 29 | pub mod queries; 30 | 31 | pub mod prelude; 32 | 33 | pub use serde_json; 34 | -------------------------------------------------------------------------------- /src/model/mod.rs: -------------------------------------------------------------------------------- 1 | mod origin_holder; 2 | mod schema_field; 3 | mod serialize_error; 4 | mod serializer; 5 | 6 | pub use origin_holder::OriginHolder; 7 | pub use schema_field::SchemaField; 8 | pub use schema_field::SchemaFieldType; 9 | pub use serialize_error::*; 10 | pub use serializer::*; 11 | -------------------------------------------------------------------------------- /src/model/origin_holder.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | /// 4 | #[derive(Clone, Copy)] 5 | pub struct OriginHolder { 6 | pub segments: [&'static str; N], 7 | } 8 | 9 | impl Display for OriginHolder { 10 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 11 | for segment in self.segments { 12 | write!(f, "{segment}")?; 13 | } 14 | 15 | Ok(()) 16 | } 17 | } 18 | 19 | impl OriginHolder { 20 | pub const fn new(segments: [&'static str; N]) -> Self { 21 | Self { segments } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/model/relation_node.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::ops::Deref; 3 | 4 | use super::SchemaFieldType; 5 | 6 | pub struct RelationNode { 7 | relation_name: &'static str, 8 | relation_type: SchemaFieldType, 9 | 10 | /// used when the node type is only needed for its Display impl, it is used by 11 | /// the Display implementation. 12 | node: T, 13 | 14 | /// used when the node type is needed for its properties, for deeper nesting, 15 | /// it is used by the deref implementation. 16 | /// 17 | /// This is used because relations are either: `->relation->Node` or 18 | /// `->relation->Node.nested_property`. In the first case the `Node` would be 19 | /// print out by `T::Display` impl whereas the second case needs to be added 20 | /// to the origin holder as the `Y::Display` impl is never called since there 21 | /// is direct access to the nested properties. 22 | nested_node: Y, 23 | } 24 | 25 | impl RelationNode { 26 | pub fn new( 27 | relation_name: &'static str, relation_type: SchemaFieldType, node: T, nested_node: Y, 28 | ) -> Self { 29 | Self { 30 | relation_name, 31 | relation_type, 32 | node, 33 | nested_node, 34 | } 35 | } 36 | } 37 | 38 | impl Display for RelationNode 39 | where 40 | T: Display, 41 | { 42 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 43 | let joining_segment = match self.relation_type { 44 | SchemaFieldType::ForeignRelation => "<-", 45 | _ => "->", 46 | }; 47 | 48 | write!(f, "{}", self.node) 49 | 50 | // write!( 51 | // f, 52 | // "{joining_segment}{}{joining_segment}{}", 53 | // self.relation_name, self.node 54 | // ) 55 | } 56 | } 57 | 58 | impl Deref for RelationNode { 59 | type Target = Y; 60 | 61 | fn deref(&self) -> &Self::Target { 62 | &self.nested_node 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/model/schema_field.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::fmt::Display; 3 | 4 | use serde::Serialize; 5 | 6 | use crate::model::OriginHolder; 7 | use crate::node_builder::ToNodeBuilder; 8 | 9 | #[derive(Debug, Clone, Copy)] 10 | pub enum SchemaFieldType { 11 | Property, 12 | Relation, 13 | ForeignRelation, 14 | } 15 | 16 | #[derive(Clone, Copy)] 17 | pub struct SchemaField { 18 | pub identifier: &'static str, 19 | field_type: SchemaFieldType, 20 | origin_holder: Option>, 21 | } 22 | 23 | impl SchemaField { 24 | pub const fn new(identifier: &'static str, field_type: SchemaFieldType) -> Self { 25 | Self { 26 | identifier, 27 | field_type, 28 | origin_holder: None, 29 | } 30 | } 31 | 32 | pub const fn with_origin( 33 | identifier: &'static str, field_type: SchemaFieldType, origin: Option>, 34 | ) -> Self { 35 | Self { 36 | identifier, 37 | field_type, 38 | origin_holder: origin, 39 | } 40 | } 41 | 42 | pub fn from_alias(self, alias: &'static str) -> SchemaField<{ N + 1 }> { 43 | let origin = match self.origin_holder { 44 | Some(h) => h, 45 | None => OriginHolder::new([""; N]), 46 | }; 47 | 48 | let mut new_origin: [&'static str; N + 1] = [""; N + 1]; 49 | new_origin[1..].clone_from_slice(&origin.segments); 50 | new_origin[0] = alias; 51 | 52 | SchemaField::<{ N + 1 }>::with_origin( 53 | self.identifier, 54 | self.field_type, 55 | Some(OriginHolder::new(new_origin)), 56 | ) 57 | } 58 | 59 | /// Return the name of the field, and if the field is an edge then return the 60 | /// name of the edge instead. 61 | /// 62 | /// # Example 63 | /// ``` 64 | /// #![allow(incomplete_features)] 65 | /// #![feature(generic_const_exprs)] 66 | /// use surreal_simple_querybuilder::prelude::*; 67 | /// 68 | /// model!(Test { 69 | /// normal_field, 70 | /// ->edge->Test as test_edge 71 | /// }); 72 | /// 73 | /// assert_eq!("normal_field", schema::model.normal_field.name()); 74 | /// assert_eq!("edge", schema::model.test_edge.name()); 75 | /// ``` 76 | pub fn name(&self) -> &'static str { 77 | match self.field_type { 78 | SchemaFieldType::Property => self.identifier, 79 | _ => { 80 | let edge_name_index = self 81 | .identifier 82 | .chars() 83 | .take_while(|c| c.is_alphanumeric()) 84 | .count(); 85 | 86 | &self.identifier[..edge_name_index] 87 | } 88 | } 89 | } 90 | } 91 | 92 | impl Display for SchemaField { 93 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 94 | match &self.origin_holder { 95 | Some(holder) => { 96 | write!(f, "{holder}")?; 97 | 98 | match &self.field_type { 99 | SchemaFieldType::Property => write!(f, ".")?, 100 | SchemaFieldType::Relation => write!(f, "->")?, 101 | SchemaFieldType::ForeignRelation => write!(f, "<-")?, 102 | }; 103 | 104 | write!(f, "{}", self.identifier) 105 | } 106 | None => { 107 | // prefix depending on the field type 108 | match &self.field_type { 109 | SchemaFieldType::Property => {} 110 | SchemaFieldType::Relation => write!(f, "->")?, 111 | SchemaFieldType::ForeignRelation => write!(f, "<-")?, 112 | }; 113 | 114 | write!(f, "{}", self.identifier) 115 | } 116 | } 117 | } 118 | } 119 | 120 | impl ToNodeBuilder for SchemaField { 121 | fn equals_parameterized(&self) -> String { 122 | // special case for the schema field as it may include dots, we replace them 123 | // by underscores. 124 | let key = self 125 | .to_string() 126 | .replace(".", "_") 127 | .replace("->", "_") 128 | .replace("<-", "_"); 129 | 130 | format!("{key} = ${key}") 131 | } 132 | } 133 | 134 | impl Into> for SchemaField { 135 | fn into(self) -> Cow<'static, str> { 136 | Cow::from(self.identifier) 137 | } 138 | } 139 | 140 | impl Into for SchemaField { 141 | fn into(self) -> String { 142 | self.to_string() 143 | } 144 | } 145 | 146 | impl Serialize for SchemaField { 147 | fn serialize(&self, serializer: S) -> Result 148 | where 149 | S: serde::Serializer, 150 | { 151 | serializer.serialize_str(&self.to_string()) 152 | } 153 | } 154 | 155 | impl std::ops::Deref for SchemaField { 156 | type Target = str; 157 | 158 | fn deref(&self) -> &Self::Target { 159 | self.identifier 160 | } 161 | } 162 | 163 | impl AsRef for SchemaField 164 | where 165 | T: ?Sized, 166 | as std::ops::Deref>::Target: AsRef, 167 | { 168 | fn as_ref(&self) -> &T { 169 | std::ops::Deref::deref(self).as_ref() 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/model/serialize_error.rs: -------------------------------------------------------------------------------- 1 | use std; 2 | use std::fmt::{self, Display}; 3 | 4 | use serde::{de, ser}; 5 | 6 | pub type SqlSerializeResult = std::result::Result; 7 | 8 | // This is a bare-bones implementation. A real library would provide additional 9 | // information in its error type, for example the line and column at which the 10 | // error occurred, the byte offset into the input, or the current key being 11 | // processed. 12 | #[derive(Debug)] 13 | pub enum SqlSerializeError { 14 | // One or more variants that can be created by data structures through the 15 | // `ser::Error` and `de::Error` traits. For example the Serialize impl for 16 | // Mutex might return an error because the mutex is poisoned, or the 17 | // Deserialize impl for a struct may return an error because a required 18 | // field is missing. 19 | Message(String), 20 | } 21 | 22 | impl ser::Error for SqlSerializeError { 23 | fn custom(msg: T) -> Self { 24 | SqlSerializeError::Message(msg.to_string()) 25 | } 26 | } 27 | 28 | impl de::Error for SqlSerializeError { 29 | fn custom(msg: T) -> Self { 30 | SqlSerializeError::Message(msg.to_string()) 31 | } 32 | } 33 | 34 | impl Display for SqlSerializeError { 35 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 36 | match self { 37 | SqlSerializeError::Message(msg) => formatter.write_str(msg), 38 | } 39 | } 40 | } 41 | 42 | impl std::error::Error for SqlSerializeError {} 43 | -------------------------------------------------------------------------------- /src/model/serializer.rs: -------------------------------------------------------------------------------- 1 | use crate::model::SqlSerializeError; 2 | use crate::model::SqlSerializeResult; 3 | use serde::{ser, Serialize}; 4 | 5 | /// Serialize a struct into a series of `field = $field` pairs for every field 6 | /// the struct has, using serde attributes. 7 | pub fn to_parameters(value: &T) -> SqlSerializeResult 8 | where 9 | T: Serialize, 10 | { 11 | let mut serializer = SqlFieldSerializer { 12 | output: String::new(), 13 | }; 14 | value.serialize(&mut serializer)?; 15 | Ok(serializer.output) 16 | } 17 | 18 | /// A special kind of serializer, its goal is to serialize structs and perhaps 19 | /// maps to a series of `field = $field` pairs for every key in the supplied object. 20 | /// 21 | /// Anything else will not be serialized, and values are ignored completely. 22 | pub struct SqlFieldSerializer { 23 | output: String, 24 | } 25 | 26 | impl<'a> ser::Serializer for &'a mut SqlFieldSerializer { 27 | type Ok = (); 28 | 29 | type Error = SqlSerializeError; 30 | 31 | type SerializeSeq = Self; 32 | type SerializeTuple = Self; 33 | type SerializeTupleStruct = Self; 34 | type SerializeTupleVariant = Self; 35 | type SerializeMap = Self; 36 | type SerializeStruct = Self; 37 | type SerializeStructVariant = Self; 38 | 39 | fn serialize_bool(self, v: bool) -> SqlSerializeResult<()> { 40 | self.output += if v { "true" } else { "false" }; 41 | Ok(()) 42 | } 43 | 44 | fn serialize_i8(self, v: i8) -> SqlSerializeResult<()> { 45 | self.serialize_i64(i64::from(v)) 46 | } 47 | 48 | fn serialize_i16(self, v: i16) -> SqlSerializeResult<()> { 49 | self.serialize_i64(i64::from(v)) 50 | } 51 | 52 | fn serialize_i32(self, v: i32) -> SqlSerializeResult<()> { 53 | self.serialize_i64(i64::from(v)) 54 | } 55 | 56 | fn serialize_i64(self, v: i64) -> SqlSerializeResult<()> { 57 | self.output += &v.to_string(); 58 | Ok(()) 59 | } 60 | 61 | fn serialize_u8(self, v: u8) -> SqlSerializeResult<()> { 62 | self.serialize_u64(u64::from(v)) 63 | } 64 | 65 | fn serialize_u16(self, v: u16) -> SqlSerializeResult<()> { 66 | self.serialize_u64(u64::from(v)) 67 | } 68 | 69 | fn serialize_u32(self, v: u32) -> SqlSerializeResult<()> { 70 | self.serialize_u64(u64::from(v)) 71 | } 72 | 73 | fn serialize_u64(self, v: u64) -> SqlSerializeResult<()> { 74 | self.output += &v.to_string(); 75 | Ok(()) 76 | } 77 | 78 | fn serialize_f32(self, v: f32) -> SqlSerializeResult<()> { 79 | self.serialize_f64(f64::from(v)) 80 | } 81 | 82 | fn serialize_f64(self, v: f64) -> SqlSerializeResult<()> { 83 | self.output += &v.to_string(); 84 | Ok(()) 85 | } 86 | 87 | fn serialize_char(self, v: char) -> SqlSerializeResult<()> { 88 | self.serialize_str(&v.to_string()) 89 | } 90 | 91 | fn serialize_str(self, v: &str) -> SqlSerializeResult<()> { 92 | self.output += v; 93 | Ok(()) 94 | } 95 | 96 | fn serialize_bytes(self, v: &[u8]) -> SqlSerializeResult<()> { 97 | use serde::ser::SerializeSeq; 98 | let mut seq = self.serialize_seq(Some(v.len()))?; 99 | for byte in v { 100 | seq.serialize_element(byte)?; 101 | } 102 | seq.end() 103 | } 104 | 105 | fn serialize_none(self) -> SqlSerializeResult<()> { 106 | self.serialize_unit() 107 | } 108 | 109 | fn serialize_some(self, value: &T) -> SqlSerializeResult<()> 110 | where 111 | T: ?Sized + Serialize, 112 | { 113 | value.serialize(self) 114 | } 115 | 116 | fn serialize_unit(self) -> SqlSerializeResult<()> { 117 | Ok(()) 118 | } 119 | 120 | fn serialize_unit_struct(self, _name: &'static str) -> SqlSerializeResult<()> { 121 | self.serialize_unit() 122 | } 123 | 124 | fn serialize_unit_variant( 125 | self, _name: &'static str, _variant_index: u32, variant: &'static str, 126 | ) -> SqlSerializeResult<()> { 127 | self.serialize_str(variant) 128 | } 129 | 130 | fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> SqlSerializeResult<()> 131 | where 132 | T: ?Sized + Serialize, 133 | { 134 | value.serialize(self) 135 | } 136 | 137 | fn serialize_newtype_variant( 138 | self, _name: &'static str, _variant_index: u32, variant: &'static str, _value: &T, 139 | ) -> SqlSerializeResult<()> 140 | where 141 | T: ?Sized + Serialize, 142 | { 143 | variant.serialize(&mut *self)?; 144 | self.output += " = $"; 145 | variant.serialize(&mut *self)?; 146 | Ok(()) 147 | } 148 | 149 | fn serialize_seq(self, _len: Option) -> SqlSerializeResult { 150 | Ok(self) 151 | } 152 | 153 | fn serialize_tuple(self, len: usize) -> SqlSerializeResult { 154 | self.serialize_seq(Some(len)) 155 | } 156 | 157 | fn serialize_tuple_struct( 158 | self, _name: &'static str, len: usize, 159 | ) -> SqlSerializeResult { 160 | self.serialize_seq(Some(len)) 161 | } 162 | 163 | fn serialize_tuple_variant( 164 | self, _name: &'static str, _variant_index: u32, variant: &'static str, _len: usize, 165 | ) -> SqlSerializeResult { 166 | variant.serialize(&mut *self)?; 167 | Ok(self) 168 | } 169 | 170 | // Maps are represented in JSON as `{ K: V, K: V, ... }`. 171 | fn serialize_map(self, _len: Option) -> SqlSerializeResult { 172 | Ok(self) 173 | } 174 | 175 | fn serialize_struct( 176 | self, _name: &'static str, len: usize, 177 | ) -> SqlSerializeResult { 178 | self.serialize_map(Some(len)) 179 | } 180 | 181 | fn serialize_struct_variant( 182 | self, _name: &'static str, _variant_index: u32, variant: &'static str, _len: usize, 183 | ) -> SqlSerializeResult { 184 | variant.serialize(&mut *self)?; 185 | Ok(self) 186 | } 187 | } 188 | 189 | impl<'a> ser::SerializeSeq for &'a mut SqlFieldSerializer { 190 | type Ok = (); 191 | type Error = SqlSerializeError; 192 | 193 | fn serialize_element(&mut self, value: &T) -> SqlSerializeResult<()> 194 | where 195 | T: ?Sized + Serialize, 196 | { 197 | value.serialize(&mut **self) 198 | } 199 | 200 | fn end(self) -> SqlSerializeResult<()> { 201 | Ok(()) 202 | } 203 | } 204 | 205 | // Same thing but for tuples. 206 | impl<'a> ser::SerializeTuple for &'a mut SqlFieldSerializer { 207 | type Ok = (); 208 | type Error = SqlSerializeError; 209 | 210 | fn serialize_element(&mut self, value: &T) -> SqlSerializeResult<()> 211 | where 212 | T: ?Sized + Serialize, 213 | { 214 | value.serialize(&mut **self) 215 | } 216 | 217 | fn end(self) -> SqlSerializeResult<()> { 218 | Ok(()) 219 | } 220 | } 221 | 222 | // Same thing but for tuple structs. 223 | impl<'a> ser::SerializeTupleStruct for &'a mut SqlFieldSerializer { 224 | type Ok = (); 225 | type Error = SqlSerializeError; 226 | 227 | fn serialize_field(&mut self, value: &T) -> SqlSerializeResult<()> 228 | where 229 | T: ?Sized + Serialize, 230 | { 231 | value.serialize(&mut **self) 232 | } 233 | 234 | fn end(self) -> SqlSerializeResult<()> { 235 | Ok(()) 236 | } 237 | } 238 | 239 | impl<'a> ser::SerializeTupleVariant for &'a mut SqlFieldSerializer { 240 | type Ok = (); 241 | type Error = SqlSerializeError; 242 | 243 | fn serialize_field(&mut self, value: &T) -> SqlSerializeResult<()> 244 | where 245 | T: ?Sized + Serialize, 246 | { 247 | value.serialize(&mut **self) 248 | } 249 | 250 | fn end(self) -> SqlSerializeResult<()> { 251 | Ok(()) 252 | } 253 | } 254 | 255 | // Some `Serialize` types are not able to hold a key and value in memory at the 256 | // same time so `SerializeMap` implementations are required to support 257 | // `serialize_key` and `serialize_value` individually. 258 | // 259 | // There is a third optional method on the `SerializeMap` trait. The 260 | // `serialize_entry` method allows serializers to optimize for the case where 261 | // key and value are both available simultaneously. In JSON it doesn't make a 262 | // difference so the default behavior for `serialize_entry` is fine. 263 | impl<'a> ser::SerializeMap for &'a mut SqlFieldSerializer { 264 | type Ok = (); 265 | type Error = SqlSerializeError; 266 | 267 | fn serialize_key(&mut self, key: &T) -> SqlSerializeResult<()> 268 | where 269 | T: ?Sized + Serialize, 270 | { 271 | if !self.output.is_empty() { 272 | self.output += " , "; 273 | } 274 | 275 | key.serialize(&mut **self)?; 276 | self.output += " = $"; 277 | key.serialize(&mut **self) 278 | } 279 | 280 | fn serialize_value(&mut self, value: &T) -> SqlSerializeResult<()> 281 | where 282 | T: ?Sized + Serialize, 283 | { 284 | self.output += ":"; 285 | value.serialize(&mut **self) 286 | } 287 | 288 | fn end(self) -> SqlSerializeResult<()> { 289 | Ok(()) 290 | } 291 | } 292 | 293 | // Structs are like maps in which the keys are constrained to be compile-time 294 | // constant strings. 295 | impl<'a> ser::SerializeStruct for &'a mut SqlFieldSerializer { 296 | type Ok = (); 297 | type Error = SqlSerializeError; 298 | 299 | fn serialize_field(&mut self, key: &'static str, _value: &T) -> SqlSerializeResult<()> 300 | where 301 | T: ?Sized + Serialize, 302 | { 303 | if !self.output.is_empty() { 304 | self.output += " , "; 305 | } 306 | 307 | self.output += key; 308 | self.output += " = $"; 309 | self.output += key; 310 | 311 | Ok(()) 312 | } 313 | 314 | fn end(self) -> SqlSerializeResult<()> { 315 | Ok(()) 316 | } 317 | } 318 | 319 | // Similar to `SerializeTupleVariant`, here the `end` method is responsible for 320 | // closing both of the curly braces opened by `serialize_struct_variant`. 321 | impl<'a> ser::SerializeStructVariant for &'a mut SqlFieldSerializer { 322 | type Ok = (); 323 | type Error = SqlSerializeError; 324 | 325 | fn serialize_field(&mut self, key: &'static str, _value: &T) -> SqlSerializeResult<()> 326 | where 327 | T: ?Sized + Serialize, 328 | { 329 | if !self.output.is_empty() { 330 | self.output += " , "; 331 | } 332 | 333 | self.output += key; 334 | self.output += " = $"; 335 | self.output += key; 336 | 337 | Ok(()) 338 | } 339 | 340 | fn end(self) -> SqlSerializeResult<()> { 341 | Ok(()) 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /src/node_builder.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | pub trait ToNodeBuilder: Display { 4 | fn quoted(&self) -> String { 5 | format!("\"{self}\"") 6 | } 7 | 8 | /// Draws the start of a relation `->node` 9 | /// 10 | /// # Example 11 | /// ``` 12 | /// use surreal_simple_querybuilder::prelude::*; 13 | /// 14 | /// let s = "user".with("project"); 15 | /// 16 | /// assert_eq!("user->project", s); 17 | /// ``` 18 | fn with(&self, relation_or_node: &str) -> String { 19 | // write the arrow only if the first character is not a special character. 20 | // there are cases where the `node` string that was passed starts with 21 | // an arrow or a dot, in which case we do not want to push a new arrow 22 | // ourselves. 23 | if !relation_or_node.starts_with("->") && !relation_or_node.starts_with(".") { 24 | format!("{self}->{relation_or_node}") 25 | } else { 26 | format!("{self}{relation_or_node}") 27 | } 28 | } 29 | 30 | /// Draws the end of a relation `<-node` 31 | /// 32 | /// # Example 33 | /// ``` 34 | /// use surreal_simple_querybuilder::prelude::*; 35 | /// 36 | /// let s = "user".from("project"); 37 | /// 38 | /// assert_eq!("user<-project", s); 39 | /// ``` 40 | fn from(&self, node: &str) -> String { 41 | format!("{self}<-{node}") 42 | } 43 | 44 | /// Take the current string and add in front of it the given label name as to 45 | /// make a string of the following format `LabelName:CurrentString` 46 | /// 47 | /// # Example 48 | /// ``` 49 | /// use surreal_simple_querybuilder::prelude::*; 50 | /// 51 | /// let label = "John".as_named_label("Account"); 52 | /// 53 | /// assert_eq!(label, "Account:John"); 54 | /// ``` 55 | fn as_named_label(&self, label_name: &str) -> String { 56 | format!("{label_name}:{self}") 57 | } 58 | 59 | fn as_param(&self) -> String { 60 | self 61 | .to_string() 62 | .replace(".", "_") 63 | .replace("->", "_") 64 | .replace("<-", "_") 65 | } 66 | 67 | /// # Example 68 | /// ``` 69 | /// use surreal_simple_querybuilder::prelude::*; 70 | /// 71 | /// let s = "user".equals("John"); 72 | /// 73 | /// // Note that it doesn't add quotes around strings 74 | /// assert_eq!("user = John", s); 75 | /// ``` 76 | fn equals(&self, value: &str) -> String { 77 | format!("{self} = {value}") 78 | } 79 | 80 | /// # Example 81 | /// ``` 82 | /// use surreal_simple_querybuilder::prelude::*; 83 | /// 84 | /// let s = "age".compares(">=", "45"); 85 | /// 86 | /// assert_eq!("age >= 45", s); 87 | /// ``` 88 | fn compares(&self, operator: &str, value: &str) -> String { 89 | format!("{self} {operator} {value}") 90 | } 91 | 92 | /// Take the current string and add the given operator plus ` $current_string` after it 93 | /// 94 | /// # Example 95 | /// ``` 96 | /// use surreal_simple_querybuilder::prelude::*; 97 | /// 98 | /// let s = "age".compares_parameterized(">="); 99 | /// 100 | /// assert_eq!("age >= $age", s); 101 | /// ``` 102 | fn compares_parameterized(&self, operator: &str) -> String { 103 | format!("{self} {operator} ${}", self.as_param()) 104 | } 105 | 106 | /// Take the current string and add `= $current_string` after it 107 | /// 108 | /// # Example 109 | /// ``` 110 | /// use surreal_simple_querybuilder::prelude::*; 111 | /// 112 | /// let s = "account".equals_parameterized(); 113 | /// 114 | /// assert_eq!("account = $account", s); 115 | /// ``` 116 | fn equals_parameterized(&self) -> String { 117 | format!("{self} = ${}", self.as_param()) 118 | } 119 | 120 | /// Take the current string and add `+= $current_string` after it 121 | /// 122 | /// # Example 123 | /// ``` 124 | /// use surreal_simple_querybuilder::prelude::*; 125 | /// 126 | /// let s = "account".plus_equal_parameterized(); 127 | /// 128 | /// assert_eq!("account += $account", s); 129 | /// ``` 130 | fn plus_equal_parameterized(&self) -> String { 131 | format!("{self} += ${}", self.as_param()) 132 | } 133 | 134 | /// Take the current string and add `> $current_string` after it 135 | /// 136 | /// # Example 137 | /// ``` 138 | /// use surreal_simple_querybuilder::prelude::*; 139 | /// 140 | /// let s = "age".greater_parameterized(); 141 | /// 142 | /// assert_eq!("age > $age", s); 143 | /// ``` 144 | fn greater_parameterized(&self) -> String { 145 | format!("{self} > ${}", self.as_param()) 146 | } 147 | 148 | /// Take the current string and add `< $current_string` after it 149 | /// 150 | /// # Example 151 | /// ``` 152 | /// use surreal_simple_querybuilder::prelude::*; 153 | /// 154 | /// let s = "age".lower_parameterized(); 155 | /// 156 | /// assert_eq!("age < $age", s); 157 | /// ``` 158 | fn lower_parameterized(&self) -> String { 159 | format!("{self} < ${}", self.as_param()) 160 | } 161 | 162 | /// Take the current string and add `> value` after it 163 | /// 164 | /// # Example 165 | /// ``` 166 | /// use surreal_simple_querybuilder::prelude::*; 167 | /// 168 | /// let s = "account".greater_than("5"); 169 | /// 170 | /// assert_eq!("account > 5", s); 171 | /// ``` 172 | fn greater_than(&self, value: &str) -> String { 173 | format!("{self} > {value}") 174 | } 175 | 176 | /// Take the current string and add `+= value` after it 177 | /// 178 | /// # Example 179 | /// ``` 180 | /// use surreal_simple_querybuilder::prelude::*; 181 | /// 182 | /// let s = "friends".plus_equal("account:john"); 183 | /// 184 | /// assert_eq!("friends += account:john", s); 185 | /// ``` 186 | fn plus_equal(&self, value: &str) -> String { 187 | format!("{self} += {value}") 188 | } 189 | 190 | /// # Example 191 | /// ``` 192 | /// use surreal_simple_querybuilder::prelude::*; 193 | /// 194 | /// let s = "account".contains_one("'c'"); 195 | /// 196 | /// assert_eq!("account CONTAINS 'c'", s); 197 | /// ``` 198 | fn contains_one(&self, value: &str) -> String { 199 | format!("{self} CONTAINS {value}") 200 | } 201 | 202 | /// # Example 203 | /// ``` 204 | /// use surreal_simple_querybuilder::prelude::*; 205 | /// 206 | /// let s = "account".contains_not("'z'"); 207 | /// 208 | /// assert_eq!("account CONTAINSNOT 'z'", s); 209 | /// ``` 210 | fn contains_not(&self, value: &str) -> String { 211 | format!("{self} CONTAINSNOT {value}") 212 | } 213 | 214 | /// # Example 215 | /// ``` 216 | /// use surreal_simple_querybuilder::prelude::*; 217 | /// 218 | /// let s = "account".contains_all("['a', 'c', 'u']"); 219 | /// 220 | /// assert_eq!("account CONTAINSALL ['a', 'c', 'u']", s); 221 | /// ``` 222 | fn contains_all(&self, values: &str) -> String { 223 | format!("{self} CONTAINSALL {values}") 224 | } 225 | 226 | /// # Example 227 | /// ``` 228 | /// use surreal_simple_querybuilder::prelude::*; 229 | /// 230 | /// let s = "account".contains_any("['a', 'c', 'u']"); 231 | /// 232 | /// assert_eq!("account CONTAINSANY ['a', 'c', 'u']", s); 233 | /// ``` 234 | fn contains_any(&self, values: &str) -> String { 235 | format!("{self} CONTAINSANY {values}") 236 | } 237 | 238 | /// # Example 239 | /// ``` 240 | /// use surreal_simple_querybuilder::prelude::*; 241 | /// 242 | /// let s = "account".contains_none("['z', 'd', 'f']"); 243 | /// 244 | /// assert_eq!("account CONTAINSNONE ['z', 'd', 'f']", s); 245 | /// ``` 246 | fn contains_none(&self, values: &str) -> String { 247 | format!("{self} CONTAINSNONE {values}") 248 | } 249 | 250 | /// Take the current string and add `as alias` after it 251 | /// 252 | /// # Example 253 | /// ``` 254 | /// use surreal_simple_querybuilder::prelude::*; 255 | /// 256 | /// let s = "account->manage->project".as_alias("account_projects"); 257 | /// 258 | /// assert_eq!("account->manage->project AS account_projects", s); 259 | /// ``` 260 | fn as_alias(&self, alias: &str) -> String { 261 | format!("{self} AS {alias}") 262 | } 263 | 264 | /// Take the current string, extract the last segment if it is a nested property, 265 | /// then add parenthesis around it and add the supplied condition in them. 266 | /// 267 | /// # Example 268 | /// ``` 269 | /// use surreal_simple_querybuilder::prelude::*; 270 | /// 271 | /// let path = "account->manage->project"; 272 | /// let s = path.filter("name = 'a_cool_project'"); 273 | /// 274 | /// assert_eq!("account->manage->(project WHERE name = 'a_cool_project')", s); 275 | /// ``` 276 | /// 277 | fn filter(&self, condition: &str) -> String { 278 | // This is a default implementation, but since we need the original string 279 | // to iterate over the chars the function does two string allocations. 280 | let original = self.to_string(); 281 | let original_size = original.len(); 282 | 283 | // this yields the size of the last segment, until a non alphanumeric character 284 | // is found. 285 | let last_segment_size = original 286 | .chars() 287 | .rev() 288 | .take_while(|c| c.is_alphanumeric()) 289 | .count(); 290 | 291 | let left = &original[..original_size - last_segment_size]; 292 | let right = &original[original_size - last_segment_size..]; 293 | 294 | format!("{left}({right} WHERE {condition})") 295 | } 296 | 297 | /// write a comma at the end of the string and append `right` after it. 298 | /// 299 | /// # Example 300 | /// ``` 301 | /// use surreal_simple_querybuilder::prelude::*; 302 | /// 303 | /// let select = "*".comma("<-manage<-User as authors"); 304 | /// let query = format!("select {select} from Files"); 305 | /// 306 | /// assert_eq!("select *, <-manage<-User as authors from Files", query); 307 | /// ``` 308 | fn comma(&self, right: &str) -> String { 309 | format!("{self}, {right}") 310 | } 311 | 312 | /// write a `count()` around the current string so that it sits between the 313 | /// parenthesis. 314 | /// 315 | /// # Example 316 | /// ``` 317 | /// use surreal_simple_querybuilder::prelude::*; 318 | /// 319 | /// let count = "id".count(); 320 | /// let query = format!("select {count} from Files"); 321 | /// 322 | /// assert_eq!("select count(id) from Files", query); 323 | /// ``` 324 | fn count(&self) -> String { 325 | format!("count({self})") 326 | } 327 | 328 | /// Add the supplied `id` right after the current string in order to get the a 329 | /// new string in the following format `current:id` 330 | /// # Example 331 | /// ``` 332 | /// use surreal_simple_querybuilder::prelude::*; 333 | /// 334 | /// let query = "Account".with_id("John"); 335 | /// 336 | /// assert_eq!(query, "Account:John"); 337 | /// ``` 338 | fn with_id(&self, id: &str) -> String { 339 | format!("{self}:{id}") 340 | } 341 | 342 | /// Add the supplied composite `id` right after the current string in order to 343 | /// get the a new string in the following format `current:⟨id⟩`. The `⟨` and `⟩` 344 | /// are automatically added around the id, if you wish to set a regular id then 345 | /// refer to [with_id()](ToNodeBuilder::with_id) 346 | /// 347 | /// # Example 348 | /// ``` 349 | /// use surreal_simple_querybuilder::prelude::*; 350 | /// 351 | /// let query = "Account".with_composite_id("John/Doe"); 352 | /// 353 | /// assert_eq!(query, "Account:⟨John/Doe⟩"); 354 | /// ``` 355 | fn with_composite_id(&self, id: &str) -> String { 356 | format!("{self}:⟨{id}⟩") 357 | } 358 | } 359 | 360 | impl<'a> ToNodeBuilder for &'a str { 361 | fn filter(&self, condition: &str) -> String { 362 | // unlike the default implementation of this trait function, the &str impl 363 | // does only one allocation. 364 | let original_size = self.len(); 365 | 366 | // this yields the size of the last segment, until a non alphanumeric character 367 | // is found. 368 | let last_segment_size = self 369 | .chars() 370 | .rev() 371 | .take_while(|c| c.is_alphanumeric()) 372 | .count(); 373 | 374 | let left = &self[..original_size - last_segment_size]; 375 | let right = &self[original_size - last_segment_size..]; 376 | 377 | format!("{left}({right} WHERE {condition})") 378 | } 379 | } 380 | 381 | pub trait NodeBuilder: Display { 382 | /// Draws the start of a relation `->node` 383 | /// 384 | /// # Example 385 | /// ``` 386 | /// use surreal_simple_querybuilder::prelude::*; 387 | /// 388 | /// let s = "user".with("project"); 389 | /// 390 | /// assert_eq!("user->project", s); 391 | /// ``` 392 | fn with(&mut self, relation_or_node: &str) -> &mut String; 393 | 394 | /// Allows you to pass a lambda that should mutate the current string when the 395 | /// passed `condition` is `true`. If `condition` is `false` then the `action` 396 | /// lambda is ignored and the string stays intact. 397 | /// 398 | /// # Example 399 | /// ``` 400 | /// use surreal_simple_querybuilder::prelude::*; 401 | /// 402 | /// // demonstrate how the given closure is ignored if the condition is `false` 403 | /// let mut label = "John".as_named_label("User"); 404 | /// let intact = &mut label 405 | /// .if_then(false, |s| s.with("LOVES").with("User")) 406 | /// .with("FRIEND") 407 | /// .with("User"); 408 | /// 409 | /// assert_eq!("User:John->FRIEND->User", *intact); 410 | /// 411 | /// // demonstrate how the given closure is executed if the condition is `true` 412 | /// let mut label = "John".as_named_label("User"); 413 | /// let modified = &mut label 414 | /// .if_then(true, |s| s.with("LOVES").with("User")) 415 | /// .with("FRIEND") 416 | /// .with("User"); 417 | /// 418 | /// assert_eq!("User:John->LOVES->User->FRIEND->User", *modified); 419 | /// ``` 420 | fn if_then(&mut self, condition: bool, action: fn(&mut Self) -> &mut Self) -> &mut String; 421 | 422 | /// Take the current string add add `> value` after it 423 | /// 424 | /// # Example 425 | /// ``` 426 | /// use surreal_simple_querybuilder::prelude::*; 427 | /// 428 | /// let s = "account".greater_than("5"); 429 | /// 430 | /// assert_eq!("account > 5", s); 431 | /// ``` 432 | fn greater_than(&mut self, value: &str) -> &mut String; 433 | 434 | /// Take the current string and add `+= value` after it 435 | /// 436 | /// # Example 437 | /// ``` 438 | /// use surreal_simple_querybuilder::prelude::*; 439 | /// 440 | /// let s = "friends".plus_equal("account:john"); 441 | /// 442 | /// assert_eq!("friends += account:john", s); 443 | /// ``` 444 | fn plus_equal(&mut self, value: &str) -> &mut String; 445 | } 446 | 447 | impl NodeBuilder for String { 448 | fn with(&mut self, node: &str) -> &mut String { 449 | // push the arrow only if the first character is not a special character. 450 | // there are cases where the `node` string that was passed starts with 451 | // an arrow or a dot, in which case we do not want to push a new arrow 452 | // ourselves. 453 | if !node.starts_with("->") && !node.starts_with(".") { 454 | self.push_str("->"); 455 | } 456 | 457 | self.push_str(node); 458 | 459 | self 460 | } 461 | 462 | fn if_then(&mut self, condition: bool, action: fn(&mut Self) -> &mut Self) -> &mut String { 463 | match condition { 464 | true => action(self), 465 | false => self, 466 | } 467 | } 468 | 469 | fn greater_than(&mut self, value: &str) -> &mut String { 470 | self.push_str(" > "); 471 | self.push_str(value); 472 | 473 | self 474 | } 475 | 476 | fn plus_equal(&mut self, value: &str) -> &mut String { 477 | self.push_str(" += "); 478 | self.push_str(value); 479 | 480 | self 481 | } 482 | } 483 | 484 | impl ToNodeBuilder for String {} 485 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "foreign")] 2 | pub use crate::foreign_key::*; 3 | 4 | #[cfg(feature = "model")] 5 | pub use crate::model; 6 | 7 | #[cfg(feature = "model")] 8 | pub use crate::model::*; 9 | 10 | pub use crate::node_builder::*; 11 | 12 | #[cfg(feature = "queries")] 13 | pub use crate::queries::*; 14 | #[cfg(feature = "queries")] 15 | pub use crate::types::*; 16 | 17 | pub use crate::querybuilder::*; 18 | -------------------------------------------------------------------------------- /src/queries/create.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::Create; 2 | use crate::prelude::Set; 3 | 4 | use super::bindings; 5 | use super::query; 6 | use super::BindingMap; 7 | use super::QueryBuilderInjecter; 8 | 9 | /// # Example 10 | /// ```rs 11 | /// let set = Set(serde_json::json!({ "name": "John", "age": 10 })); 12 | /// let (query, params) = create("User", set).unwrap(); 13 | /// 14 | /// assert_eq!("CREATE User SET age = $age , name = $name", query); 15 | /// assert_eq!(params.get("name"), Some(&"\"John\"".to_owned())); 16 | /// assert_eq!(params.get("age"), Some(&"10".to_owned())); 17 | /// ``` 18 | pub fn create<'a, T>( 19 | what: &'static str, component: Set, 20 | ) -> serde_json::Result<(String, BindingMap)> 21 | where 22 | Set: QueryBuilderInjecter<'a> + 'a, 23 | { 24 | let params = (Create(what), component); 25 | let query = query(¶ms)?; 26 | let bindings = bindings(params)?; 27 | 28 | Ok((query, bindings)) 29 | } 30 | 31 | #[test] 32 | fn test_create() { 33 | use crate::prelude::*; 34 | use serde_json::Value; 35 | 36 | let set = Set(serde_json::json!({ "name": "John", "age": 10 })); 37 | let (query, params) = create("User", set).unwrap(); 38 | 39 | assert_eq!("CREATE User SET name = $name , age = $age", query); 40 | 41 | assert_eq!(params.get("name"), Some(&Value::from("John".to_owned()))); 42 | assert_eq!(params.get("age"), Some(&Value::from(10))); 43 | } 44 | -------------------------------------------------------------------------------- /src/queries/delete.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::Delete; 2 | 3 | use super::bindings; 4 | use super::query; 5 | use super::BindingMap; 6 | use super::QueryBuilderInjecter; 7 | 8 | /// ``` 9 | /// use surreal_simple_querybuilder::prelude::*; 10 | /// use serde_json::json; 11 | /// 12 | /// let filter = Where(serde_json::json!({ "name": "John", "age": 10 })); 13 | /// let (query, params) = delete("User", filter).unwrap(); 14 | /// 15 | /// assert_eq!("DELETE User WHERE name = $name AND age = $age", query); 16 | /// assert_eq!(params.get("name"), Some(&json!("John"))); 17 | /// assert_eq!(params.get("age"), Some(&json!(10))); 18 | /// 19 | /// let (query, params) = delete("User:john", ()).unwrap(); 20 | /// 21 | /// assert_eq!("DELETE User:john", query); 22 | /// assert!(params.is_empty()); 23 | /// ``` 24 | pub fn delete<'a, 'b>( 25 | table: &'static str, component: impl QueryBuilderInjecter<'a> + 'a, 26 | ) -> serde_json::Result<(String, BindingMap)> { 27 | let params = (Delete(table), component); 28 | 29 | Ok((query(¶ms)?, bindings(params)?)) 30 | } 31 | -------------------------------------------------------------------------------- /src/queries/impls.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | /// Blanket implementation for the unit type so it can be passed as a "null" type 4 | /// of param 5 | impl<'a> QueryBuilderInjecter<'a> for () {} 6 | 7 | impl<'a> QueryBuilderInjecter<'a> for bool { 8 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 9 | querybuilder.raw(match self { 10 | true => "true", 11 | false => "false", 12 | }) 13 | } 14 | 15 | fn params(self, _map: &mut BindingMap) -> serde_json::Result<()> 16 | where 17 | Self: Sized, 18 | { 19 | Ok(()) 20 | } 21 | } 22 | 23 | /// Allows to pass Option types of injecters, useful for optional injecters: 24 | /// ```rs 25 | /// let should_fetch = false; 26 | /// let maybe_fetch = false.then(|| Some(Fetch(["author"]))); 27 | /// ``` 28 | impl<'a, Injecters> QueryBuilderInjecter<'a> for Option 29 | where 30 | Injecters: QueryBuilderInjecter<'a>, 31 | { 32 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 33 | match self { 34 | Some(inner) => inner.inject(querybuilder), 35 | None => querybuilder, 36 | } 37 | } 38 | 39 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 40 | where 41 | Self: Sized, 42 | { 43 | match self { 44 | Some(inner) => inner.params(map), 45 | None => Ok(()), 46 | } 47 | } 48 | } 49 | 50 | /// Allows to pass a vec of Injecters 51 | impl<'a, Injecters> QueryBuilderInjecter<'a> for Vec 52 | where 53 | Injecters: QueryBuilderInjecter<'a>, 54 | { 55 | fn inject(&self, mut querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 56 | for injecter in self { 57 | querybuilder = injecter.inject(querybuilder); 58 | } 59 | 60 | querybuilder 61 | } 62 | 63 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 64 | where 65 | Self: Sized, 66 | { 67 | for injecter in self { 68 | injecter.params(map)?; 69 | } 70 | 71 | Ok(()) 72 | } 73 | } 74 | 75 | impl<'a, I1, I2> QueryBuilderInjecter<'a> for (I1, I2) 76 | where 77 | I1: QueryBuilderInjecter<'a>, 78 | I2: QueryBuilderInjecter<'a>, 79 | { 80 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 81 | self.1.inject(self.0.inject(querybuilder)) 82 | } 83 | 84 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 85 | where 86 | Self: Sized, 87 | { 88 | self.1.params(map).and(self.0.params(map)) 89 | } 90 | } 91 | 92 | impl<'a, I1, I2, I3> QueryBuilderInjecter<'a> for (I1, I2, I3) 93 | where 94 | I1: QueryBuilderInjecter<'a>, 95 | I2: QueryBuilderInjecter<'a>, 96 | I3: QueryBuilderInjecter<'a>, 97 | { 98 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 99 | self.2.inject(self.1.inject(self.0.inject(querybuilder))) 100 | } 101 | 102 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 103 | where 104 | Self: Sized, 105 | { 106 | self 107 | .2 108 | .params(map) 109 | .and(self.1.params(map).and(self.0.params(map))) 110 | } 111 | } 112 | 113 | impl<'a, I1, I2, I3, I4> QueryBuilderInjecter<'a> for (I1, I2, I3, I4) 114 | where 115 | I1: QueryBuilderInjecter<'a>, 116 | I2: QueryBuilderInjecter<'a>, 117 | I3: QueryBuilderInjecter<'a>, 118 | I4: QueryBuilderInjecter<'a>, 119 | { 120 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 121 | self 122 | .3 123 | .inject(self.2.inject(self.1.inject(self.0.inject(querybuilder)))) 124 | } 125 | 126 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 127 | where 128 | Self: Sized, 129 | { 130 | self.3.params(map).and( 131 | self 132 | .2 133 | .params(map) 134 | .and(self.1.params(map).and(self.0.params(map))), 135 | ) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/queries/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::prelude::QueryBuilder; 4 | 5 | mod create; 6 | mod delete; 7 | mod impls; 8 | mod select; 9 | mod update; 10 | 11 | pub use create::create; 12 | pub use delete::delete; 13 | pub use select::select; 14 | pub use update::update; 15 | 16 | pub type BindingMap = HashMap; 17 | 18 | pub trait QueryBuilderInjecter<'a> { 19 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 20 | querybuilder 21 | } 22 | 23 | fn params(self, _map: &mut BindingMap) -> serde_json::Result<()> 24 | where 25 | Self: Sized, 26 | { 27 | Ok(()) 28 | } 29 | } 30 | 31 | // TODO: this function could maybe be converted to a const fn? Or at least be 32 | // cached 33 | /// Constructs the query string using the supplied injecters. Refer to the individual injecters 34 | /// for more information on how to combine them before passing them to this function or [create], or [delete], or [select], or [update]. 35 | /// 36 | /// See: [injecters](super::types) module. 37 | pub fn query<'a>(component: &impl QueryBuilderInjecter<'a>) -> serde_json::Result { 38 | let builder = QueryBuilder::new(); 39 | let builder = component.inject(builder); 40 | let query = builder.build(); 41 | 42 | Ok(query) 43 | } 44 | 45 | /// Collects the parameters out of the supplied injecters. 46 | /// 47 | /// ``` 48 | /// use surreal_simple_querybuilder::prelude::*; 49 | /// use serde_json::json; 50 | /// 51 | /// let filter = Where(("id", 5)); 52 | /// let and = And(("name", "john")); 53 | /// 54 | /// let bindings = bindings((filter, and)).unwrap(); 55 | /// 56 | /// assert_eq!(bindings.get("id"), Some(&json!(5))); 57 | /// assert_eq!(bindings.get("name"), Some(&json!("john"))); 58 | /// ``` 59 | pub fn bindings<'a>( 60 | component: impl QueryBuilderInjecter<'a> + 'a, 61 | ) -> serde_json::Result { 62 | let mut params = HashMap::new(); 63 | component.params(&mut params)?; 64 | 65 | Ok(params) 66 | } 67 | -------------------------------------------------------------------------------- /src/queries/select.rs: -------------------------------------------------------------------------------- 1 | use crate::types::From; 2 | use crate::types::Select; 3 | 4 | use super::bindings; 5 | use super::query; 6 | use super::BindingMap; 7 | use super::QueryBuilderInjecter; 8 | 9 | pub fn select<'a>( 10 | what: &'static str, from: &'static str, component: impl QueryBuilderInjecter<'a> + 'a, 11 | ) -> serde_json::Result<(String, BindingMap)> { 12 | let params = (Select(what), From(from), component); 13 | let query = query(¶ms)?; 14 | let bindings = bindings(params)?; 15 | 16 | Ok((query, bindings)) 17 | } 18 | 19 | #[test] 20 | fn test_select() { 21 | use crate::prelude::*; 22 | use serde_json::Value; 23 | 24 | let filter = serde_json::json!({ "name": "John", "age": 10 }); 25 | let pagination = Pagination::from(10..25); 26 | let fetch = Fetch(["friends", "articles"]); 27 | let components = (Where(filter), pagination, fetch); 28 | 29 | let (query, params) = select("*", "User", components).unwrap(); 30 | 31 | assert_eq!( 32 | "SELECT * FROM User WHERE name = $name AND age = $age LIMIT 15 START AT 10 FETCH friends , articles", 33 | query 34 | ); 35 | 36 | assert_eq!(params.get("name"), Some(&Value::from("John".to_owned()))); 37 | assert_eq!(params.get("age"), Some(&Value::from(10))); 38 | 39 | let (query, params) = select( 40 | "*", 41 | "User", 42 | Where(( 43 | Or(serde_json::json!({ 44 | "one": 1, 45 | "two": 2 46 | })), 47 | ("three", 3), 48 | )), 49 | ) 50 | .unwrap(); 51 | 52 | assert_eq!( 53 | "SELECT * FROM User WHERE one = $one OR two = $two AND three = $three", 54 | query 55 | ); 56 | assert_eq!(params.get("one"), Some(&Value::from(1))); 57 | assert_eq!(params.get("two"), Some(&Value::from(2))); 58 | assert_eq!(params.get("three"), Some(&Value::from(3))); 59 | } 60 | -------------------------------------------------------------------------------- /src/queries/update.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::Update; 2 | 3 | use super::bindings; 4 | use super::query; 5 | use super::BindingMap; 6 | use super::QueryBuilderInjecter; 7 | 8 | /// # Example 9 | /// ```rs 10 | /// use crate::prelude::*; 11 | /// use serde_json::Value; 12 | /// 13 | /// let filter = Set(serde_json::json!({ "name": "John", "age": 10 })); 14 | /// let (query, params) = update("User", filter).unwrap(); 15 | /// 16 | /// assert_eq!("UPDATE User SET age = $age , name = $name", query); 17 | /// assert_eq!(params.get("name"), Some(&Value::from("John".to_owned()))); 18 | /// assert_eq!(params.get("age"), Some(&Value::from(10))); 19 | /// ``` 20 | /// # Security 21 | /// The `table` parameter is not escaped, if it contains user input then it is 22 | /// recommended you escape the data manually first. 23 | pub fn update<'a, 'b>( 24 | table: &'a str, component: impl QueryBuilderInjecter<'a> + 'a, 25 | ) -> serde_json::Result<(String, BindingMap)> { 26 | let params = (Update(table), component); 27 | 28 | Ok((query(¶ms)?, bindings(params)?)) 29 | } 30 | 31 | #[test] 32 | fn test_update() { 33 | use crate::prelude::*; 34 | use serde_json::Value; 35 | 36 | let filter = Set(serde_json::json!({ "name": "John", "age": 10 })); 37 | let (query, params) = update("User", filter).unwrap(); 38 | 39 | assert_eq!("UPDATE User SET name = $name , age = $age", query); 40 | assert_eq!(params.get("name"), Some(&Value::from("John".to_owned()))); 41 | assert_eq!(params.get("age"), Some(&Value::from(10))); 42 | } 43 | -------------------------------------------------------------------------------- /src/types/also.rs: -------------------------------------------------------------------------------- 1 | use crate::queries::QueryBuilderInjecter; 2 | use crate::querybuilder::QueryBuilder; 3 | 4 | /// Can be used to add a comma to the query followed by a parameter or a string 5 | /// 6 | /// # Example 7 | /// ``` 8 | /// use surreal_simple_querybuilder::prelude::*; 9 | /// 10 | /// let fetch = Fetch("profile"); 11 | /// let other_fetch = Also("friends"); 12 | /// 13 | /// let (query, _params) = select("*", "users", (fetch, other_fetch)).unwrap(); 14 | /// 15 | /// assert_eq!(query, "SELECT * FROM users FETCH profile , friends"); 16 | /// ``` 17 | pub struct Also(pub T); 18 | 19 | /// implementation for `Also` that contains a string slice, 20 | impl<'a> QueryBuilderInjecter<'a> for Also<&'a str> { 21 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 22 | querybuilder.also(self.0) 23 | } 24 | } 25 | 26 | impl<'a, T> QueryBuilderInjecter<'a> for Also 27 | where 28 | T: QueryBuilderInjecter<'a>, 29 | { 30 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 31 | querybuilder.commas(|q| self.0.inject(q)) 32 | } 33 | 34 | fn params(self, map: &mut crate::queries::BindingMap) -> serde_json::Result<()> 35 | where 36 | Self: Sized, 37 | { 38 | self.0.params(map) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/types/and.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::QueryBuilder; 2 | use crate::prelude::QueryBuilderInjecter; 3 | use crate::queries::BindingMap; 4 | 5 | /// Appends a `AND` statement followed by anything inside the [and]. 6 | /// If the [And] contains multiple items then `AND`s will be added **between** them 7 | /// but not in front. 8 | /// 9 | /// # Example 10 | /// ``` 11 | /// use surreal_simple_querybuilder::prelude::*; 12 | /// 13 | /// let filter = Where("id = 5"); 14 | /// let q = query(&(filter, And("name = 'john'"))).unwrap(); 15 | /// assert_eq!(q, "WHERE id = 5 AND name = 'john'"); 16 | /// 17 | /// // passing multiple items to And tells it to no longer append a AND in front 18 | /// // but between the items: 19 | /// let filter = Where(( 20 | /// And(( // <- notice the Or is INSIDE the Where, as AND is added between the items now 21 | /// ("id", Sql("5")), 22 | /// ("name", Sql("'john'")), 23 | /// ("role", Sql("'premium'")) 24 | /// )) 25 | /// )); 26 | /// 27 | /// let q = query(&filter).unwrap(); 28 | /// assert_eq!(q, "WHERE id = 5 AND name = 'john' AND role = 'premium'"); 29 | /// ``` 30 | pub struct And(pub T); 31 | 32 | impl<'a, T: QueryBuilderInjecter<'a>> QueryBuilderInjecter<'a> for And { 33 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 34 | querybuilder.ands(|q| self.0.inject(q)) 35 | } 36 | 37 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> { 38 | self.0.params(map) 39 | } 40 | } 41 | 42 | impl<'a> QueryBuilderInjecter<'a> for And<&'a str> { 43 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 44 | querybuilder.and(self.0) 45 | } 46 | 47 | fn params(self, _map: &mut BindingMap) -> serde_json::Result<()> 48 | where 49 | Self: Sized, 50 | { 51 | Ok(()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/types/bind.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | use crate::prelude::QueryBuilderInjecter; 4 | 5 | /// Used to explicitly bind a variable in the final hashmap of bindings without 6 | /// altering the query string in any way. 7 | /// ``` 8 | /// use surreal_simple_querybuilder::prelude::*; 9 | /// use serde_json::json; 10 | /// 11 | /// let param = Bind(("my_id", 5)); 12 | /// let (query, bindings) = select("*", "users WHERE id = $my_id", param).unwrap(); 13 | /// 14 | /// assert_eq!(query, "SELECT * FROM users WHERE id = $my_id"); 15 | /// assert_eq!(bindings.get("my_id"), Some(&json!(5))); 16 | /// 17 | /// let param = Bind(json!({ 18 | /// "my_id": 5, 19 | /// "created_at": 123456 20 | /// })); 21 | /// let (query, bindings) = select("*", "users WHERE id = $my_id AND created_at > $created_at", param).unwrap(); 22 | /// 23 | /// assert_eq!(query, "SELECT * FROM users WHERE id = $my_id AND created_at > $created_at"); 24 | /// assert_eq!(bindings.get("my_id"), Some(&json!(5))); 25 | /// assert_eq!(bindings.get("created_at"), Some(&json!(123456))); 26 | /// ``` 27 | pub struct Bind(pub T); 28 | 29 | impl<'a, Key, V> QueryBuilderInjecter<'a> for Bind<(Key, V)> 30 | where 31 | Key: crate::node_builder::ToNodeBuilder, 32 | V: Serialize, 33 | { 34 | fn params(self, map: &mut crate::queries::BindingMap) -> serde_json::Result<()> 35 | where 36 | Self: Sized, 37 | { 38 | super::Equal::equal_params(map, &self.0 .0, self.0 .1) 39 | } 40 | } 41 | 42 | impl<'a> QueryBuilderInjecter<'a> for Bind { 43 | fn params(self, map: &mut crate::queries::BindingMap) -> serde_json::Result<()> 44 | where 45 | Self: Sized, 46 | { 47 | self.0.params(map) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/types/build.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::QueryBuilder; 2 | use crate::prelude::QueryBuilderInjecter; 3 | 4 | /// An indirect way to access the underlying querybuilder that is passed to 5 | /// the injecters in special cases where you may use injecters everywhere but need 6 | /// to bypass an eventual limitation around them: 7 | /// ``` 8 | /// use surreal_simple_querybuilder::prelude::*; 9 | /// 10 | /// let param_where = Where(("id", Sql("5"))); 11 | /// let param_build = Build(|querybuilder: QueryBuilder| querybuilder.and("name = 'john'")); 12 | /// let query = query(&(param_where, param_build)).unwrap(); 13 | /// 14 | /// assert_eq!(query, "WHERE id = 5 AND name = 'john'"); 15 | /// ``` 16 | pub struct Build(pub T) 17 | where 18 | T: Fn(QueryBuilder) -> QueryBuilder; 19 | 20 | impl<'a, T> QueryBuilderInjecter<'a> for Build 21 | where 22 | T: Fn(QueryBuilder) -> QueryBuilder, 23 | { 24 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 25 | self.0(querybuilder) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/types/cmp.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use serde::Serialize; 4 | use serde_json::Value; 5 | 6 | use crate::prelude::QueryBuilder; 7 | use crate::prelude::QueryBuilderInjecter; 8 | use crate::prelude::ToNodeBuilder; 9 | use crate::queries::BindingMap; 10 | 11 | use super::ser_to_param_value; 12 | 13 | type Operator = &'static str; 14 | 15 | /// Appends a comparison. This type is primarily made in case a pre-made injecter 16 | /// is not available in this crate, for the common operators then refer to: 17 | /// - [Equal](super::Equal) 18 | /// - [Greater](super::Greater) 19 | /// - [Lower](super::Lower) 20 | /// 21 | /// # Example 22 | /// ``` 23 | /// use surreal_simple_querybuilder::prelude::*; 24 | /// 25 | /// let param = ( 26 | /// Select("*"), 27 | /// From("users"), 28 | /// Where( 29 | /// Cmp( 30 | /// "~=", 31 | /// ("roles", "premium") 32 | /// ) 33 | /// ) 34 | /// ); 35 | /// 36 | /// assert_eq!(query(¶m).unwrap(), "SELECT * FROM users WHERE roles ~= $roles"); 37 | /// let params = bindings(param).unwrap(); 38 | /// assert_eq!(params.get("roles"), Some(&serde_json::json!("premium"))); 39 | /// ``` 40 | pub struct Cmp(pub Operator, pub T); 41 | 42 | /// Base functions for all implementations of the `QueryBuilderInjecter` trait 43 | impl Cmp<()> { 44 | fn cmp_inject<'a>( 45 | mut querybuilder: QueryBuilder<'a>, operator: Operator, key: &impl ToNodeBuilder, 46 | ) -> QueryBuilder<'a> { 47 | querybuilder.add_segment(key.compares_parameterized(operator)); 48 | 49 | querybuilder 50 | } 51 | 52 | fn cmp_params( 53 | map: &mut BindingMap, key: &impl ToNodeBuilder, value: impl Serialize, 54 | ) -> serde_json::Result<()> { 55 | map.insert(key.as_param(), ser_to_param_value(value)?); 56 | 57 | Ok(()) 58 | } 59 | } 60 | 61 | impl<'a, Key, Value> QueryBuilderInjecter<'a> for Cmp<&(Key, Value)> 62 | where 63 | Key: ToNodeBuilder, 64 | Value: Serialize, 65 | { 66 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 67 | Cmp::cmp_inject(querybuilder, self.0, &self.1 .0) 68 | } 69 | 70 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 71 | where 72 | Self: Sized, 73 | { 74 | Cmp::cmp_params(map, &self.1 .0, &self.1 .1) 75 | } 76 | } 77 | 78 | impl<'a, Key, Value> QueryBuilderInjecter<'a> for Cmp<(Key, Value)> 79 | where 80 | Key: ToNodeBuilder + Display, 81 | Value: Serialize, 82 | { 83 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 84 | Cmp(self.0, &self.1).inject(querybuilder) 85 | } 86 | 87 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 88 | where 89 | Self: Sized, 90 | { 91 | Cmp(self.0, &self.1).params(map) 92 | } 93 | } 94 | 95 | impl<'a> QueryBuilderInjecter<'a> for Cmp { 96 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 97 | if let Some(map) = self.1.as_object() { 98 | return map 99 | .keys() 100 | .fold(querybuilder, |q, key| Cmp::cmp_inject(q, self.0, key)); 101 | } 102 | 103 | querybuilder 104 | } 105 | 106 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> { 107 | self.1.params(map) 108 | } 109 | } 110 | 111 | impl<'a, Value> QueryBuilderInjecter<'a> for Cmp<&[(&str, Value)]> 112 | where 113 | Value: Serialize, 114 | { 115 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 116 | self.1.inject(querybuilder) 117 | } 118 | 119 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 120 | where 121 | Self: Sized, 122 | { 123 | self.1.params(map) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/types/create.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::QueryBuilder; 2 | use crate::prelude::QueryBuilderInjecter; 3 | 4 | /// ``` 5 | /// use surreal_simple_querybuilder::prelude::*; 6 | /// 7 | /// let param = Create("user:john"); 8 | /// let q = query(¶m).unwrap(); 9 | /// assert_eq!(q, "CREATE user:john"); 10 | /// ``` 11 | pub struct Create(pub T); 12 | 13 | impl<'a> QueryBuilderInjecter<'a> for Create<&'a str> { 14 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 15 | querybuilder.create(self.0) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/types/delete.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::QueryBuilder; 2 | use crate::prelude::QueryBuilderInjecter; 3 | 4 | /// Appends a DELETE statement to the query. 5 | /// 6 | /// It isn't very useful by itself, especially when compared to the [delete](crate::queries::delete) 7 | /// query function that takes care of building the query until the end. 8 | /// ``` 9 | /// use surreal_simple_querybuilder::prelude::*; 10 | /// 11 | /// let param = Delete("user:john"); 12 | /// let query = QueryBuilder::new().injecter(¶m).build(); 13 | /// 14 | /// assert_eq!(query, "DELETE user:john"); 15 | /// ``` 16 | pub struct Delete(pub T); 17 | 18 | impl<'a> QueryBuilderInjecter<'a> for Delete<&'a str> { 19 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 20 | querybuilder.delete(self.0) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/types/equal.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use serde::Serialize; 4 | use serde_json::Value; 5 | 6 | use crate::prelude::QueryBuilder; 7 | use crate::prelude::QueryBuilderInjecter; 8 | use crate::prelude::ToNodeBuilder; 9 | use crate::queries::BindingMap; 10 | 11 | use super::ser_to_param_value; 12 | use super::to_param_value; 13 | 14 | /// Append a `left = $left` comparison. The default behaviour for the right side 15 | /// is to always be turned into a parameter that ends up in the `params` hashmap: 16 | /// ``` 17 | /// use surreal_simple_querybuilder::prelude::*; 18 | /// 19 | /// let value = 10; 20 | /// let param = Where(Equal(("id", value))); 21 | /// let (query, params) = select("*", "users", param).unwrap(); 22 | /// 23 | /// assert_eq!(query, "SELECT * FROM users WHERE id = $id"); 24 | /// assert_eq!(params.get("id"), Some(&serde_json::json!(10))); 25 | /// ``` 26 | /// 27 | /// **NOTE**: [Equal] offers the same result as using a raw tuple `(left, right)`: 28 | /// ``` 29 | /// use surreal_simple_querybuilder::prelude::*; 30 | /// 31 | /// let value = 10; 32 | /// let param = Where(("id", value)); // <- a tuple directly 33 | /// let (query, params) = select("*", "users", param).unwrap(); 34 | /// 35 | /// assert_eq!(query, "SELECT * FROM users WHERE id = $id"); 36 | /// assert_eq!(params.get("id"), Some(&serde_json::json!(10))); 37 | /// ``` 38 | /// 39 | /// In order to bypass the default behaviour of the right side being turned into 40 | /// a parameter, and until trait specialization is not available, the [Sql](super::Sql) 41 | /// type can be used to force raw SQL: 42 | /// ``` 43 | /// use surreal_simple_querybuilder::prelude::*; 44 | /// 45 | /// let param = Where(("id", Sql("10"))); 46 | /// let (query, params) = select("*", "users", param).unwrap(); 47 | /// 48 | /// assert_eq!(query, "SELECT * FROM users WHERE id = 10"); 49 | /// assert_eq!(params.get("id"), None); 50 | /// ``` 51 | pub struct Equal(pub T); 52 | 53 | /// Base functions for all implementations of the `QueryBuilderInjecter` trait 54 | impl Equal<()> { 55 | pub(crate) fn equal_inject<'a>( 56 | mut querybuilder: QueryBuilder<'a>, key: &impl ToNodeBuilder, 57 | ) -> QueryBuilder<'a> { 58 | querybuilder.add_segment(key.equals_parameterized()); 59 | 60 | querybuilder 61 | } 62 | 63 | pub(crate) fn equal_params( 64 | map: &mut BindingMap, key: &impl ToNodeBuilder, value: impl Serialize, 65 | ) -> serde_json::Result<()> { 66 | map.insert(key.as_param(), ser_to_param_value(value)?); 67 | 68 | Ok(()) 69 | } 70 | } 71 | 72 | /// This impl allows for passing a raw col that won't be parameterized using the 73 | /// [Sql] type. See the dedicated test below for more information 74 | impl<'a> QueryBuilderInjecter<'a> for (&str, super::Sql<&'a str>) { 75 | fn inject(&self, mut querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 76 | querybuilder.add_segment(self.0.equals(self.1 .0)); 77 | querybuilder 78 | } 79 | 80 | fn params(self, _: &mut BindingMap) -> serde_json::Result<()> 81 | where 82 | Self: Sized, 83 | { 84 | Ok(()) 85 | } 86 | } 87 | 88 | #[test] 89 | fn test_queries_equal_tuple_str() { 90 | use crate::prelude::*; 91 | 92 | let param = Where(("col", Sql("value"))); 93 | let (query, _) = select("*", "users", param).unwrap(); 94 | 95 | assert_eq!(query, "SELECT * FROM users WHERE col = value"); 96 | } 97 | 98 | impl<'a, Value> QueryBuilderInjecter<'a> for &(&str, Value) 99 | where 100 | Value: Serialize, 101 | { 102 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 103 | Equal::equal_inject(querybuilder, &self.0) 104 | } 105 | 106 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 107 | where 108 | Self: Sized, 109 | { 110 | Equal::equal_params(map, &self.0, &self.1) 111 | } 112 | } 113 | 114 | impl<'a, Value> QueryBuilderInjecter<'a> for (&str, Value) 115 | where 116 | Value: Serialize, 117 | { 118 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 119 | Equal::equal_inject(querybuilder, &self.0) 120 | } 121 | 122 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 123 | where 124 | Self: Sized, 125 | { 126 | Equal::equal_params(map, &self.0, &self.1) 127 | } 128 | } 129 | 130 | #[cfg(feature = "model")] 131 | use crate::prelude::SchemaField; 132 | 133 | #[cfg(feature = "model")] 134 | impl<'a, Value, const N: usize> QueryBuilderInjecter<'a> for (SchemaField, Value) 135 | where 136 | Value: Serialize, 137 | { 138 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 139 | Equal::equal_inject(querybuilder, &self.0) 140 | } 141 | 142 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 143 | where 144 | Self: Sized, 145 | { 146 | Equal::equal_params(map, &self.0, &self.1) 147 | } 148 | } 149 | 150 | /// This impl allows for passing a raw model field that won't be parameterized 151 | /// using the [Sql] type. 152 | #[cfg(feature = "model")] 153 | impl<'a, const N: usize, const N1: usize> QueryBuilderInjecter<'a> 154 | for (SchemaField, super::Sql>) 155 | where 156 | Value: Serialize, 157 | { 158 | fn inject(&self, mut querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 159 | querybuilder.add_segment(self.0.equals(&self.1 .0)); 160 | querybuilder 161 | } 162 | 163 | fn params(self, _: &mut BindingMap) -> serde_json::Result<()> 164 | where 165 | Self: Sized, 166 | { 167 | Ok(()) 168 | } 169 | } 170 | 171 | impl<'a, Value> QueryBuilderInjecter<'a> for &[(&str, Value)] 172 | where 173 | Value: Serialize, 174 | { 175 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 176 | (*self).iter().fold(querybuilder, |q, pair| pair.inject(q)) 177 | } 178 | 179 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 180 | where 181 | Self: Sized, 182 | { 183 | for pair in self { 184 | pair.params(map)?; 185 | } 186 | 187 | Ok(()) 188 | } 189 | } 190 | 191 | impl<'a> QueryBuilderInjecter<'a> for Value { 192 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 193 | let mut query = querybuilder; 194 | 195 | if let Some(map) = self.as_object() { 196 | for key in map.keys() { 197 | query = Equal::equal_inject(query, key); 198 | } 199 | } 200 | 201 | query 202 | } 203 | 204 | fn params(self, params: &mut BindingMap) -> serde_json::Result<()> { 205 | match self { 206 | Value::Object(map) => { 207 | let iter = map 208 | .into_iter() 209 | .map(|(key, value)| (key.as_param(), to_param_value(value).unwrap())); 210 | 211 | params.extend(iter); 212 | } 213 | _ => {} 214 | }; 215 | 216 | Ok(()) 217 | } 218 | } 219 | 220 | impl<'a, Key, Value> QueryBuilderInjecter<'a> for Equal<&(Key, Value)> 221 | where 222 | Key: ToNodeBuilder, 223 | Value: Serialize, 224 | { 225 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 226 | Equal::equal_inject(querybuilder, &self.0 .0) 227 | } 228 | 229 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 230 | where 231 | Self: Sized, 232 | { 233 | Equal::equal_params(map, &self.0 .0, &self.0 .1) 234 | } 235 | } 236 | 237 | impl<'a, Key, Value> QueryBuilderInjecter<'a> for Equal<(Key, Value)> 238 | where 239 | Key: ToNodeBuilder + Display, 240 | Value: Serialize, 241 | { 242 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 243 | Equal(&self.0).inject(querybuilder) 244 | } 245 | 246 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 247 | where 248 | Self: Sized, 249 | { 250 | Equal(&self.0).params(map) 251 | } 252 | } 253 | 254 | impl<'a> QueryBuilderInjecter<'a> for Equal { 255 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 256 | self.0.inject(querybuilder) 257 | } 258 | 259 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> { 260 | self.0.params(map) 261 | } 262 | } 263 | 264 | impl<'a, Value> QueryBuilderInjecter<'a> for Equal<&[(&str, Value)]> 265 | where 266 | Value: Serialize, 267 | { 268 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 269 | self.0.inject(querybuilder) 270 | } 271 | 272 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 273 | where 274 | Self: Sized, 275 | { 276 | self.0.params(map) 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/types/ext.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt::Display; 3 | 4 | use serde::Serialize; 5 | 6 | use crate::queries::QueryBuilderInjecter; 7 | 8 | pub trait IntoOptionalInjecterExt 9 | where 10 | Self: Sized, 11 | { 12 | /// Helper function to turn the current injecter into a `None` if the passed 13 | /// condition is false, or into `Some(Self)` if the condition is true. 14 | /// 15 | /// # Example 16 | /// ```rs 17 | /// let fetch_author = true; 18 | /// let fetch = Fetch(["author"]).when(fetch_author); 19 | /// 20 | /// // is the equivalent of: 21 | /// let fetch = fetch_author.then(|| Some(Fetch(["author"]))); 22 | /// ``` 23 | fn when(self, condition: bool) -> Option; 24 | } 25 | 26 | impl<'a, Injecters> IntoOptionalInjecterExt for Injecters 27 | where 28 | Injecters: QueryBuilderInjecter<'a>, 29 | { 30 | fn when(self, condition: bool) -> Option { 31 | match condition { 32 | true => Some(self), 33 | false => None, 34 | } 35 | } 36 | } 37 | 38 | //////////////////////////////////////////////////////////////////////////////// 39 | /// Function to easily serialize+flatten serializable types into injecters that 40 | /// accept the `Value` type 41 | 42 | #[derive(Debug)] 43 | pub enum FlattenSerializeError { 44 | Serialize(serde_json::Error), 45 | Flatten(flatten_json_object::Error), 46 | } 47 | 48 | impl Display for FlattenSerializeError { 49 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 50 | match self { 51 | FlattenSerializeError::Serialize(e) => e.fmt(f), 52 | FlattenSerializeError::Flatten(e) => e.fmt(f), 53 | } 54 | } 55 | } 56 | 57 | impl Error for FlattenSerializeError { 58 | fn source(&self) -> Option<&(dyn Error + 'static)> { 59 | match self { 60 | FlattenSerializeError::Serialize(e) => e.source(), 61 | FlattenSerializeError::Flatten(e) => e.source(), 62 | } 63 | } 64 | } 65 | 66 | impl From for FlattenSerializeError { 67 | fn from(value: serde_json::Error) -> Self { 68 | Self::Serialize(value) 69 | } 70 | } 71 | 72 | impl From for FlattenSerializeError { 73 | fn from(value: flatten_json_object::Error) -> Self { 74 | Self::Flatten(value) 75 | } 76 | } 77 | 78 | pub fn flatten_serialize( 79 | value: impl Serialize, 80 | ) -> std::result::Result { 81 | let value = serde_json::to_value(value)?; 82 | let flattened = flatten_json_object::Flattener::new() 83 | .set_key_separator(".") 84 | .flatten(&value)?; 85 | 86 | Ok(flattened) 87 | } 88 | -------------------------------------------------------------------------------- /src/types/fetch.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::QueryBuilder; 2 | use crate::prelude::QueryBuilderInjecter; 3 | 4 | pub struct Fetch(pub T); 5 | 6 | impl<'a> QueryBuilderInjecter<'a> for Fetch<&'a str> { 7 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 8 | querybuilder.fetch(self.0) 9 | } 10 | } 11 | 12 | impl<'a, const N: usize> QueryBuilderInjecter<'a> for Fetch<[&'a str; N]> { 13 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 14 | querybuilder.fetch_many(&self.0) 15 | } 16 | } 17 | 18 | impl<'a> QueryBuilderInjecter<'a> for Fetch<&[&'a str]> { 19 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 20 | querybuilder.fetch_many(&self.0) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/types/filter.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::QueryBuilder; 2 | use crate::prelude::QueryBuilderInjecter; 3 | use crate::queries::BindingMap; 4 | 5 | /// Add a WHERE clause to the query, the `Where` type is made to accept anything 6 | /// that implements the [QueryBuilderInjecter] trait, meaning any of the injecter 7 | /// types that come with the crate or your own, or a string. 8 | /// 9 | /// # Examples 10 | /// ```rs 11 | /// // a single field: 12 | /// let filter = Where(("username", "John")); 13 | /// 14 | /// // if you use the `model` macro: 15 | /// let filter = Where((schema.username, "John")); 16 | /// 17 | /// // a raw string: 18 | /// let filter = Where("username = 'john'"); 19 | /// ``` 20 | /// 21 | /// ```rs 22 | /// // multiple fields: 23 | /// let filter = Where(json!({ "username": "John", schema.slug: "john-doe" })); 24 | /// 25 | /// // or using the shorter alias macro: 26 | /// let filter = wjson!({ "username": "John", schema.slug: "john-doe" }); 27 | /// ``` 28 | /// 29 | /// # Note 30 | /// Both the json macro (that results in a `serde_json::Value`) and 31 | /// the tuple can work when inside a `Where` because they both work the same way 32 | /// as the [Equal] injecter. In the same style, passing an `Option` as the value 33 | /// can be used to pass an optional filter, where the whole key/value pair will 34 | /// be ignored on a `None` 35 | pub struct Where(pub T); 36 | 37 | /// An alias macro for 38 | /// ```rs 39 | /// Where(json!({ foo: bar })); 40 | /// ``` 41 | /// 42 | /// # Example 43 | /// ```rs 44 | /// let filter = wjson!({ foo: bar }); 45 | /// ``` 46 | #[macro_export] 47 | macro_rules! wjson { 48 | ($($json:tt)+) => { 49 | surreal_simple_querybuilder::types::Where(surreal_simple_querybuilder::serde_json::json!($($json)+)) 50 | }; 51 | } 52 | 53 | impl<'a, T: QueryBuilderInjecter<'a>> QueryBuilderInjecter<'a> for Where { 54 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 55 | querybuilder.filter("").ands(|q| self.0.inject(q)) 56 | } 57 | 58 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> { 59 | self.0.params(map) 60 | } 61 | } 62 | 63 | impl<'a, Own> Where 64 | where 65 | Own: QueryBuilderInjecter<'a>, 66 | { 67 | pub fn extend(self, other: Other) -> Where<(Own, Other)> 68 | where 69 | Other: QueryBuilderInjecter<'a>, 70 | { 71 | Where((self.0, other)) 72 | } 73 | 74 | pub fn extend_on( 75 | self, condition: bool, other: Other, 76 | ) -> Where<(Own, Option)> 77 | where 78 | Other: QueryBuilderInjecter<'a>, 79 | Output: QueryBuilderInjecter<'a>, 80 | { 81 | match condition { 82 | true => Where((self.0, Some(other))), 83 | false => Where((self.0, None)), 84 | } 85 | } 86 | } 87 | 88 | impl<'a> QueryBuilderInjecter<'a> for Where<&'a str> { 89 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 90 | querybuilder.and_where(self.0) 91 | } 92 | 93 | fn params(self, _map: &mut crate::queries::BindingMap) -> serde_json::Result<()> 94 | where 95 | Self: Sized, 96 | { 97 | Ok(()) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/types/from.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::QueryBuilder; 2 | use crate::prelude::QueryBuilderInjecter; 3 | 4 | /// Appends a `FROM` clause 5 | /// 6 | /// # Example 7 | /// ``` 8 | /// use surreal_simple_querybuilder::prelude::*; 9 | /// 10 | /// let param = (Select("*"), From("users")); 11 | /// assert_eq!(query(¶m).unwrap(), "SELECT * FROM users"); 12 | /// ``` 13 | pub struct From(pub &'static str); 14 | 15 | impl<'a> QueryBuilderInjecter<'a> for From { 16 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 17 | querybuilder.from(self.0) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/types/greater.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use serde::Serialize; 4 | use serde_json::Value; 5 | 6 | use crate::prelude::QueryBuilder; 7 | use crate::prelude::QueryBuilderInjecter; 8 | use crate::prelude::ToNodeBuilder; 9 | use crate::queries::BindingMap; 10 | 11 | use super::ser_to_param_value; 12 | 13 | /// ``` 14 | /// use surreal_simple_querybuilder::prelude::*; 15 | /// 16 | /// let param = Where(Greater(("id", 5))); 17 | /// let (query, params) = select("*", "users", param).unwrap(); 18 | /// 19 | /// assert_eq!(query, "SELECT * FROM users WHERE id > $id"); 20 | /// assert_eq!(params.get("id"), Some(&serde_json::json!(5))); 21 | /// ``` 22 | pub struct Greater(pub T); 23 | 24 | /// Base functions for all implementations of the `QueryBuilderInjecter` trait 25 | impl Greater<()> { 26 | fn greater_inject<'a>( 27 | mut querybuilder: QueryBuilder<'a>, key: &impl ToNodeBuilder, 28 | ) -> QueryBuilder<'a> { 29 | querybuilder.add_segment(key.greater_parameterized()); 30 | 31 | querybuilder 32 | } 33 | 34 | fn greater_params( 35 | map: &mut BindingMap, key: &impl ToNodeBuilder, value: impl Serialize, 36 | ) -> serde_json::Result<()> { 37 | map.insert(key.as_param(), ser_to_param_value(value)?); 38 | 39 | Ok(()) 40 | } 41 | } 42 | 43 | impl<'a, Key, Value> QueryBuilderInjecter<'a> for Greater<&(Key, Value)> 44 | where 45 | Key: ToNodeBuilder, 46 | Value: Serialize, 47 | { 48 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 49 | Greater::greater_inject(querybuilder, &self.0 .0) 50 | } 51 | 52 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 53 | where 54 | Self: Sized, 55 | { 56 | Greater::greater_params(map, &self.0 .0, &self.0 .1) 57 | } 58 | } 59 | 60 | impl<'a, Key, Value> QueryBuilderInjecter<'a> for Greater<(Key, Value)> 61 | where 62 | Key: ToNodeBuilder + Display, 63 | Value: Serialize, 64 | { 65 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 66 | Greater(&self.0).inject(querybuilder) 67 | } 68 | 69 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 70 | where 71 | Self: Sized, 72 | { 73 | Greater(&self.0).params(map) 74 | } 75 | } 76 | 77 | impl<'a> QueryBuilderInjecter<'a> for Greater { 78 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 79 | if let Some(map) = self.0.as_object() { 80 | return map 81 | .keys() 82 | .fold(querybuilder, |q, key| Greater::greater_inject(q, key)); 83 | } 84 | 85 | querybuilder 86 | } 87 | 88 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> { 89 | self.0.params(map) 90 | } 91 | } 92 | 93 | impl<'a, Value> QueryBuilderInjecter<'a> for Greater<&[(&str, Value)]> 94 | where 95 | Value: Serialize, 96 | { 97 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 98 | self.0.inject(querybuilder) 99 | } 100 | 101 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 102 | where 103 | Self: Sized, 104 | { 105 | self.0.params(map) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/types/limit.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::QueryBuilder; 2 | use crate::prelude::QueryBuilderInjecter; 3 | 4 | /// Dynamically add a limit statement to the query. 5 | /// ```rs 6 | /// Limit(10); 7 | /// Limit("10"); 8 | /// ``` 9 | /// 10 | /// **Note:** If you know the limit value at compile time prefer a 11 | /// `&'static str` over a `u64` to avoid an unnecessary `to_string()` call. 12 | /// 13 | pub struct Limit(pub T); 14 | 15 | impl<'a> QueryBuilderInjecter<'a> for Limit<&'a str> { 16 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 17 | querybuilder.limit(self.0) 18 | } 19 | } 20 | 21 | impl<'a> QueryBuilderInjecter<'a> for Limit { 22 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 23 | querybuilder.limit(self.0.to_string()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/types/lower.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use serde::Serialize; 4 | use serde_json::Value; 5 | 6 | use crate::prelude::QueryBuilder; 7 | use crate::prelude::QueryBuilderInjecter; 8 | use crate::prelude::ToNodeBuilder; 9 | use crate::queries::BindingMap; 10 | 11 | use super::ser_to_param_value; 12 | 13 | /// ``` 14 | /// use surreal_simple_querybuilder::prelude::*; 15 | /// 16 | /// let param = Where(Lower(("id", 5))); 17 | /// let (query, params) = select("*", "users", param).unwrap(); 18 | /// 19 | /// assert_eq!(query, "SELECT * FROM users WHERE id < $id"); 20 | /// assert_eq!(params.get("id"), Some(&serde_json::json!(5))); 21 | /// ``` 22 | pub struct Lower(pub T); 23 | 24 | /// Base functions for all implementations of the `QueryBuilderInjecter` trait 25 | impl Lower<()> { 26 | fn lower_inject<'a>( 27 | mut querybuilder: QueryBuilder<'a>, key: &impl ToNodeBuilder, 28 | ) -> QueryBuilder<'a> { 29 | querybuilder.add_segment(key.lower_parameterized()); 30 | 31 | querybuilder 32 | } 33 | 34 | fn lower_params( 35 | map: &mut BindingMap, key: &impl ToNodeBuilder, value: impl Serialize, 36 | ) -> serde_json::Result<()> { 37 | map.insert(key.as_param(), ser_to_param_value(value)?); 38 | 39 | Ok(()) 40 | } 41 | } 42 | 43 | impl<'a, Key, Value> QueryBuilderInjecter<'a> for Lower<&(Key, Value)> 44 | where 45 | Key: ToNodeBuilder, 46 | Value: Serialize, 47 | { 48 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 49 | Lower::lower_inject(querybuilder, &self.0 .0) 50 | } 51 | 52 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 53 | where 54 | Self: Sized, 55 | { 56 | Lower::lower_params(map, &self.0 .0, &self.0 .1) 57 | } 58 | } 59 | 60 | impl<'a, Key, Value> QueryBuilderInjecter<'a> for Lower<(Key, Value)> 61 | where 62 | Key: ToNodeBuilder + Display, 63 | Value: Serialize, 64 | { 65 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 66 | Lower(&self.0).inject(querybuilder) 67 | } 68 | 69 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 70 | where 71 | Self: Sized, 72 | { 73 | Lower(&self.0).params(map) 74 | } 75 | } 76 | 77 | impl<'a> QueryBuilderInjecter<'a> for Lower { 78 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 79 | if let Some(map) = self.0.as_object() { 80 | return map 81 | .keys() 82 | .fold(querybuilder, |q, key| Lower::lower_inject(q, key)); 83 | } 84 | 85 | querybuilder 86 | } 87 | 88 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> { 89 | self.0.params(map) 90 | } 91 | } 92 | 93 | impl<'a, Value> QueryBuilderInjecter<'a> for Lower<&[(&str, Value)]> 94 | where 95 | Value: Serialize, 96 | { 97 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 98 | self.0.inject(querybuilder) 99 | } 100 | 101 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 102 | where 103 | Self: Sized, 104 | { 105 | self.0.params(map) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/types/mod.rs: -------------------------------------------------------------------------------- 1 | //! "Injecters" or "dynamic parameters" are types that allow you to build queries 2 | //! in a fully composable manner via tuples. Each SQL clause or statement has its 3 | //! injecter type that you can return or pass around as parameters in your functions. 4 | //! 5 | //! ``` 6 | //! use surreal_simple_querybuilder::prelude::*; 7 | //! 8 | //! assert_eq!( 9 | //! query(&(Select("*"), From("users"), Where("id = 5"))).unwrap(), 10 | //! "SELECT * FROM users WHERE id = 5" 11 | //! ); 12 | //! ``` 13 | //! 14 | //! Another strength of the injecters is their ability to turn the parts that are 15 | //! usually sensitive to XSS injections. For example: 16 | //! ``` 17 | //! use surreal_simple_querybuilder::prelude::*; 18 | //! use serde_json::json; 19 | //! 20 | //! let injecters = ( 21 | //! Select("*"), 22 | //! From("users"), 23 | //! Where(And(( 24 | //! ("id", 5), 25 | //! ("name", "john") 26 | //! ))) 27 | //! ); 28 | //! 29 | //! let query_string = query(&injecters).unwrap(); 30 | //! let query_bindings = bindings(injecters).unwrap(); 31 | //! 32 | //! assert_eq!(query_string, "SELECT * FROM users WHERE id = $id AND name = $name"); 33 | //! assert_eq!(query_bindings.get("id"), Some(&json!(5))); 34 | //! assert_eq!(query_bindings.get("name"), Some(&json!("john"))); 35 | //! ``` 36 | //! 37 | //! --- 38 | //! 39 | //! The first scenario that comes to mind is a standard function to retrieve books 40 | //! by the author: 41 | //! ```rust 42 | //! impl Book { 43 | //! fn find_by_author_id(id: &str) -> Vec { 44 | //! // ... 45 | //! } 46 | //!} 47 | //!``` 48 | //! 49 | //! In some cases you'll need the list of books and nothing else, another time you'll need 50 | //! the results to be paginated, and sometimes you'll want to fetch the author data 51 | //! on top of the books. Considering you may also want to have the books with both pagination 52 | //! and fetch this could potentially result in at least 4 different functions & queries 53 | //! to write. 54 | //! 55 | //! With the dynamic parameters you can update your `find` function to accept optional 56 | //! parameters so that only 1 simple function is needed: 57 | //! 58 | //! ```rust 59 | //! use serde_json::json; 60 | //! 61 | //! impl Book { 62 | //! fn find_by_author_id<'a>(id: &str, params: impl QueryBuilderInjecter<'a> + 'a) -> Vec { 63 | //! let filter = Where(json!({"author": id})); 64 | //! let combined_params = (filter, params); 65 | //! 66 | //! let (query, params) = select("*", "Book", combined_params).unwrap(); 67 | //! 68 | //! DB.query(query) 69 | //! .bind(params) 70 | //! .await.unwrap() 71 | //! .get(..).unwrap() 72 | //! } 73 | //! } 74 | //! ``` 75 | //! So you can now do: 76 | //! ```rust 77 | //! let books = Book::find_by_author_id("User:john", ()); 78 | //! let paginated_books = Book::find_by_author_id("User:john", Pagination(0..25)); 79 | //! let paginated_books_with_author_data = Book::find_by_author_id( 80 | //! "User:john", 81 | //! ( 82 | //! Pagination(0..25), 83 | //! Fetch(["author"]) 84 | //! ) 85 | //! ); 86 | //! ``` 87 | //! 88 | //! You can read more about the injecters in the crates README and its guided examples. 89 | #[test] 90 | fn test_injecters_doccomment() { 91 | assert_eq!( 92 | crate::queries::query(&(Select("*"), From("users"), Where("id = 5"))).unwrap(), 93 | "SELECT * FROM users WHERE id = 5" 94 | ); 95 | 96 | use serde_json::json; 97 | 98 | let injecters = ( 99 | Select("*"), 100 | From("users"), 101 | Where(And((("id", 5), ("name", "john")))), 102 | ); 103 | 104 | let query_string = crate::queries::query(&injecters).unwrap(); 105 | let query_bindings = crate::queries::bindings(injecters).unwrap(); 106 | 107 | assert_eq!( 108 | query_string, 109 | "SELECT * FROM users WHERE id = $id AND name = $name" 110 | ); 111 | assert_eq!(query_bindings.get("id"), Some(&json!(5))); 112 | assert_eq!(query_bindings.get("name"), Some(&json!("john"))); 113 | } 114 | 115 | mod also; 116 | mod and; 117 | mod bind; 118 | mod build; 119 | mod cmp; 120 | mod create; 121 | mod delete; 122 | mod equal; 123 | mod ext; 124 | mod fetch; 125 | mod filter; 126 | mod from; 127 | mod greater; 128 | mod limit; 129 | mod lower; 130 | mod or; 131 | mod order_by; 132 | mod pagination; 133 | mod plus_equal; 134 | mod select; 135 | mod set; 136 | mod sql; 137 | mod update; 138 | 139 | pub use also::Also; 140 | pub use and::And; 141 | pub use bind::Bind; 142 | pub use build::Build; 143 | pub use cmp::Cmp; 144 | pub use create::Create; 145 | pub use delete::Delete; 146 | pub use equal::Equal; 147 | pub use ext::*; 148 | pub use fetch::Fetch; 149 | pub use filter::Where; 150 | pub use from::From; 151 | pub use greater::Greater; 152 | pub use limit::Limit; 153 | pub use lower::Lower; 154 | pub use or::Or; 155 | pub use order_by::OrderAsc; 156 | pub use order_by::OrderBy; 157 | pub use order_by::OrderDesc; 158 | pub use pagination::Pagination; 159 | pub use plus_equal::PlusEqual; 160 | pub use select::Select; 161 | pub use set::Set; 162 | pub use sql::Sql; 163 | pub use update::Update; 164 | 165 | mod on; 166 | #[cfg(feature = "sql_standard")] 167 | pub use on::On; 168 | 169 | pub(crate) fn to_param_value(value: serde_json::Value) -> serde_json::Result { 170 | Ok(value) 171 | } 172 | 173 | /// An internal function made public so the macro generate code can use it. Allows 174 | /// the macro code to use serde_json functions without the parent crates to import 175 | /// the serde_json crate directly. 176 | pub fn ser_to_param_value(value: T) -> serde_json::Result { 177 | to_param_value(serde_json::to_value(value)?) 178 | } 179 | -------------------------------------------------------------------------------- /src/types/on.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::QueryBuilder; 2 | use crate::prelude::QueryBuilderInjecter; 3 | 4 | /// # Example 5 | /// ``` 6 | /// use surreal_simple_querybuilder::prelude::*; 7 | /// 8 | /// let param = On(("users.id", Sql("permissions.user_id"))); 9 | /// let (query, params) = select("*", "users INNER JOIN permissions", param).unwrap(); 10 | /// 11 | /// assert_eq!(query, "SELECT * FROM users INNER JOIN permissions ON users.id = permissions.user_id"); 12 | /// ``` 13 | pub struct On(pub T); 14 | 15 | impl<'a, T> QueryBuilderInjecter<'a> for On 16 | where 17 | T: QueryBuilderInjecter<'a>, 18 | { 19 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 20 | self.0.inject(querybuilder.on("")) 21 | } 22 | 23 | fn params(self, map: &mut crate::queries::BindingMap) -> serde_json::Result<()> 24 | where 25 | Self: Sized, 26 | { 27 | self.0.params(map) 28 | } 29 | } 30 | 31 | impl<'a> QueryBuilderInjecter<'a> for On<&'a str> { 32 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 33 | querybuilder.on(self.0) 34 | } 35 | 36 | fn params(self, _map: &mut crate::queries::BindingMap) -> serde_json::Result<()> 37 | where 38 | Self: Sized, 39 | { 40 | Ok(()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/types/or.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::QueryBuilder; 2 | use crate::prelude::QueryBuilderInjecter; 3 | 4 | /// Appends a `OR` statement followed by anything inside the [Or]. 5 | /// If the [Or] contains multiple items then `OR`s will be added **between** them 6 | /// but not in front. 7 | /// 8 | /// # Example 9 | /// ``` 10 | /// use surreal_simple_querybuilder::prelude::*; 11 | /// 12 | /// let filter = Where("id = 5"); 13 | /// let q = query(&(filter, Or("id = 10"))).unwrap(); 14 | /// assert_eq!(q, "WHERE id = 5 OR id = 10"); 15 | /// 16 | /// // passing multiple items to Or tells it to no longer append a OR in front 17 | /// // but between the items: 18 | /// let filter = Where(( 19 | /// Or(( // <- notice the Or is INSIDE the Where, as OR is added between the items now 20 | /// ("id", Sql("5")), 21 | /// ("id", Sql("10")), 22 | /// ("id", Sql("15")) 23 | /// )) 24 | /// )); 25 | /// 26 | /// let q = query(&filter).unwrap(); 27 | /// assert_eq!(q, "WHERE id = 5 OR id = 10 OR id = 15"); 28 | /// ``` 29 | pub struct Or(pub T); 30 | 31 | impl<'a, T> QueryBuilderInjecter<'a> for Or 32 | where 33 | T: QueryBuilderInjecter<'a>, 34 | { 35 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 36 | querybuilder.ors(|q| self.0.inject(q)) 37 | } 38 | 39 | fn params(self, map: &mut crate::queries::BindingMap) -> serde_json::Result<()> 40 | where 41 | Self: Sized, 42 | { 43 | self.0.params(map) 44 | } 45 | } 46 | 47 | impl<'a> QueryBuilderInjecter<'a> for Or<&'a str> { 48 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 49 | querybuilder.or(self.0) 50 | } 51 | 52 | fn params(self, _map: &mut crate::queries::BindingMap) -> serde_json::Result<()> 53 | where 54 | Self: Sized, 55 | { 56 | Ok(()) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/types/order_by.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::QueryBuilder; 2 | use crate::prelude::QueryBuilderInjecter; 3 | 4 | /// Appends a `ORDER BY` clause that is either ASC or DESC. 5 | /// 6 | /// # Example 7 | /// ``` 8 | /// use surreal_simple_querybuilder::prelude::*; 9 | /// 10 | /// let param = OrderBy(OrderDesc, "created_at"); 11 | /// let q = query(¶m).unwrap(); 12 | /// assert_eq!(q, "ORDER BY created_at DESC"); 13 | /// 14 | /// // you can also use the available methods to construct it: 15 | /// let asc = OrderBy::asc("created_at"); 16 | /// let desc = OrderBy::desc("created_at"); 17 | /// assert_eq!(q, query(&desc).unwrap()); 18 | /// assert_eq!(query(&asc).unwrap(), "ORDER BY created_at ASC"); 19 | /// ``` 20 | pub struct OrderBy(pub Order, pub T); 21 | 22 | pub struct OrderDesc; 23 | pub struct OrderAsc; 24 | 25 | impl OrderBy { 26 | pub fn desc(field: T) -> OrderBy { 27 | Self(OrderDesc, field) 28 | } 29 | } 30 | 31 | impl OrderBy { 32 | pub fn asc(field: T) -> OrderBy { 33 | Self(OrderAsc, field) 34 | } 35 | } 36 | 37 | impl<'a> QueryBuilderInjecter<'a> for OrderBy { 38 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 39 | querybuilder.order_by_desc(self.1) 40 | } 41 | } 42 | impl<'a> QueryBuilderInjecter<'a> for OrderBy { 43 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 44 | querybuilder.order_by_asc(self.1) 45 | } 46 | } 47 | 48 | #[cfg(feature = "model")] 49 | use crate::model::SchemaField; 50 | 51 | #[cfg(feature = "model")] 52 | impl<'a, const N: usize> QueryBuilderInjecter<'a> for OrderBy> { 53 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 54 | querybuilder.order_by_desc(self.1.to_string()) 55 | } 56 | } 57 | #[cfg(feature = "model")] 58 | impl<'a, const N: usize> QueryBuilderInjecter<'a> for OrderBy> { 59 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 60 | querybuilder.order_by_asc(self.1.to_string()) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/types/pagination.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | use crate::prelude::QueryBuilder; 4 | use crate::prelude::QueryBuilderInjecter; 5 | 6 | /// Declare a LIMIT and START AT clause that will include the items from the 7 | /// supplied range. The [`Self::new_page(page: u64, page_size: u64)`] function 8 | /// offers an easy way to construct a range with a given page & page size. 9 | /// 10 | /// _The START AT clause is omitted if the left side of the range is lower or 11 | /// equal than 0._ 12 | pub struct Pagination(pub Range); 13 | 14 | impl From> for Pagination { 15 | fn from(value: Range) -> Self { 16 | Pagination(value) 17 | } 18 | } 19 | 20 | impl Pagination { 21 | pub fn new(range: Range) -> Self { 22 | Pagination(range) 23 | } 24 | 25 | /// Create a new [Pagination] for the given `page` where each page contains 26 | /// `page_size` elements. This function assumes the pagination starts at page 27 | /// 0, so `Pagination::new_page(1, 20)` is the second page with elements from 28 | /// the `20..40` range. 29 | pub fn new_page(page: u64, page_size: u64) -> Self { 30 | Self::new(page * page_size..(page + 1) * page_size) 31 | } 32 | 33 | pub fn limit(&self) -> u64 { 34 | self.0.end - self.0.start 35 | } 36 | 37 | pub fn start(&self) -> u64 { 38 | self.0.start 39 | } 40 | } 41 | 42 | impl<'a> QueryBuilderInjecter<'a> for Pagination { 43 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 44 | let start = self.start(); 45 | 46 | querybuilder 47 | .limit(self.limit().to_string()) 48 | .if_then(start > 0, |q| q.start_at(start.to_string())) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/types/plus_equal.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use serde::Serialize; 4 | use serde_json::Value; 5 | 6 | use crate::prelude::QueryBuilder; 7 | use crate::prelude::QueryBuilderInjecter; 8 | use crate::prelude::ToNodeBuilder; 9 | use crate::queries::BindingMap; 10 | 11 | use super::ser_to_param_value; 12 | 13 | /// ``` 14 | /// use surreal_simple_querybuilder::prelude::*; 15 | /// 16 | /// let param = Set(PlusEqual(("read_count", 1))); 17 | /// let (query, params) = update("articles", param).unwrap(); 18 | /// 19 | /// assert_eq!(query, "UPDATE articles SET read_count += $read_count"); 20 | /// assert_eq!(params.get("read_count"), Some(&serde_json::json!(1))); 21 | /// ``` 22 | pub struct PlusEqual(pub T); 23 | 24 | /// Base functions for all implementations of the `QueryBuilderInjecter` trait 25 | impl PlusEqual<()> { 26 | fn plusequal_inject<'a>( 27 | mut querybuilder: QueryBuilder<'a>, key: &impl ToNodeBuilder, 28 | ) -> QueryBuilder<'a> { 29 | querybuilder.add_segment(key.plus_equal_parameterized()); 30 | 31 | querybuilder 32 | } 33 | 34 | fn plusequal_params( 35 | map: &mut BindingMap, key: &impl ToNodeBuilder, value: impl Serialize, 36 | ) -> serde_json::Result<()> { 37 | map.insert(key.as_param(), ser_to_param_value(value)?); 38 | 39 | Ok(()) 40 | } 41 | } 42 | 43 | impl<'a, Key, Value> QueryBuilderInjecter<'a> for PlusEqual<&(Key, Value)> 44 | where 45 | Key: ToNodeBuilder, 46 | Value: Serialize, 47 | { 48 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 49 | PlusEqual::plusequal_inject(querybuilder, &self.0 .0) 50 | } 51 | 52 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 53 | where 54 | Self: Sized, 55 | { 56 | PlusEqual::plusequal_params(map, &self.0 .0, &self.0 .1) 57 | } 58 | } 59 | 60 | impl<'a, Key, Value> QueryBuilderInjecter<'a> for PlusEqual<(Key, Value)> 61 | where 62 | Key: ToNodeBuilder + Display, 63 | Value: Serialize, 64 | { 65 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 66 | PlusEqual(&self.0).inject(querybuilder) 67 | } 68 | 69 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 70 | where 71 | Self: Sized, 72 | { 73 | PlusEqual(&self.0).params(map) 74 | } 75 | } 76 | 77 | impl<'a> QueryBuilderInjecter<'a> for PlusEqual { 78 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 79 | if let Some(map) = self.0.as_object() { 80 | return map 81 | .keys() 82 | .fold(querybuilder, |q, key| PlusEqual::plusequal_inject(q, key)); 83 | } 84 | 85 | querybuilder 86 | } 87 | 88 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> { 89 | self.0.params(map) 90 | } 91 | } 92 | 93 | impl<'a, Value> QueryBuilderInjecter<'a> for PlusEqual<&[(&str, Value)]> 94 | where 95 | Value: Serialize, 96 | { 97 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 98 | self.0.inject(querybuilder) 99 | } 100 | 101 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> 102 | where 103 | Self: Sized, 104 | { 105 | self.0.params(map) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/types/select.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::QueryBuilder; 2 | use crate::prelude::QueryBuilderInjecter; 3 | 4 | /// Appends a `SELECT` clause 5 | /// 6 | /// # Example 7 | /// ``` 8 | /// use surreal_simple_querybuilder::prelude::*; 9 | /// 10 | /// let param = (Select("*"), From("users")); 11 | /// assert_eq!(query(¶m).unwrap(), "SELECT * FROM users"); 12 | /// ``` 13 | pub struct Select(pub &'static str); 14 | 15 | impl<'a> QueryBuilderInjecter<'a> for Select { 16 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 17 | querybuilder.select(self.0) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/types/set.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::QueryBuilder; 2 | use crate::prelude::QueryBuilderInjecter; 3 | use crate::queries::BindingMap; 4 | 5 | /// ``` 6 | /// use surreal_simple_querybuilder::prelude::*; 7 | /// use serde_json::json; 8 | /// let filter = Set(json!({ "name": "John", "age": 10 })); 9 | /// let (query, params) = update("User", filter).unwrap(); 10 | /// 11 | /// assert_eq!("UPDATE User SET name = $name , age = $age", query); 12 | /// assert_eq!(params.get("name"), Some(&json!("John")) ); 13 | /// assert_eq!(params.get("age"), Some(&json!(10)) ); 14 | /// ``` 15 | /// ``` 16 | /// use surreal_simple_querybuilder::prelude::*; 17 | /// 18 | /// let param = Set(( 19 | /// PlusEqual(("read_count", 1)), 20 | /// ("last_read", Sql("now()")) 21 | /// )); 22 | /// let (query, params) = update("articles", param).unwrap(); 23 | /// 24 | /// assert_eq!(query, "UPDATE articles SET read_count += $read_count , last_read = now()"); 25 | /// assert_eq!(params.get("read_count"), Some(&serde_json::json!(1))); 26 | /// ``` 27 | pub struct Set(pub T); 28 | 29 | impl<'a, T: QueryBuilderInjecter<'a>> QueryBuilderInjecter<'a> for Set { 30 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 31 | querybuilder.set("").commas(|q| self.0.inject(q)) 32 | } 33 | 34 | fn params(self, map: &mut BindingMap) -> serde_json::Result<()> { 35 | self.0.params(map) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/types/sql.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::QueryBuilder; 2 | use crate::prelude::QueryBuilderInjecter; 3 | use crate::queries::BindingMap; 4 | 5 | /// Acts as a way to send raw unaltered SQL as an injecter. It is the same as 6 | /// doing 7 | /// ```rs 8 | /// QueryBuilder.raw("my string") 9 | /// ``` 10 | pub struct Sql(pub T); 11 | 12 | impl<'a> QueryBuilderInjecter<'a> for Sql<&'a str> { 13 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 14 | querybuilder.raw(self.0) 15 | } 16 | 17 | fn params(self, _map: &mut BindingMap) -> serde_json::Result<()> { 18 | Ok(()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/types/update.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::QueryBuilder; 2 | use crate::prelude::QueryBuilderInjecter; 3 | 4 | /// Appends a `UPDATE` clause 5 | /// 6 | /// # Example 7 | /// ``` 8 | /// use surreal_simple_querybuilder::prelude::*; 9 | /// 10 | /// let param = (Update("users"), Where(("id", 5))); 11 | /// assert_eq!(query(¶m).unwrap(), "UPDATE users WHERE id = $id"); 12 | /// ``` 13 | pub struct Update(pub T); 14 | 15 | impl<'a> QueryBuilderInjecter<'a> for Update<&'a str> { 16 | fn inject(&self, querybuilder: QueryBuilder<'a>) -> QueryBuilder<'a> { 17 | querybuilder.update(self.0) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/foreign.rs: -------------------------------------------------------------------------------- 1 | #![allow(incomplete_features)] 2 | #![feature(generic_const_exprs)] 3 | 4 | #[test] 5 | #[cfg(feature = "foreign")] 6 | fn foreign_key_impl_eq() { 7 | use surreal_simple_querybuilder::prelude::*; 8 | 9 | #[derive(Clone, PartialEq, Debug)] 10 | struct TestA(pub &'static str); 11 | 12 | #[derive(Clone, Debug, PartialEq)] 13 | struct TestB { 14 | field: Foreign, 15 | } 16 | 17 | // 0. 18 | // confirm comparison works as expected for a value vs a key 19 | assert_ne!( 20 | TestB { 21 | field: Foreign::new_value(TestA("lorem")), 22 | }, 23 | TestB { 24 | field: Foreign::new_key("key".to_owned()), 25 | } 26 | ); 27 | 28 | // 1. 29 | // confirm comparison works as expected for two different values 30 | assert_ne!( 31 | TestB { 32 | field: Foreign::new_value(TestA("lorem")), 33 | }, 34 | TestB { 35 | field: Foreign::new_value(TestA("ipsum")), 36 | } 37 | ); 38 | 39 | // 2. 40 | // confirm comparison works as expected for two identical values 41 | assert_eq!( 42 | TestB { 43 | field: Foreign::new_value(TestA("lorem")), 44 | }, 45 | TestB { 46 | field: Foreign::new_value(TestA("lorem")), 47 | } 48 | ); 49 | 50 | // 3. 51 | // confirm comparison works as expected for two identical keys 52 | assert_eq!( 53 | TestB { 54 | field: Foreign::new_key("key".to_owned()) 55 | }, 56 | TestB { 57 | field: Foreign::new_key("key".to_owned()) 58 | } 59 | ); 60 | 61 | // 4. 62 | // confirm two unloaded values are considered equal 63 | assert_eq!( 64 | TestB { 65 | field: Foreign::new() 66 | }, 67 | TestB { 68 | field: Foreign::new() 69 | } 70 | ); 71 | } 72 | 73 | /// This test is more of a "failsafe" just to ensure the Clone implementation is 74 | /// not removed from the ForeignKey type by mistake. 75 | /// 76 | /// ... It also relies heavily on the implementation of Eq 77 | #[test] 78 | #[cfg(feature = "foreign")] 79 | fn foreign_key_impl_clone() { 80 | use surreal_simple_querybuilder::prelude::*; 81 | 82 | #[derive(Clone, PartialEq, Debug)] 83 | struct TestA(pub &'static str); 84 | 85 | #[derive(Clone, Debug, PartialEq)] 86 | struct TestB { 87 | field: Foreign, 88 | } 89 | 90 | let original = TestB { 91 | field: Foreign::new_value(TestA("lorem")), 92 | }; 93 | 94 | let cloned = original.clone(); 95 | 96 | assert_eq!(original, cloned); 97 | } 98 | -------------------------------------------------------------------------------- /tests/model.rs: -------------------------------------------------------------------------------- 1 | #![allow(incomplete_features)] 2 | #![feature(generic_const_exprs)] 3 | 4 | #[cfg(feature = "querybuilder")] 5 | #[cfg(feature = "queries")] 6 | #[cfg(feature = "model")] 7 | #[cfg(feature = "foreign")] 8 | 9 | mod one { 10 | use serde::Serialize; 11 | 12 | surreal_simple_querybuilder::model!(TestModel0 as model_base with(partial) { 13 | id, 14 | }); 15 | } 16 | 17 | #[cfg(feature = "querybuilder")] 18 | #[cfg(feature = "queries")] 19 | #[cfg(feature = "model")] 20 | #[cfg(feature = "foreign")] 21 | mod two { 22 | use super::one::model_base::TestModel0; 23 | 24 | surreal_simple_querybuilder::model!(TestModel1 { 25 | id, 26 | pub r#in, 27 | pub other, 28 | pub ->relation->TestModel0 as r#for 29 | }); 30 | 31 | #[test] 32 | fn test_string_literal() { 33 | assert_eq!(schema::model.r#in.to_string(), "in"); 34 | assert_eq!(schema::model.r#for.to_string(), "->relation->TestModel0"); 35 | 36 | assert_eq!( 37 | serde_json::to_string(&schema::model.r#in).unwrap(), 38 | "\"in\"" 39 | ); 40 | 41 | let filter = surreal_simple_querybuilder::types::Where((schema::model.r#in, "some_value")); 42 | let (query, params) = 43 | surreal_simple_querybuilder::queries::select("*", "table", filter).unwrap(); 44 | 45 | assert_eq!(query, "SELECT * FROM table WHERE in = $in"); 46 | 47 | assert_eq!( 48 | params.get("in"), 49 | Some(&serde_json::to_value("some_value").unwrap()) 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/query_params.rs: -------------------------------------------------------------------------------- 1 | #![allow(incomplete_features)] 2 | #![feature(generic_const_exprs)] 3 | 4 | #[cfg(feature = "model")] 5 | #[cfg(feature = "queries")] 6 | mod test { 7 | use serde_json::json; 8 | use surreal_simple_querybuilder::model; 9 | use surreal_simple_querybuilder::prelude::*; 10 | 11 | model!(User { id, name, age }); 12 | use schema::model; 13 | 14 | #[test] 15 | fn test_select_fn() { 16 | let (q, _bindings) = select("*", &model, ()).unwrap(); 17 | assert_eq!("SELECT * FROM User", q); 18 | 19 | let (q, _bindings) = select("*", &model, Where(json!({ model.name: "John" }))).unwrap(); 20 | assert_eq!("SELECT * FROM User WHERE name = $name", q); 21 | 22 | let filter = Where(( 23 | json!({ model.name: "John" }), 24 | Greater(json!({ model.age: 10 })), 25 | )); 26 | let (q, _bindings) = select("*", &model, filter).unwrap(); 27 | assert_eq!("SELECT * FROM User WHERE name = $name AND age > $age", q); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/querybuilder.rs: -------------------------------------------------------------------------------- 1 | #![allow(incomplete_features)] 2 | #![feature(generic_const_exprs)] 3 | 4 | #[cfg(feature = "querybuilder")] 5 | #[cfg(feature = "model")] 6 | #[cfg(feature = "queries")] 7 | #[cfg(feature = "foreign")] 8 | mod test { 9 | 10 | use serde::Deserialize; 11 | use serde::Serialize; 12 | 13 | use surreal_simple_querybuilder::prelude::*; 14 | 15 | #[derive(Debug, Serialize, Deserialize, Default)] 16 | struct Account { 17 | id: Option, 18 | handle: String, 19 | password: String, 20 | email: String, 21 | 22 | projects: ForeignVec, 23 | } 24 | 25 | #[derive(Debug, Serialize, Deserialize, Default)] 26 | struct Project { 27 | id: Option, 28 | name: String, 29 | 30 | releases: ForeignVec, 31 | } 32 | 33 | #[derive(Debug, Serialize, Deserialize, Default)] 34 | struct Release { 35 | id: Option, 36 | name: String, 37 | } 38 | 39 | mod release { 40 | use surreal_simple_querybuilder::prelude::*; 41 | 42 | model!(Release { 43 | pub name 44 | }); 45 | } 46 | 47 | mod project { 48 | use super::account::schema::Account; 49 | use super::release::schema::Release; 50 | use surreal_simple_querybuilder::prelude::*; 51 | 52 | model!(Project { 53 | pub name, 54 | 55 | pub ->has->Release as releases, 56 | pub <-manage<-Account as authors 57 | }); 58 | } 59 | 60 | mod account { 61 | use super::project::schema::Project; 62 | use surreal_simple_querybuilder::prelude::*; 63 | 64 | model!(Account { 65 | pub handle, 66 | pub password, 67 | pub email, 68 | friend, 69 | r#in, 70 | 71 | ->manage->Project as managed_projects, 72 | }); 73 | } 74 | 75 | use account::schema::model as account; 76 | use project::schema::model as project; 77 | 78 | #[derive(Debug, Serialize, Deserialize)] 79 | struct File { 80 | name: String, 81 | author: Foreign, 82 | } 83 | 84 | impl IntoKey for Project { 85 | fn into_key(&self) -> Result { 86 | account.r#in; 87 | 88 | self 89 | .id 90 | .as_ref() 91 | .map(String::clone) 92 | .ok_or(IntoKeyError::Custom("The project has no ID")) 93 | } 94 | } 95 | 96 | impl IntoKey for Release { 97 | fn into_key(&self) -> Result { 98 | self 99 | .id 100 | .as_ref() 101 | .map(String::clone) 102 | .ok_or(IntoKeyError::Custom("The release has no ID")) 103 | } 104 | } 105 | 106 | impl IntoKey for Account { 107 | fn into_key(&self) -> Result { 108 | self 109 | .id 110 | .as_ref() 111 | .map(String::clone) 112 | .ok_or(IntoKeyError::Custom("The account has no ID")) 113 | } 114 | } 115 | 116 | #[test] 117 | fn test_create_account_query() { 118 | let query = QueryBuilder::new() 119 | .create(account.handle.as_named_label(&account.to_string())) 120 | .set_model(&account) 121 | .unwrap() 122 | .build(); 123 | 124 | assert_eq!( 125 | query, 126 | "CREATE Account:handle SET handle = $handle , password = $password , email = $email" 127 | ); 128 | } 129 | 130 | #[test] 131 | fn test_account_find_query() { 132 | let query = QueryBuilder::new() 133 | .select("*") 134 | .from(account) 135 | .filter(account.email.equals_parameterized()) 136 | .build(); 137 | 138 | assert_eq!(query, "SELECT * FROM Account WHERE email = $email"); 139 | } 140 | 141 | #[test] 142 | pub fn test_nodebuilder_relation() { 143 | let s = "Account".with("IS_FRIEND").with("Account:Mark").to_owned(); 144 | 145 | assert_eq!("Account->IS_FRIEND->Account:Mark", s); 146 | } 147 | 148 | #[test] 149 | pub fn test_nodebuilder_condition() { 150 | let should_be_friend_with_mark = true; 151 | let should_be_friend_with_john = false; 152 | 153 | let s = String::new() 154 | .with("IS_FRIEND") 155 | .if_then(should_be_friend_with_mark, |s| s.with("Account:Mark")) 156 | .if_then(should_be_friend_with_john, |s| s.with("Account:John")) 157 | .to_owned(); 158 | 159 | assert_eq!("->IS_FRIEND->Account:Mark", s); 160 | } 161 | 162 | #[test] 163 | pub fn test_as_named_label() { 164 | let user_handle = "John"; 165 | let label = user_handle.as_named_label("Account"); 166 | 167 | assert_eq!(label, "Account:John"); 168 | } 169 | 170 | #[test] 171 | pub fn test_foreign_serialize() { 172 | let f: Foreign = Foreign::new_key("Account:John".to_owned()); 173 | 174 | // Confirm a foreign key is serialized into a simple string 175 | assert_eq!( 176 | serde_json::Value::String("Account:John".to_owned()), 177 | serde_json::to_value(f).unwrap() 178 | ); 179 | 180 | let f: Foreign = Foreign::new_value(Account { 181 | id: Some("Account:John".to_owned()), 182 | ..Default::default() 183 | }); 184 | 185 | // Confirm a loaded value uses the IntoKey trait during serialization 186 | assert_eq!( 187 | serde_json::Value::String("Account:John".to_owned()), 188 | serde_json::to_value(f).unwrap() 189 | ); 190 | } 191 | 192 | #[test] 193 | pub fn test_foreign_serialize_allowed() { 194 | let f: Foreign = Foreign::new_value(Account { 195 | id: Some("Account:John".to_owned()), 196 | ..Default::default() 197 | }); 198 | 199 | // once called, the Foreign should deserialize even the Values without calling 200 | // IntoKeys 201 | f.allow_value_serialize(); 202 | 203 | assert_eq!( 204 | serde_json::to_string(&Account { 205 | id: Some("Account:John".to_owned()), 206 | ..Default::default() 207 | }) 208 | .unwrap(), 209 | serde_json::to_string(&f).unwrap() 210 | ); 211 | } 212 | 213 | #[test] 214 | pub fn test_foreign_serialize_allowed_vec() { 215 | let v = vec![ 216 | Foreign::new_value(Account { 217 | id: Some("Account:John".to_owned()), 218 | ..Default::default() 219 | }), 220 | Foreign::new_key("Account:John".to_owned()), 221 | ]; 222 | 223 | // once called, the Foreign should deserialize even the Values without calling 224 | // IntoKeys 225 | v.allow_value_serialize(); 226 | 227 | assert_eq!( 228 | serde_json::to_value(&vec![ 229 | serde_json::to_value(Account { 230 | id: Some("Account:John".to_owned()), 231 | ..Default::default() 232 | }) 233 | .unwrap(), 234 | serde_json::to_value("Account:John").unwrap(), 235 | ]) 236 | .unwrap(), 237 | serde_json::to_value(&v).unwrap() 238 | ); 239 | 240 | let v = ForeignVec::::new_value(vec![ 241 | Account { 242 | id: Some("Account:John".to_owned()), 243 | ..Default::default() 244 | }, 245 | Account { 246 | id: Some("Account:Mark".to_owned()), 247 | ..Default::default() 248 | }, 249 | ]); 250 | 251 | v.allow_value_serialize(); 252 | 253 | assert_eq!( 254 | serde_json::to_value(vec![ 255 | Account { 256 | id: Some("Account:John".to_owned()), 257 | ..Default::default() 258 | }, 259 | Account { 260 | id: Some("Account:Mark".to_owned()), 261 | ..Default::default() 262 | }, 263 | ]) 264 | .unwrap(), 265 | serde_json::to_value(&v).unwrap() 266 | ); 267 | } 268 | 269 | #[test] 270 | fn test_foreign_deserialize() { 271 | let created_account = Account { 272 | id: Some("Account:John".to_owned()), 273 | handle: "JohnTheUser".to_owned(), 274 | password: "abc".to_owned(), 275 | email: "abc".to_owned(), 276 | ..Default::default() 277 | }; 278 | 279 | // build a json string where the author field contains a fully built Account 280 | // object. 281 | let loaded_author_json = format!( 282 | "{{ \"name\": \"filename\", \"author\": {} }}", 283 | serde_json::to_string(&created_account).unwrap() 284 | ); 285 | 286 | let file: File = serde_json::from_str(&loaded_author_json).unwrap(); 287 | 288 | // confirm the `Foreign` contains a value 289 | assert!(match &file.author.value() { 290 | Some(acc) => acc.id == Some("Account:John".to_owned()), 291 | _ => false, 292 | }); 293 | 294 | // build a json string where the author field is an ID string. 295 | let key_author_json = "{ \"name\": \"filename\", \"author\": \"Account:John\" }"; 296 | let file: File = serde_json::from_str(&key_author_json).unwrap(); 297 | 298 | // confirm the author field of the file is a Key with the account's ID 299 | assert!(match file.author.key().as_deref() { 300 | Some(key) => key == &"Account:John".to_owned(), 301 | _ => false, 302 | }); 303 | 304 | // build a json string where the author field is set to null. 305 | let unloaded_author_json = "{ \"name\": \"filename\", \"author\": null }"; 306 | let file: File = serde_json::from_str(&unloaded_author_json).unwrap(); 307 | 308 | // confirm the author field of the file is Unloaded 309 | assert!(file.author.is_unloaded()); 310 | } 311 | 312 | /// Test that a model can have fields that reference the `Self` type. 313 | #[test] 314 | fn test_model_self_reference() { 315 | assert_eq!("friend", account.friend.to_string()); 316 | assert_eq!("Account", account.friend().to_string()); 317 | assert_eq!("friend.handle", account.friend().handle.to_string()); 318 | } 319 | 320 | #[test] 321 | fn test_model_serializing_relations() { 322 | assert_eq!( 323 | "->manage->Project AS account_projects", 324 | account.managed_projects.as_alias("account_projects") 325 | ); 326 | assert_eq!("Project", account.managed_projects().to_string()); 327 | assert_eq!( 328 | "->manage->Project.name AS project_names", 329 | account.managed_projects().name.as_alias("project_names") 330 | ); 331 | 332 | assert_eq!( 333 | "->manage->Project->has->Release AS account_projects_releases", 334 | account 335 | .managed_projects() 336 | .releases 337 | .as_alias("account_projects_releases") 338 | ); 339 | 340 | assert_eq!( 341 | "->manage->Project->has->Release.name AS account_projects_release_names", 342 | account 343 | .managed_projects() 344 | .releases() 345 | .name 346 | .as_alias("account_projects_release_names") 347 | ); 348 | 349 | assert_eq!( 350 | "<-manage<-Account AS authors", 351 | project.authors.as_alias("authors") 352 | ); 353 | } 354 | 355 | #[test] 356 | fn test_with_id_edge() { 357 | let query_one = "an_id" 358 | .as_named_label(&account.to_string()) 359 | .with(&account.managed_projects.with_id("other_id")); 360 | 361 | let query_two = account 362 | .with_id("an_id") 363 | .with(&account.managed_projects.with_id("other_id")); 364 | 365 | assert_eq!("Account:an_id->manage->Project:other_id", query_two); 366 | assert_eq!(query_one, query_two); 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /tests/surrealdb_client.rs: -------------------------------------------------------------------------------- 1 | #![allow(incomplete_features)] 2 | #![feature(generic_const_exprs)] 3 | 4 | #[cfg(feature = "querybuilder")] 5 | #[cfg(feature = "queries")] 6 | #[cfg(feature = "model")] 7 | #[cfg(feature = "foreign")] 8 | mod test { 9 | 10 | use std::borrow::Cow; 11 | use std::fmt::Display; 12 | 13 | use serde::de::DeserializeOwned; 14 | use serde::Deserialize; 15 | use serde::Serialize; 16 | use serde_json::json; 17 | use surreal_simple_querybuilder::prelude::*; 18 | use surrealdb::engine::local::Db; 19 | use surrealdb::sql::Thing; 20 | use surrealdb::Response; 21 | use surrealdb::Surreal; 22 | 23 | //------------------------------------------------------------------------------ 24 | // STEP 0: create models and structs 25 | 26 | #[derive(Serialize, Deserialize, Default, Debug)] 27 | struct IUser { 28 | pub id: Option, 29 | pub name: String, 30 | pub email: String, 31 | } 32 | 33 | model!(User as user_model { 34 | id, 35 | pub name, 36 | pub email 37 | }); 38 | 39 | impl IntoKey for IUser { 40 | fn into_key(&self) -> Result { 41 | self 42 | .id 43 | .as_ref() 44 | .map(Thing::clone) 45 | .ok_or(IntoKeyError::Custom("The author has no ID")) 46 | } 47 | } 48 | 49 | use surrealdb::engine::local::Mem; 50 | use surrealdb::opt::QueryResult; 51 | use user_model::model as user; 52 | use user_model::User; 53 | 54 | #[derive(Serialize, Deserialize, Default, Debug)] 55 | struct IBook { 56 | pub id: Option, 57 | pub title: String, 58 | pub author: ForeignKey, 59 | pub read: bool, 60 | } 61 | 62 | model!(Book as book_model with(partial) { 63 | id, 64 | pub title, 65 | pub author, 66 | pub read 67 | }); 68 | 69 | use book_model::model as book; 70 | 71 | //------------------------------------------------------------------------------ 72 | // STEP 1: create functions that connect the querybuilder to the DB client 73 | 74 | pub type DbResult = Result>; 75 | pub static DB: once_cell::sync::Lazy> = 76 | once_cell::sync::Lazy::new(Surreal::init); 77 | 78 | pub async fn connect_db() -> DbResult<()> { 79 | DB.connect::(()).await?; 80 | DB.use_ns("namespace").use_db("database").await?; 81 | 82 | Ok(()) 83 | } 84 | 85 | pub async fn select<'a, R>( 86 | table: &'static str, params: impl QueryBuilderInjecter<'a> + 'a, 87 | ) -> DbResult 88 | where 89 | R: DeserializeOwned, 90 | usize: QueryResult, 91 | { 92 | let (query, params) = surreal_simple_querybuilder::queries::select("*", table, params)?; 93 | let items = bind_params(DB.query(query), params).await?.take(0)?; 94 | 95 | Ok(items) 96 | } 97 | 98 | pub async fn create(table: Table, object: &Object) -> DbResult 99 | where 100 | Object: Serialize + DeserializeOwned + Default, 101 | Table: Into> + Serialize + Display, 102 | { 103 | // Note how it doesn't use the params but instead use the model to know which 104 | // field it should include in the object: 105 | let item: Option = DB 106 | .query( 107 | QueryBuilder::new() 108 | .create(table.to_string()) 109 | .set_model(&table)? 110 | .build(), 111 | ) 112 | .bind(object) 113 | .await? 114 | .take(0)?; 115 | 116 | Ok(item.unwrap_or_default()) 117 | } 118 | 119 | pub async fn update<'a>( 120 | table: &'a str, params: impl QueryBuilderInjecter<'a> + 'a, 121 | ) -> DbResult { 122 | let (query, params) = surreal_simple_querybuilder::queries::update(table, params)?; 123 | let response = bind_params(DB.query(query), params).await?; 124 | 125 | Ok(response) 126 | } 127 | 128 | /// There is currently a rough edge between the bindings from the querybuilder 129 | /// and surrealdb itself because of the Serialize impl of [surrealdb::sql::Thing] 130 | fn bind_params( 131 | mut query: surrealdb::method::Query, 132 | params: std::collections::HashMap, 133 | ) -> surrealdb::method::Query { 134 | for (key, value) in params { 135 | match value { 136 | serde_json::Value::Object(mut obj) => { 137 | if obj.contains_key("id") && obj.contains_key("tb") { 138 | use serde_json::Value; 139 | use surrealdb::sql::Id; 140 | 141 | let Some(Value::String(tb)) = obj.remove("tb") else { 142 | continue; 143 | }; 144 | 145 | let Some(Value::Object(mut id)) = obj.remove("id") else { 146 | continue; 147 | }; 148 | 149 | let Some(Value::String(id)) = id.remove("String") else { 150 | continue; 151 | }; 152 | 153 | query = query.bind(( 154 | key, 155 | surrealdb::sql::Thing { 156 | id: Id::from(id), 157 | tb: tb, 158 | }, 159 | )); 160 | } else { 161 | query = query.bind((key, obj)); 162 | } 163 | } 164 | _ => { 165 | query = query.bind((key, value)); 166 | } 167 | }; 168 | } 169 | 170 | query 171 | } 172 | 173 | //------------------------------------------------------------------------------ 174 | // STEP 2: use the functions 175 | 176 | #[tokio::test] 177 | async fn main() -> DbResult<()> { 178 | connect_db().await?; 179 | 180 | let user0 = create( 181 | user, 182 | &IUser { 183 | id: None, 184 | email: "john.doe@mail.com".to_owned(), 185 | name: "John Doe".to_owned(), 186 | }, 187 | ) 188 | .await?; 189 | 190 | let user1 = create( 191 | user, 192 | &IUser { 193 | id: None, 194 | email: "jean.dupont@mail.com".to_owned(), 195 | name: "Jean Dupont".to_owned(), 196 | }, 197 | ) 198 | .await?; 199 | 200 | println!("created user 0: {user0:#?}"); 201 | println!("created user 1: {user1:#?}"); 202 | 203 | assert!(user0.id.is_some()); 204 | 205 | let user0_id = user0.id.as_ref().unwrap(); 206 | let user1_id = user1.id.as_ref().unwrap(); 207 | 208 | create_books(&user0_id, 10).await?; 209 | create_books(&user1_id, 5).await?; 210 | 211 | let all_books: Vec = select(&book, ()).await?; 212 | let user0_books: Vec = select(&book, Where((book.author, user0_id))).await?; 213 | let user1_books: Vec = select(&book, Where((book.author, user1_id))).await?; 214 | let both_users_books: Vec = select( 215 | &book, 216 | Where(Or(json!({ 217 | book.author: user0_id, 218 | book.author: user1_id 219 | }))), 220 | ) 221 | .await?; 222 | 223 | assert_eq!(all_books.len(), 15); 224 | assert_eq!(user0_books.len(), 10); 225 | assert_eq!(user1_books.len(), 5); 226 | 227 | println!("all books: {all_books:#?}"); 228 | println!("user0 books: {user0_books:#?}"); 229 | println!("user1 books: {user1_books:#?}"); 230 | println!("both users books: {both_users_books:#?}"); 231 | 232 | // let's mark a few random books as read, 233 | // we're using raw indices here to keep it simple: 234 | let books_to_read = &[&all_books[5].id, &all_books[10].id]; 235 | 236 | for id in books_to_read { 237 | if let Some(id) = id { 238 | read_book(&id).await?; 239 | } 240 | } 241 | 242 | let read_books: Vec = select(&book, Where((book.read, true))).await?; 243 | println!("read books: {read_books:#?}"); 244 | assert_eq!(read_books.len(), 2); 245 | 246 | let books_with_author: Vec = select( 247 | &book, 248 | ( 249 | Where((book.author, user1_id)), 250 | Fetch([book.author.as_ref()]), 251 | ), 252 | ) 253 | .await?; 254 | 255 | assert!(!books_with_author.is_empty()); 256 | 257 | if let Some(first) = books_with_author.first() { 258 | assert!(!first.author.is_unloaded()); 259 | 260 | if let Some(author) = first.author.value() { 261 | assert_eq!(author.id, user1.id); 262 | } 263 | } 264 | 265 | println!("books with author: {books_with_author:#?}"); 266 | 267 | Ok(()) 268 | } 269 | 270 | async fn create_books(author_id: &Thing, amount: usize) -> DbResult<()> { 271 | for i in 0..amount { 272 | create( 273 | book, 274 | &IBook { 275 | id: None, 276 | author: ForeignKey::new_key(author_id.to_owned()), 277 | title: format!("Lorem Ipsum {i}"), 278 | read: false, 279 | }, 280 | ) 281 | .await?; 282 | } 283 | 284 | Ok(()) 285 | } 286 | 287 | async fn read_book(book_id: &Thing) -> DbResult<()> { 288 | let filter = Where((book.id, book_id)); 289 | let set = Set((book.read, true)); 290 | update(&book_id.tb, (set, filter)).await?; 291 | 292 | Ok(()) 293 | } 294 | } 295 | --------------------------------------------------------------------------------