pg-worm 0.4.0

An easy to use, straightforward and async ORM for PostgreSQL databases
Documentation

GitHub Actions Testing

pg-worm

PostgreSQL's Worst 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 and is intended to be used with tokio.

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:

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:

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

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:

  • panics. 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.