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
#![deny(
    clippy::cargo,
    clippy::nursery,
    clippy::pedantic,
    clippy::restriction,
    rustdoc::missing_crate_level_docs,
    rustdoc::private_doc_tests,
    rustdoc::invalid_html_tags,
    warnings,
    absolute_paths_not_starting_with_crate,
    elided_lifetimes_in_paths,
    explicit_outlives_requirements,
    keyword_idents,
    macro_use_extern_crate,
    meta_variable_misuse,
    missing_abi,
    missing_copy_implementations,
    missing_debug_implementations,
    missing_docs,
    non_ascii_idents,
    noop_method_call,
    pointer_structural_match,
    rust_2021_incompatible_closure_captures,
    rust_2021_incompatible_or_patterns,
    rust_2021_prefixes_incompatible_syntax,
    rust_2021_prelude_collisions,
    single_use_lifetimes,
    trivial_casts,
    trivial_numeric_casts,
    unsafe_code,
    unsafe_op_in_unsafe_fn,
    unstable_features,
    unused_crate_dependencies,
    unused_extern_crates,
    unused_import_braces,
    unused_lifetimes,
    unused_macro_rules,
    unused_qualifications,
    variant_size_differences,
    // Unstable lints:
    // unreachable_pub,
    // Nightly lints:
    // rustdoc::missing_doc_code_examples,
    // fuzzy_provenance_casts,
    // lossy_provenance_casts,
    // must_not_suspend,
    // non_exhaustive_omitted_patterns,
)]
#![allow(
    clippy::blanket_clippy_restriction_lints,
    clippy::exhaustive_structs,
    clippy::missing_inline_in_public_items,
    clippy::implicit_return,
    clippy::unwrap_used,
    clippy::multiple_inherent_impl,
    clippy::pattern_type_mismatch,
    clippy::wildcard_enum_match_arm,
    clippy::exhaustive_enums,
    clippy::self_named_module_files,
    clippy::pub_use,
    clippy::else_if_without_else,
    clippy::std_instead_of_alloc
)]
#![doc = include_str!("../README.md")]

use sqlx::{query, PgPool};

/// Implementing [`sparkle_cache::Backend`] on [`Cache`]
mod backend;
/// Implementing [`sparkle_cache::Cache`] on [`Cache`]
mod cache;
/// Models for SQL select queries and their conversion to `sparkle_cache` models
pub(crate) mod model;
/// Tests for this crate
#[cfg(test)]
mod tests;

/// The Discord cache
///
/// This is a wrapper over [`PgPool`], you can use [`Cache::pg`] to get the
/// inner [`PgPool`], see its documentation for more info
///
///
/// # Indexing
///
/// Most ID and name columns are indexed, you can inspect the database to see
/// which columns are indexed, you can also create your own indexes using the
/// inner [`PgPool`], if you think there's a missing index, please create an
/// issue
///
/// [`PgPool`]: https://docs.rs/sqlx/latest/sqlx/type.PgPool.html
#[derive(Debug)]
pub struct Cache(PgPool);

impl Cache {
    /// Create a new cache using the given URL
    ///
    /// Refer to [`sqlx::postgres::PgConnectOptions`] for the URL format
    ///
    /// This only clears the tables about Sparkle Cache, meaning any other
    /// custom tables are persistent
    ///
    /// # Errors
    ///
    /// Returns the error `SQLx` returns when the database connection failed or
    /// the `init.sql` script failed to run
    pub async fn new(url: &str) -> Result<Self, sqlx::Error> {
        let cache = PgPool::connect(url).await?;

        let init_sql = include_str!("../sql/init.sql");
        for statement in init_sql.split(';') {
            query(statement.trim()).execute(&cache).await?;
        }

        Ok(Self(cache))
    }

    /// Return a reference to the inner `PgPool` for the cache, so that you can
    /// use it in custom queries
    ///
    /// Make sure the names of tables or indexes you create don't collide with
    /// the ones created by this crate
    ///
    /// # Example
    ///
    /// ```rust
    /// use sparkle_cache_postgres::Cache;
    /// use sqlx::query;
    /// # #[tokio::main]
    /// # async fn main() -> Result<(), anyhow::Error> {
    /// # let cache = Cache::new("postgresql://localhost:5432/sparkle").await?;
    /// query!("CREATE TABLE IF NOT EXISTS members_ext (id bigint, age smallint)")
    ///     .execute(cache.pg())
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    #[must_use]
    pub const fn pg(&self) -> &PgPool {
        &self.0
    }
}