database_schema/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(
3    missing_copy_implementations,
4    missing_debug_implementations,
5    missing_docs,
6    trivial_casts,
7    trivial_numeric_casts,
8    unsafe_code,
9    unused_import_braces,
10    unused_qualifications,
11    unused_extern_crates,
12    unused_results,
13    variant_size_differences
14)]
15#![cfg_attr(docsrs, feature(doc_cfg), allow(unused_attributes))]
16
17#[cfg(not(any(feature = "sqlite", feature = "postgres", feature = "mysql"),))]
18compile_error!(
19    "At least one of the following features must be enabled: sqlite, postgres or mysql."
20);
21#[cfg(not(any(feature = "sqlx", feature = "diesel"),))]
22compile_error!("At least one of the following features must be enabled: sqlx or diesel.");
23
24#[cfg(feature = "sqlite")]
25#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))]
26mod sqlite;
27
28#[cfg(feature = "mysql")]
29#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))]
30mod mysql;
31
32#[cfg(feature = "postgres")]
33#[cfg_attr(docsrs, doc(cfg(feature = "mysql")))]
34mod postgres;
35
36#[cfg(all(
37    feature = "macros",
38    not(any(feature = "runtime-async-std", feature = "runtime-tokio"))
39))]
40compile_error!(
41    "At least one of the following features must be enabled: runtime-async-std or runtime-tokio."
42);
43
44#[cfg(all(
45    feature = "macros",
46    any(feature = "runtime-async-std", feature = "runtime-tokio")
47))]
48pub mod macros;
49
50pub(crate) mod process;
51
52pub mod error;
53pub use error::Error;
54
55/// Entry point for using the crate and the result of calling [`DatabaseSchemaBuilder::build`].
56///
57/// ```rust,ignore
58/// DatabaseSchemaBuilder::new().build().dump().await?;
59/// ```
60#[cfg(all(
61    any(feature = "sqlite", feature = "postgres", feature = "mysql"),
62    any(feature = "sqlx", feature = "diesel")
63))]
64#[derive(Debug, Default)]
65pub struct DatabaseSchema(DatabaseSchemaInner);
66
67#[derive(Debug, Clone)]
68struct ConnectionUrl(String);
69
70impl Default for ConnectionUrl {
71    fn default() -> Self {
72        #[cfg(feature = "sqlite")]
73        let conn = ConnectionUrl(String::from(sqlite::DEFAULT_CONNECTION_URL));
74        #[cfg(feature = "mysql")]
75        let conn = ConnectionUrl(String::from(mysql::DEFAULT_CONNECTION_URL));
76        #[cfg(feature = "postgres")]
77        let conn = ConnectionUrl(String::from(postgres::DEFAULT_CONNECTION_URL));
78
79        conn
80    }
81}
82
83#[derive(Debug, Default, Clone)]
84struct DatabaseSchemaInner {
85    connection_url: ConnectionUrl,
86    migrations_path: std::path::PathBuf,
87    destination_path: std::path::PathBuf,
88}
89
90/// Builder for `DatabaseSchema`
91#[cfg(all(
92    any(feature = "sqlite", feature = "postgres", feature = "mysql"),
93    any(feature = "sqlx", feature = "diesel")
94))]
95#[derive(Debug, Default)]
96pub struct DatabaseSchemaBuilder(DatabaseSchemaInner);
97
98#[cfg(all(
99    any(feature = "sqlite", feature = "postgres", feature = "mysql"),
100    any(feature = "sqlx", feature = "diesel")
101))]
102#[allow(dead_code)]
103impl DatabaseSchemaBuilder {
104    /// Create a new `DatabaseSchemaBuilder`
105    pub fn new() -> Self {
106        Self::default()
107    }
108
109    /// This is the connection URL used to connect to the database.
110    ///
111    /// For `mysql` and `postgres` this is the same URL you would pass to the `connect` method of the client.
112    ///
113    /// * `mysql`: `mysql://[user[:password]@]host/database_name[?unix_socket=socket-path&ssl_mode=SSL_MODE*&ssl_ca=/etc/ssl/certs/ca-certificates.crt&ssl_cert=/etc/ssl/certs/client-cert.crt&ssl_key=/etc/ssl/certs/client-key.crt]`
114    ///
115    /// * `postgres`: `postgresql://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]` - you can read more at [libpq docs](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING)
116    ///
117    /// * `sqlite`: `sqlite::memory:` in the case of `sqlx` and `:memory:` in the case of
118    /// `diesel` - you don't need to set this for `sqlite` as we auto-detect it as long as
119    /// you enable the `sqlite` feature.
120    pub fn connection_url<S: Into<String>>(&mut self, connection_url: S) -> &mut Self {
121        self.0.connection_url = ConnectionUrl(connection_url.into());
122        self
123    }
124
125    /// Set `migrations_dir` - this is the directory path where your migrations are stored.
126    ///
127    /// By default we assume that the migrations are stored in the `migrations` directory
128    /// starting at the root of your project.
129    ///
130    /// We call `canonicalize()` on the path, so you can pass in a relative path. The
131    /// downside is that this call can fail.
132    pub fn migrations_dir<P: AsRef<std::path::Path>>(
133        &mut self,
134        migrations_dir: P,
135    ) -> Result<&mut Self, Error> {
136        self.0.migrations_path = migrations_dir.as_ref().to_path_buf().canonicalize()?;
137        Ok(self)
138    }
139
140    /// Set `destination_path` - this is the path to the file where we'll store the SQL dump.
141    ///
142    /// By default we assume `structure.sql` in the root of your project.
143    pub fn destination_path<P: AsRef<std::path::Path>>(
144        &mut self,
145        destination_path: P,
146    ) -> &mut Self {
147        self.0.destination_path = destination_path.as_ref().to_path_buf();
148        self
149    }
150
151    /// Build `DatabaseSchema` from `DatabaseSchemaBuilder`
152    pub fn build(&self) -> DatabaseSchema {
153        DatabaseSchema(self.0.clone())
154    }
155}
156
157impl DatabaseSchema {
158    /// Dump the database schema.
159    pub async fn dump(&self) -> Result<(), Error> {
160        #[cfg(all(feature = "mysql", any(feature = "sqlx", feature = "diesel")))]
161        use crate::mysql::write_structure_sql;
162        #[cfg(all(feature = "postgres", any(feature = "sqlx", feature = "diesel")))]
163        use crate::postgres::write_structure_sql;
164        #[cfg(all(feature = "sqlite", any(feature = "sqlx", feature = "diesel")))]
165        use crate::sqlite::write_structure_sql;
166
167        write_structure_sql(
168            &self.0.connection_url.0,
169            self.0.migrations_path.clone(),
170            self.0.destination_path.clone(),
171        )
172        .await?;
173        Ok(())
174    }
175}
176
177/// Generate a `destination_path` SQL file using migrations from the `migrations_path`
178/// folder.
179///
180/// Calling this function is strictly equivalent to:
181///
182/// ```rust,ignore
183/// // This assumes you're using SQLite in memory.
184/// //
185/// // If you need to set up a `connection_url` you can use `DatabaseSchemaBuilder::connection_url`
186/// // before calling `build()`.
187///
188/// DatabaseSchemaBuilder::new()
189///     .migrations_dir(migrations_path)?
190///     .destination_path(destination_path)
191///     .build()
192///     .dump()
193///     .await
194/// ```
195/// Requires an executor to be available.
196#[cfg(all(
197    any(feature = "sqlite", feature = "postgres", feature = "mysql"),
198    any(feature = "sqlx", feature = "diesel")
199))]
200pub async fn generate<P: AsRef<std::path::Path>, Q: AsRef<std::path::Path>>(
201    connection_url: Option<&str>,
202    migrations_path: P,
203    destination_path: Q,
204) -> Result<(), Error> {
205    let mut builder = DatabaseSchemaBuilder::new();
206    if let Some(connection_url) = connection_url {
207        let _ = builder.connection_url(connection_url);
208    }
209    builder
210        .migrations_dir(migrations_path)?
211        .destination_path(destination_path)
212        .build()
213        .dump()
214        .await
215}