pg-worm 0.4.0

An easy to use, straightforward and async ORM for PostgreSQL databases
Documentation
![GitHub Actions Testing](https://github.com/Einliterflasche/pg-worm/actions/workflows/rust.yml/badge.svg)

# `pg-worm`
### *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. Currently it's mainly experimental.

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/).

## Usage
Fortunately, using this library is very easy.

Just derive the `Model` trait for your type, connect to your database 
and you are ready to go!

Here's a quick example: 

```rust
use pg_worm::{connect, force_register, Filter, JoinType, Model, NoTls, Query, QueryBuilder};
use tokio::try_join;

// First easily define your models.
#[derive(Model)]
struct Book {
    // `id` will be the primary key column and
	// automatically generated/incremented
    #[column(primary_key, auto)]
    id: i64,
    #[column(unique)]
    title: String,
    author_id: i64,
}

#[derive(Model)]
struct Author {
    #[column(primary_key, auto)]
    id: i64,
    name: String,
}

#[tokio::main]
async fn main() -> Result<(), pg_worm::Error> {
    // First connect to your server. This can be only done once.
    connect!("postgres://me:me@localhost:5432", NoTls).await?;

    // Then, create tables for your models.
    // Use `register!` if you want to fail if a
    // table with the same name already exists.
    //
    // `force_register` drops the old table,
    // which is useful for development.
    //
    // If your tables already exist, skip this part.
    force_register!(Author, Book)?;

    // Next, insert some data.
    // This works by passing values for all
    // fields which aren't autogenerated.
    try_join!(
        Author::insert("Stephen King"),
        Author::insert("Martin Luther King"),
        Author::insert("Karl Marx"),
        Book::insert("Foo - Part I", 1),
        Book::insert("Foo - Part II", 2),
        Book::insert("Foo - Part III", 3)
    )?;

	// Do a simple query for all books
    let books: Vec<Book> = Book::select(Filter::all()).await;
    assert_eq!(books.len(), 3);

    // Or search for a specific book
    let book = Book::select_one(Book::title.eq("Foo - Part II")).await;
    assert!(book.is_some());

    // Or make more complex queries using the query builder
    let king_books: Vec<Book> = Query::select(Book::COLUMNS)
        .filter(Author::name.like("%King%"))
        .join(&Book::author_id, &Author::id, JoinType::Inner)
        .build()
        .exec()
        .await?;
    assert_eq!(king_books.len(), 2);

    // Or delete a book, you don't like
    Book::delete(Book::title.eq("Foo - Part II")).await;

	// Graceful shutdown
    Ok(())
}
```

## Filters
Filters can be used to easily include `WHERE` clauses in your queries. 

They can be constructed by calling functions of the respective column. 
`pg_worm` automatically constructs a `Column` constant for each field 
of your `Model`. 

A practical example would look like this:

```rust
MyModel::select(MyModel::my_field.eq(5))
```

Currently the following filter functions are supported:

 * `Filter::all()` - doesn't check anything
 * `eq(T)` - checks whether the column is equal to a given value
 * `one_of(Vec<T>)` - checks whether the column is (at least) one of the given values
 * `like(String)` - check whether a `String` is `LIKE` a given [pattern]https://www.postgresql.org/docs/current/functions-matching.html
 
You can also do filter logic using `!`, `&` and `|`: `MyModel::my_field.eq(5) & !MyModel::other_field.eq("Foo")`.
This works as you expect logical OR and AND to work.
Please notice that, at this point, custom priorization via parantheses 
is **not possible**.

## Query Builder
Simply attaching a `Filter` to your query often does not suffice. 
For this reason, `pg-worm` provides a `QueryBuilder` interface for
constructing more complex queries. 

Start building your query by calling `Query::select()` and passing 
the columns you want to select. 
Normally you want to query all columns of a `Model` which you can do by passing 
`YourModel::columns()`.

You can modify your query using the following methods:

 * `.filter()` - add a `WHERE` clause
 * `.join()` - add a `JOIN` for querying accross tables/models
 * `.limit()` - add a `LIMIT` to how many rows are returned

After you have configured your query, build it using the `.build()` method.
Then, execute it by calling `.exec::<M>()`, where `M` is the `Model` which
should be parsed from the query result. It may be inferred.

## Opiniatedness
As mentioned before, `pg_worm` is opiniated in a number of ways. 
These include:

 * `panic`s. For the sake of convenience `pg_worm` only returns a  `Result` when 
   inserting data, since in that case Postgres might reject the data because of
   some constraint. 

   This means that should something go wrong, like:
    - the connection to the database collapsed,
    - `pg_worm` is unable to parse Postgres' response,
    - ...
   
   the program will panic.
 * ease of use. The goal of `pg_worm` is **not** to become an enterprise solution.
   If adding an option means infringing the ease of use then it will likely
   not be added.

## License
This project is dual-licensed under the MIT and Apache 2.0 licenses.