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
//! # `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. 
//! 
//! 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 `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
//! use pg_worm::prelude::*;
//! 
//! #[derive(Model)]
//! struct Book {
//!     // An auto-generated primary key column
//!     #[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.
//!     connect!("postgres://postgres:postgres@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.
//!     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 in the above example, `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`])
//! `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 could look like this:
//! 
//! ```ignore
//! 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 implement [`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 documentation 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:
//! 
//! 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.
//! 
//! A query will always return a `Result`.
//! 
//! ## Raw queries
//! 
//! Though these features are nice, they are not sufficient for most applications. This is why you can easily execute custom queries and still take advantage of automatic parsing, etc:
//! 
//! ```ignore
//! // NOTE: You have to pass the exact type that Postgres 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);
//! ```
//! 
//! ## 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 query;

use std::ops::Deref;

use prelude::Query;
pub use query::{Column, TypedColumn};
use query::{Delete, Update};

use crate::query::Select;
pub use async_trait::async_trait;
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 once_cell::sync::OnceCell;
use pg::{tls::MakeTlsConnect, Client, Connection, Socket, types::ToSql};
use thiserror::Error;

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

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

/// An enum representing the errors which are emitted by this crate.
#[derive(Error, Debug)]
pub enum Error {
    /// Something went wrong while connection to the database.
    #[error("couldn't connect to database")]
    ConnectionError,
    /// There already is a connection to the database.
    #[error("already connected to database")]
    AlreadyConnected,
    /// No connection has yet been established.
    #[error("not connected to database")]
    NotConnected,
    /// Errors emitted by the Postgres server.
    ///
    /// Most likely an invalid query.
    #[error("error communicating with database")]
    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>: TryFrom<Row, Error = Error> {
    /// 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<'a>(_: impl Into<String>, _: Vec<&'a (dyn ToSql + Sync)>) -> Query<'a, Vec<T>>;
}

static CLIENT: OnceCell<Client> = OnceCell::new();

/// Get a reference to the client, if a connection has been made.
/// Returns `Err(Error::NotConnected)` otherwise.
///
/// **This is a private library function needed to derive
/// the `Model` trait. Do not use!**
#[doc(hidden)]
#[inline]
pub fn _get_client() -> Result<&'static Client, Error> {
    if let Some(client) = CLIENT.get() {
        Ok(client)
    } else {
        Err(Error::NotConnected)
    }
}

/// Connect the `pg_worm` client to a postgres database.
///
/// You need to *_activate the connection by spawning it off into a new thread_*, only then will the client actually work.
///
/// You can connect to a database only once. If you try to connect again,
/// the function will return an error.
///
/// # Example
/// ```ignore
/// let conn = connect("my_db_url", NoTls).expect("db connection failed");
/// tokio::spawn(async move {
///     conn.await.expect("connection error")
/// });
/// ```
pub async fn connect<T>(config: &str, tls: T) -> Result<Connection<Socket, T::Stream>, Error>
where
    T: MakeTlsConnect<Socket>,
{
    let (client, conn) = tokio_postgres::connect(config, tls).await?;
    if let Err(_) = CLIENT.set(client) {
        return Err(Error::AlreadyConnected)
    };
    Ok(conn)
}

/// Convenience macro for connecting the `pg-worm` client
/// to a database server. Essentially writes the boilerplate
/// code needed. See the [`tokio_postgres`](https://docs.rs/tokio-postgres/latest/tokio_postgres/config/struct.Config.html)
/// documentation for more information on the config format.
///
/// Calls the [`connect()`] function.
/// Needs `tokio` to work.
///
/// # Panics
/// Panics when the connection is closed due to a fatal error.
#[macro_export]
macro_rules! connect {
    ($config:literal, $tls:expr) => {
        async {
            match $crate::connect($config, $tls).await {
                Ok(conn) => {
                    tokio::spawn(async move { conn.await.expect("fatal connection error") });
                    return Ok(());
                }
                Err(err) => return Err(err),
            }
        }
    };
}

/// Register your model with the database.
/// This creates a table representing your model.
///
/// Use the [`register!`] 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::register_model::<M>().await?;
/// }
/// ```
pub async fn register_model<M: Model<M>>() -> Result<(), Error>
where
    Error: From<<M as TryFrom<Row>>::Error>,
{
    let client = _get_client()?;
    client.batch_execute(M::_table_creation_sql()).await?;

    Ok(())
}

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

    client.batch_execute(&query).await?;

    Ok(())
}

/// Registers a [`Model`] with the database by creating a
/// corresponding table.
///
/// This is just a more convenient version api
/// for the [`register_model`] function.
///
/// This macro, too, requires the `tokio` crate.
///
/// 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
/// }
///
/// #[tokio::main]
/// async fn main() -> Result<(), pg_worm::Error> {
///     // ---- snip connection setup ----
///     register!(Foo)?;
/// }
/// ```
#[macro_export]
macro_rules! register {
    ($($x:ty),+) => {
        tokio::try_join!(
            $($crate::register_model::<$x>()),*
        )
    };
}

/// Like [`register!`] but if a table with the same name already
/// exists, it is dropped instead of returning an error.
#[macro_export]
macro_rules! force_register {
    ($($x:ty),+) => {
        tokio::try_join!(
            $($crate::force_register_model::<$x>()),*
        )
    };
}