# `pg-worm` [![Latest Version](https://img.shields.io/crates/v/pg-worm.svg)](https://crates.io/crates/pg-worm) ![GitHub Actions Testing](https://github.com/Einliterflasche/pg-worm/actions/workflows/rust.yml/badge.svg) [![docs](https://docs.rs/pg-worm/badge.svg)](https://docs.rs/pg-worm) [![license](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ### *P*ost*g*reSQL's *W*orst *ORM* `pg-worm` is a straightforward, fully typed, async ORM and Query Builder for PostgreSQL. Well, at least that's the goal. ## Features/Why `pg-worm`? - Existing ORMs are not **`async`**, require you to write migrations or use a cli. `pg-worm`'s explicit goal is to be **easy** and to require **no setup** beyond defining your types. - `pg-worm` also features **built-in pooling** and a **concise syntax**. - `pg-worm` **doesn't get in your way** - easily include raw queries while still profiting off the other features. ## Usage This library is based on [`tokio_postgres`](https://docs.rs/tokio-postgres/0.7.8/tokio_postgres/index.html) and is intended to be used with [`tokio`](https://tokio.rs/). Fortunately, using `pg-worm` is very easy. Simply derive the `Model` trait for your type, connect to your database and you are ready to go! Here's a quick example: ```rust // Import the prelude to get started quickly use pg_worm::prelude::*; #[derive(Model)] struct Book { // An auto-generated primary key #[column(primary_key, auto)] id: i64, title: String, author_id: i64 } #[derive(Model)] struct Author { #[column(primary_key, auto)] id: i64, name: String } #[tokio::main] async fn main() -> Result<(), Box> { // First create a connection. This can be only done once. Connection::build("postgres://postgres:postgres@localhost:5432").connect()?; // Then, create tables for your models. // Use `try_create_table!` if you want to fail if a // table with the same name already exists. // // `force_create_table` drops the old table, // which is useful for development. // // If your tables already exist, skip this part. force_create_table!(Author, Book).await?; // Next, insert some data. // This works by passing values for all // fields which aren't autogenerated. Author::insert("Stephen King").await?; Author::insert("Martin Luther King").await?; Author::insert("Karl Marx").await?; Book::insert("Foo - Part I", 1).await?; Book::insert("Foo - Part II", 2).await?; Book::insert("Foo - Part III", 3).await?; // Let's start with a simple query for all books: let books = Book::select().await?; // Vec assert_eq!(books.len(), 3); // You can also search for a specific book. // Adding a `WHERE` clause is as simple as // calling a method on the respective field: let book = Book::select_one() .where_(Book::title.eq(&"Foo - Part I".to_string())) .await?; // Option assert!(book.is_some()); // Or update exsisting records: let books_updated = Book::update() .set(Book::title, &"Foo - Part III".to_string()) .where_(Book::title.eq(&"Foo - Part II".to_string())) .await?; // u64 assert_eq!(books_updated, 1); // Or delete a book, you don't like: let books_deleted = Book::delete() .where_(Book::title.eq(&"Foo - Part III".to_string())) .await?; // u64 assert_eq!(books_deleted, 2); Ok(()) } ``` If you want to see more code examples, have a look at the [tests directory](https://github.com/Einliterflasche/pg-worm/tree/main/pg-worm/tests). ## Query Builders As you can see above, `pg-worm` allows you to build queries by chaining methods on so called 'builders'. For each query type `pg-worm` provides a respective builder (except for `INSERT` which is handled differently). These builders expose a set of methods for building queries. Here's a list of them: Method | Description | Availability -------|-------------|------------- `where_` | Attach a `WHERE` clause to the query. | All builders (`Select`, `Update`, `Delete`) `where_raw` | Same as `where_` but you can pass raw SQL. | All builders (`Select`, `Update`, `Delete`) `set` | `SET` a column's value. Note: this method has to be called at least once before you can execute the query. | `Update` `limit`, `offset` | Attach a [`LIMIT` or `OFFSET`](https://www.postgresql.org/docs/current/queries-limit.html) to the query. | `Select` ## Filtering using `WHERE` `.where_()` can be used to easily include `WHERE` clauses in your queries. This is done by passing a `Where` object which can be constructed by calling methods on the respective column. `pg-worm` automatically constructs a constant for each field of your `Model`. A practical example would look like this: ```rust let where_: Where<'_> = MyModel::my_field.eq(&5); ``` ### Available methods Currently, the following methods are implemented: Function | Description | Availability ---------|-------------|------------- `eq` | Checks for equality. | Any type `gt`, `gte`, `lt`, `lte` | Check whether this column's value is greater than, etc than some other value. | Any type which implements [`PartialOrd`](https://doc.rust-lang.org/std/cmp/trait.PartialOrd.html). Note: it's not guaranteed that Postgres supports these operator for a type just because it's `PartialOrd`. Be sure to check the Postgres documentation for your type beforehand. `null`, `not_null` | Checks whether a column is `NULL`. | Any `Option`. All other types are not `NULL`able and thus guaranteed not to be `NULL`. `contains`, `contains_not`, `contains_all`, `conatains_none`, `contains_any` | Array operations. Check whether this column's array contains a value, a value _not_, or any/all/none values of another array. | Any `Vec`. ### Boolean logic You can also chain/modify these filters with standard boolean logic: ```rust Book::select() .where_(!Book::id.eq(&1) & Book::id.gt(&3)) .await?; ``` Operator/Method | Description ----------------|------------ `!`, `.not()` | Negate a filter using a locigal `NOT` `&`, `.and()` | Combine two filters using a logical `AND` `\|\|`, `.or()` | Combine two filters using a logical `OR` ### Executing a query After having finished building your query, you can simply call `.await`. This will turn the builder into a `Query` object which is then executed asynchronously. Executing a query will always result in a `Result`. ## Raw queries Though these features are nice, they are not sufficient for all applications. This is why you can easily execute custom queries and still take advantage of automatic parsing, etc: ```rust // NOTE: You have to pass the exact type that PostgreSQL is // expecting. Doing otherwise will result in a runtime error. let king_books = Book::query(r#" SELECT * FROM book JOIN author ON author.id = book.author_id WHERE POSITION(? in author.name) > 0 "#, vec![&"King".to_string()] ).await?; assert_eq!(king_books.len(), 2); ``` Alse see `.where_raw` on query builders by which you can pass a raw condition without needing to write the whole query yourself. ## Transactions `pg-worm` also supports transactions. You can easily execute any query inside a `Transaction` and only commit when you are satisfied. `Transaction`s are automatically rolled-back when dropped, unless they have been committed beforehand. Here's an example: ```rust use pg_worm::prelude::*; #[derive(Model)] struct Foo { bar: i64 } async fn foo() -> Result<(), Box> { // Easily create a new transaction let transaction = Transaction::begin().await?; // Execute any query inside the transaction let all_foo = transaction.execute( Foo::select() ).await?; // Commit the transaction when done. // If not committed, transaction are rolled back // when dropped. transaction.commit().await?; } ``` ## Supported types The following is a list of supported (Rust) types and which PostgreSQL type they are mapped to. Rust type | PostgreSQL type -------------|--------------------- `bool` | `BOOL` `i16` | `INT2` `i32` | `INT4` `i64` | `INT8` `f32` | `FLOAT4` `f64` | `FLOAT8` `String` | `TEXT` `Option`* | `T` (but the column becomes `NULLABLE`) `Vec`* | `T[]` _*`T` must be another supported type. Nesting and mixing `Option`/`Vec` is currently not supported._ ### JSON, timestamps and more are supported, too. To use them activate the respective feature, like so: ```toml # Cargo.toml [dependencies] pg-worm = { version = "latest-version", features = ["foo"] } ``` Here is a list of the supported features/types with their respective PostgreSQL type: * `"serde-json"` for [`serde_json`](https://crates.io/crates/serde_json) `v1.0` Rust type | PostgreSQL type ----------|---------------- `Value` | `JSONB` * `"time"` for [`time`](https://crates.io/crates/time/0.3.0) `v3.0` Rust type | PostgreSQL type --------------------|---------------- `Date` | `DATE` `Time` | `TIME` `PrimitiveDateTime` | `TIMESTAMP` `OffsetDateTime` | `TIMESTAMP WITH TIME ZONE` * `"uuid"` for [`uuid`](https://crates.io/crates/uuid) `v1.0` Rust type | PostgreSQL type ----------|---------------- `Uuid` | `UUID` ## `derive` options You can configure some options for you `Model`. This is done by using one of the two attributes `pg-worm` exposes. ### The `#[table]` attribute The `#[table]` attribute can be used to pass configurations to a `Model` which affect the respective table itself. ```rust use pg_worm::prelude::*; #[derive(Model)] #[table(table_name = "book_list")] struct Book { id: i64 } ``` Option | Meaning | Usage | Default -------|---------|-------|-------- `table_name` | Set the table's name | `table_name = "new_table_name"` | The `struct`'s name converted to snake case using [this crate](https://crates.io/crates/convert_case). ### The `#[column]` attribute The `#[column]` attribute can be used to pass configurations to a `Model`'s field which affect the respective column. ```rust use pg_worm::prelude::*; #[derive(Model)] struct Book { #[column(primary_key, auto)] id: i64 } ``` Option | Meaning | Usage | Default -------|---------|-------|-------- `column_name` | Set this column's name. | `#[column(column_name = "new_column_name")]` | The fields's name converted to snake case using [this crate](https://crates.io/crates/convert_case). `primary_key` | Make this column the primary key. Only use this once per `Model`. If you want this column to be auto generated use `auto` as well. | `#[column(primary_key)]` | `false` `auto` | Make this column auto generated. Works only for `i16`, `i32` and `i64`, as well as `Uuid` *if* the `"uuid"` feature has been enabled *and* you use PostgreSQL version 13 or later. | `#[column(auto)]` | `false` ## MSRV The minimum supported rust version is `1.70` as this crate uses the recently introduced `OnceLock` from the standard library. ## License This project is dual-licensed under the MIT and Apache 2.0 licenses.