1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
/*!
# `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,
    age: i64
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // First create a connection. This can be only done once.
    Connection::to("postgres://postgres:postgres@localhost:5432").await?;

    // 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](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<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>`.

### 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<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?;
}
```

## Supported types
The following is a list of all supported (Rust) types and which PostgreSQL type they are mapped to.
Rust type | PostgreSQL type
----------|---------------------
`bool` | `bool`
`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._

## 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.
*/

#![deny(missing_docs)]

// This allows importing this crate's contents from pg-worm-derive.
extern crate self as pg_worm;

pub mod config;
pub mod query;

use std::{ops::Deref, sync::OnceLock};

use deadpool_postgres::{Client as DpClient, GenericClient, Pool, Transaction as DpTransaction};
use prelude::Query;
pub use query::{Column, TypedColumn};
use query::{Delete, Update};

use crate::query::Select;
pub use async_trait::async_trait;
/// This crate's reexport of the `futures` crate.
pub use futures;
pub use pg::{NoTls, Row};
pub use pg_worm_derive::Model;
/// This crate's reexport of the `tokio_postgres` crate.
pub use tokio_postgres as pg;

use pg::types::ToSql;
use thiserror::Error;

/// This module contains all necessary imports to get you started
/// easily.
pub mod prelude {
    pub use crate::{force_create_table, try_create_table, FromRow, Model};

    pub use crate::config::Connection;

    pub use crate::query::{
        Column, Executable, NoneSet, Query, Select, SomeSet, ToQuery, Transaction, TypedColumn,
    };
    pub use std::ops::Deref;
    pub use std::str::FromStr;
}

/// An enum representing the errors which are emitted by this crate.
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum Error {
    /// Something went wrong while connection to the database.
    #[error("not connected to database")]
    NotConnected,
    /// There already is a connection to the database.
    #[error("already connected to database")]
    AlreadyConnected,
    /// No connection has yet been established.
    #[error("couldn't connect to database")]
    ConnectionError(#[from] deadpool_postgres::CreatePoolError),
    /// No connection object could be created.
    #[error("couldn't build connection/config")]
    ConnectionBuildError(#[from] deadpool_postgres::BuildError),
    /// Emitted when an invalid config string is passed to `Connection::to`.
    #[error("invalid config")]
    ConfigError(#[from] deadpool_postgres::ConfigError),
    /// Emitted when no connection could be fetched from the pool.
    #[error("couldn't fetch connection from pool")]
    PoolError(#[from] deadpool_postgres::PoolError),
    /// Errors emitted by the Postgres server.
    ///
    /// Most likely an invalid query.
    #[error("postgres returned an error")]
    PostgresError(#[from] tokio_postgres::Error),
}

/// A trait signaling that a struct may be parsed from
/// a Postgres Row.
///
/// This being a new trait allows the exposure of a
/// derive macro for it.
pub trait FromRow: TryFrom<Row, Error = Error> {}

/// This is the trait which you should derive for your model structs.
///
/// It provides the ORM functionality.
///
#[async_trait]
pub trait Model<T>: FromRow {
    /// This is a library function needed to derive the `Model`trait.
    ///
    /// *_DO NOT USE_*
    #[doc(hidden)]
    #[must_use]
    fn _table_creation_sql() -> &'static str;

    /// Returns a slice of all columns this model's table has.
    fn columns() -> &'static [&'static dyn Deref<Target = Column>];

    /// Returns the name of this model's table's name.
    fn table_name() -> &'static str;

    /// Start building a `SELECT` query which will be parsed to this model.
    fn select<'a>() -> Select<'a, Vec<T>>;

    /// Start building a `SELECT` query which returns either
    /// one entity or `None`.
    fn select_one<'a>() -> Select<'a, Option<T>>;

    /// Start building an `UPDATE` query.
    ///
    /// Returns the number of rows affected.
    fn update<'a>() -> Update<'a>;

    /// Start building a `DELETE` query.
    ///
    /// Returns the number or rows affected.
    fn delete<'a>() -> Delete<'a>;

    /// Build a raw query by passing in a statement along with
    /// arguments.
    ///
    /// You can reference the params by using `?` as a placeholder.
    fn query(_: impl Into<String>, _: Vec<&(dyn ToSql + Sync)>) -> Query<'_, Vec<T>>;
}

static POOL: OnceLock<Pool> = OnceLock::new();

/// Try to fetch a client from the connection pool.
#[doc(hidden)]
pub async fn fetch_client() -> Result<DpClient, Error> {
    POOL.get()
        .ok_or(Error::NotConnected)?
        .get()
        .await
        .map_err(Error::from)
}

/// Hidden function so set the pool from the `config` module.
#[doc(hidden)]
pub fn set_pool(pool: Pool) -> Result<(), Error> {
    POOL.set(pool).map_err(|_| Error::AlreadyConnected)
}

/// Create a table for your model.
///
/// Use the [`try_create_table!`] macro for a more convenient api.
///
/// # Usage
/// ```ignore
/// #[derive(Model)]
/// struct Foo {
///     #[column(primary_key)]
///     id: i64
/// }
///
/// #[tokio::main]
/// async fn main() -> Result<(), pg_worm::Error> {
///     // ---- snip connection setup ----
///     pg_worm::try_create_table::<M>().await?;
/// }
/// ```
#[doc(hidden)]
pub async fn try_create_table<M: Model<M>>() -> Result<(), Error>
where
    Error: From<<M as TryFrom<Row>>::Error>,
{
    let client = fetch_client().await?;
    client.batch_execute(M::_table_creation_sql()).await?;

    Ok(())
}

/// Same as [`try_create_table`] but if a table with the same name
/// already exists, it is dropped instead of returning an error.
#[doc(hidden)]
pub async fn force_create_table<M: Model<M>>() -> Result<(), Error>
where
    Error: From<<M as TryFrom<Row>>::Error>,
{
    let client = fetch_client().await?;
    let query = format!(
        "DROP TABLE IF EXISTS {} CASCADE; ",
        M::columns()[0].table_name()
    ) + M::_table_creation_sql();

    client.batch_execute(&query).await?;

    Ok(())
}

/// Creates a table for the specified [`Model`].
///
/// This is just a more convenient api
/// for the [`try_create_table()`] function.
///
/// Returns an error if:
///  - a table with the same name already exists,
///  - the client is not connected,
///  - the creation of the table fails.
///
/// # Usage
///
/// ```ignore
/// use pg_worm::prelude::*;
///
/// #[derive(Model)]
/// struct Foo {
///     id: i64
/// }
///
/// #[derive(Model)]
/// struct Bar {
///     baz: String
/// }
///
/// #[tokio::main]
/// async fn main() -> Result<(), pg_worm::Error> {
///     // ---- snip connection setup ----
///     try_create_table!(Foo, Bar)?;
/// }
/// ```
#[macro_export]
macro_rules! try_create_table {
    ($($x:ty),+) => {
        $crate::futures::future::try_join_all(
            vec![
                $(
                    $crate::futures::future::FutureExt::boxed(
                        $crate::try_create_table::<$x>()
                    )
                ),*
            ]
        )
    };
}

/// Like [`try_create_table!`] but if a table with the same name already
/// exists, it is dropped instead of returning an error.
///
/// # Example
/// ```ignore
/// force_create_table(MyModel, AnotherModel).await?;
/// ```
#[macro_export]
macro_rules! force_create_table {
    ($($x:ty),+) => {
        $crate::futures::future::try_join_all(
            vec![
                $(
                    $crate::futures::future::FutureExt::boxed(
                        $crate::force_create_table::<$x>()
                    )
                ),*
            ]
        )
    };
}