Crates.io | pg-worm |
lib.rs | pg-worm |
version | 0.7.0 |
source | src |
created_at | 2023-05-18 11:51:57.136337 |
updated_at | 2023-08-30 19:11:08.58455 |
description | An async, fully-typed and straightforward ORM for PostgreSQL databases |
homepage | https://github.com/Einliterflasche/pg-worm |
repository | https://github.com/Einliterflasche/pg-worm |
max_upload_size | |
id | 867791 |
size | 85,883 |
pg-worm
pg-worm
is a straightforward, fully typed, async ORM and Query Builder for PostgreSQL.
Well, at least that's the goal.
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.
This library is based on tokio_postgres
and is intended to be used with tokio
.
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:
// 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<dyn std::error::Error>> {
// 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<Book>
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<Book>
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.
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
to the query. | Select
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:
let where_: Where<'_> = MyModel::my_field.eq(&5);
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
. 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<T>
. 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<T>
.
You can also chain/modify these filters with standard boolean logic:
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
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
.
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:
// 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.
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:
use pg_worm::prelude::*;
#[derive(Model)]
struct Foo {
bar: i64
}
async fn foo() -> Result<(), Box<dyn std::error::Error>> {
// 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?;
}
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>
* | T
(but the column becomes NULLABLE
)
Vec<T>
* | T[]
*T
must be another supported type. Nesting and mixing Option
/Vec
is currently not supported.
are supported, too. To use them activate the respective feature, like so:
# 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
v1.0
Rust type | PostgreSQL type |
---|
Value
| JSONB
"time"
for time
v3.0
Rust type | PostgreSQL type |
---|
Date
| DATE
Time
| TIME
PrimitiveDateTime
| TIMESTAMP
OffsetDateTime
| TIMESTAMP WITH TIME ZONE
"uuid"
for uuid
v1.0
Rust type | PostgreSQL type |
---|
Uuid
| UUID
derive
optionsYou can configure some options for you Model
.
This is done by using one of the two attributes pg-worm
exposes.
#[table]
attributeThe #[table]
attribute can be used to pass configurations to a Model
which affect the respective table itself.
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.
#[column]
attributeThe #[column]
attribute can be used to pass configurations to a Model
's field which affect the respective column.
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.
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
The minimum supported rust version is 1.70
as this crate uses the recently introduced OnceLock
from the standard library.
This project is dual-licensed under the MIT and Apache 2.0 licenses.