Skip to content

Commit 7c7cbee

Browse files
committed
WIP feat: introduce parsing of sqlx.toml
1 parent 1e526a2 commit 7c7cbee

File tree

8 files changed

+317
-5
lines changed

8 files changed

+317
-5
lines changed

Cargo.lock

Lines changed: 51 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,17 @@ features = ["all-databases", "_unstable-all-types"]
5252
rustdoc-args = ["--cfg", "docsrs"]
5353

5454
[features]
55-
default = ["any", "macros", "migrate", "json"]
55+
default = ["any", "macros", "migrate", "json", "config-all"]
5656

5757
derive = ["sqlx-macros/derive"]
5858
macros = ["derive", "sqlx-macros/macros"]
5959
migrate = ["sqlx-core/migrate", "sqlx-macros?/migrate", "sqlx-mysql?/migrate", "sqlx-postgres?/migrate", "sqlx-sqlite?/migrate"]
6060

61+
# Enable parsing of `sqlx.toml` for configuring macros, migrations, or both
62+
config-macros = ["sqlx-core/config-macros"]
63+
config-migrate = ["sqlx-core/config-migrate"]
64+
config-all = ["config-macros", "config-migrate"]
65+
6166
# intended mainly for CI and docs
6267
all-databases = ["mysql", "sqlite", "postgres", "any"]
6368
_unstable-all-types = [

sqlx-core/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ _tls-none = []
2828
# support offline/decoupled building (enables serialization of `Describe`)
2929
offline = ["serde", "either/serde"]
3030

31+
config-toml = ["serde", "toml/parse"]
32+
config-macros = ["config-toml"]
33+
config-migrate = ["config-toml"]
34+
3135
[dependencies]
3236
# Runtimes
3337
async-std = { workspace = true, optional = true }
@@ -75,6 +79,7 @@ percent-encoding = "2.1.0"
7579
regex = { version = "1.5.5", optional = true }
7680
serde = { version = "1.0.132", features = ["derive", "rc"], optional = true }
7781
serde_json = { version = "1.0.73", features = ["raw_value"], optional = true }
82+
toml = { version = "0.8.16", optional = true }
7883
sha1 = { version = "0.10.1", default-features = false, optional = true }
7984
sha2 = { version = "0.10.0", default-features = false, optional = true }
8085
sqlformat = "0.2.0"

sqlx-core/src/config/macros.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// Configuration for the [`sqlx::query!()`] family of macros.
2+
#[derive(Debug, serde::Deserialize)]
3+
pub struct Config {
4+
/// Override the environment variable
5+
pub database_url_var: Option<String>,
6+
}

sqlx-core/src/config/migrate.rs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/// Configuration for migrations when executed using `sqlx::migrate!()` or through `sqlx-cli`.
2+
///
3+
/// ### Note
4+
/// A manually constructed [`Migrator`][crate::migrate::Migrator] will not be aware of these
5+
/// configuration options. We recommend using [`sqlx::migrate!()`] instead.
6+
///
7+
/// ### Warning: Potential Data Loss or Corruption!
8+
/// Many of these options, if changed after migrations are set up,
9+
/// can result in data loss or corruption of a production database
10+
/// if the proper precautions are not taken.
11+
///
12+
/// Be sure you know what you are doing and to read all relevant documentation _thoroughly_.
13+
#[derive(Debug, serde::Deserialize)]
14+
pub struct Config {
15+
/// Override the name of the table used to track executed migrations.
16+
///
17+
/// May be schema-qualified. Defaults to `_sqlx_migrations`.
18+
///
19+
/// Potentially useful for multi-tenant databases.
20+
///
21+
/// ### Warning: Potential Data Loss or Corruption!
22+
/// Changing this option for a production database will likely result in data loss or corruption
23+
/// as the migration machinery will no longer be aware of what migrations have been applied
24+
/// and will attempt to re-run them.
25+
///
26+
/// You should create the new table as a copy of the existing migrations table (with contents!),
27+
/// and be sure all instances of your application have been migrated to the new
28+
/// table before deleting the old one.
29+
///
30+
/// ### Example
31+
/// `sqlx.toml`:
32+
/// ```toml
33+
/// [migrate]
34+
/// table_name = "foo._sqlx_migrations"
35+
/// ```
36+
pub table_name: Option<String>,
37+
38+
/// Specify characters that should be ignored when hashing migrations.
39+
///
40+
/// Any characters contained in the given string will be dropped when a migration is hashed.
41+
///
42+
/// ### Example: Ignore Carriage Return (`<CR>` | `\r`)
43+
/// Line ending differences between platforms can result in migrations having non-repeatable
44+
/// hashes. The most common culprit is the carriage return (`<CR>` | `\r`), which Windows
45+
/// uses in its line endings alongside line feed (`<LF>` | `\n`), often written `CRLF` or `\r\n`,
46+
/// whereas Linux and macOS use only line feeds.
47+
///
48+
/// `sqlx.toml`:
49+
/// ```toml
50+
/// [migrate]
51+
/// ignored_chars = "\r"
52+
/// ```
53+
///
54+
/// For projects using Git, this can also be addressed using [`.gitattributes`]:
55+
///
56+
/// ```text
57+
/// # Force newlines in migrations to be line feeds on all platforms
58+
/// migrations/*.sql text eol=lf
59+
/// ```
60+
///
61+
/// This may require resetting or re-checking out the migrations files to take effect.
62+
///
63+
/// [`.gitattributes`]: https://git-scm.com/docs/gitattributes
64+
///
65+
/// ### Example: Ignore all Whitespace Characters
66+
/// To make your migrations amenable to reformatting, you may wish to tell SQLx to ignore
67+
/// _all_ whitespace characters in migrations.
68+
///
69+
/// ##### Warning: Beware Syntatically Significant Whitespace!
70+
/// If your migrations use string literals or quoted identifiers which contain whitespace,
71+
/// this configuration will cause the migration machinery to ignore some changes to these.
72+
/// This may result in a mismatch between the development and production versions of
73+
/// your database.
74+
///
75+
/// `sqlx.toml`:
76+
/// ```toml
77+
/// [migrate]
78+
/// # Ignore common whitespace characters when hashing
79+
/// ignored_chars = " \t\r\n"
80+
/// ```
81+
#[serde(default)]
82+
pub ignored_chars: Box<str>,
83+
84+
/// Specify the default type of migration that `sqlx migrate create` should create by default.
85+
///
86+
/// ### Example: Use Reversible Migrations by Default
87+
/// `sqlx.toml`:
88+
/// ```toml
89+
/// [migrate]
90+
/// default_type = "reversible"
91+
/// ```
92+
pub default_type: DefaultMigrationType,
93+
94+
/// Specify the default scheme that `sqlx migrate create` should use for version integers.
95+
///
96+
/// ### Example: Use Sequential Versioning by Default
97+
/// `sqlx.toml`:
98+
/// ```toml
99+
/// [migrate]
100+
/// default_versioning = "sequential"
101+
/// ```
102+
pub default_versioning: DefaultVersioning,
103+
}
104+
105+
/// The default type of migration that `sqlx migrate create` should create by default.
106+
#[derive(Debug, Default, serde::Deserialize)]
107+
#[serde(rename_all = "snake_case")]
108+
pub enum DefaultMigrationType {
109+
/// Create the same migration type as that of the latest existing migration,
110+
/// or `Simple` otherwise.
111+
#[default]
112+
Inferred,
113+
114+
/// Create a non-reversible migration (`<VERSION>_<DESCRIPTION>.sql`).
115+
Simple,
116+
117+
/// Create a reversible migration (`<VERSION>_<DESCRIPTION>.up.sql` and `[...].down.sql`).
118+
Reversible
119+
}
120+
121+
/// The default scheme that `sqlx migrate create` should use for version integers.
122+
#[derive(Debug, Default, serde::Deserialize)]
123+
#[serde(rename_all = "snake_case")]
124+
pub enum DefaultVersioning {
125+
/// Infer the versioning scheme from existing migrations:
126+
///
127+
/// * If the versions of the last two migrations differ by `1`, infer `Sequential`.
128+
/// * If only one migration exists and has version `1`, infer `Sequential`.
129+
/// * Otherwise, infer `Timestamp`.
130+
#[default]
131+
Inferred,
132+
133+
/// Use UTC timestamps for migration versions.
134+
///
135+
/// This is the recommended versioning format as it's less likely to collide when multiple
136+
/// developers are creating migrations on different branches.
137+
///
138+
/// The exact timestamp format is unspecified.
139+
Timestamp,
140+
141+
/// Use sequential integers for migration versions.
142+
Sequential
143+
}

sqlx-core/src/config/mod.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//! Support for reading and caching configuration data from `sqlx.toml`
2+
3+
use std::fmt::Debug;
4+
use std::path::PathBuf;
5+
6+
#[cfg(feature = "config-macros")]
7+
pub mod macros;
8+
9+
#[cfg(feature = "config-migrate")]
10+
pub mod migrate;
11+
12+
/// The parsed structure of a `sqlx.toml` file.
13+
#[derive(Debug, serde::Deserialize)]
14+
pub struct Config {
15+
/// Configuration for the [`sqlx::query!()`] family of macros.
16+
///
17+
/// See type documentation for details.
18+
#[cfg_attr(docsrs, doc(cfg(any(feature = "config-all", feature = "config-macros"))))]
19+
#[cfg(feature = "config-macros")]
20+
pub macros: macros::Config,
21+
22+
/// Configuration for migrations when executed using `sqlx::migrate!()` or through `sqlx-cli`.
23+
///
24+
/// See type documentation for details.
25+
#[cfg_attr(docsrs, doc(cfg(any(feature = "config-all", feature = "config-migrate"))))]
26+
#[cfg(feature = "config-migrate")]
27+
pub migrate: migrate::Config,
28+
}
29+
30+
#[derive(thiserror::Error, Debug)]
31+
pub enum ConfigError {
32+
#[error("CARGO_MANIFEST_DIR must be set and valid")]
33+
Env(#[from] #[source] std::env::VarError),
34+
35+
#[error("error reading config file {path:?}")]
36+
Read {
37+
path: PathBuf,
38+
#[source] error: std::io::Error,
39+
},
40+
41+
#[error("error parsing config file {path:?}")]
42+
Parse {
43+
path: PathBuf,
44+
#[source] error: toml::de::Error,
45+
}
46+
}
47+
48+
#[doc(hidden)]
49+
#[allow(clippy::result_large_err)]
50+
impl Config {
51+
pub fn get() -> &'static Self {
52+
Self::try_get().unwrap()
53+
}
54+
55+
pub fn try_get() -> Result<&'static Self, ConfigError> {
56+
Self::try_get_with(|| {
57+
let mut path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);
58+
path.push("sqlx.toml");
59+
Ok(path)
60+
})
61+
}
62+
63+
pub fn try_get_with(make_path: impl FnOnce() -> Result<PathBuf, ConfigError>) -> Result<&'static Self, ConfigError> {
64+
// `std::sync::OnceLock` doesn't have a stable `.get_or_try_init()`
65+
// because it's blocked on a stable `Try` trait.
66+
use once_cell::sync::OnceCell;
67+
68+
static CACHE: OnceCell<Config> = OnceCell::new();
69+
70+
CACHE.get_or_try_init(|| {
71+
let path = make_path()?;
72+
Self::read_from(path)
73+
})
74+
}
75+
76+
fn read_from(path: PathBuf) -> Result<Self, ConfigError> {
77+
// The `toml` crate doesn't provide an incremental reader.
78+
let toml_s = match std::fs::read_to_string(&path) {
79+
Ok(toml) => toml,
80+
Err(error) => {
81+
return Err(ConfigError::Read {
82+
path,
83+
error
84+
});
85+
}
86+
};
87+
88+
tracing::debug!("read config TOML from {path:?}:\n{toml_s}");
89+
90+
toml::from_str(&toml_s).map_err(|error| {
91+
ConfigError::Parse {
92+
path,
93+
error,
94+
}
95+
})
96+
}
97+
}

sqlx-core/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ pub mod any;
9393
#[cfg(feature = "migrate")]
9494
pub mod testing;
9595

96+
#[cfg(feature = "config-toml")]
97+
pub mod config;
98+
9699
pub use error::{Error, Result};
97100

98101
pub use either::Either;

0 commit comments

Comments
 (0)