Skip to content

Commit 8f87890

Browse files
committed
feat: create sqlx.toml format
1 parent 9c30aa7 commit 8f87890

File tree

12 files changed

+826
-105
lines changed

12 files changed

+826
-105
lines changed

Cargo.toml

+6-4
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ authors.workspace = true
5050
repository.workspace = true
5151

5252
[package.metadata.docs.rs]
53-
features = ["all-databases", "_unstable-all-types"]
53+
features = ["all-databases", "_unstable-all-types", "_unstable-doc"]
5454
rustdoc-args = ["--cfg", "docsrs"]
5555

5656
[features]
@@ -60,9 +60,9 @@ derive = ["sqlx-macros/derive"]
6060
macros = ["derive", "sqlx-macros/macros"]
6161
migrate = ["sqlx-core/migrate", "sqlx-macros?/migrate", "sqlx-mysql?/migrate", "sqlx-postgres?/migrate", "sqlx-sqlite?/migrate"]
6262

63-
# Enable parsing of `sqlx.toml` for configuring macros, migrations, or both
64-
config-macros = ["sqlx-core/config-macros"]
65-
config-migrate = ["sqlx-core/config-migrate"]
63+
# Enable parsing of `sqlx.toml` for configuring macros, migrations, or both.
64+
config-macros = ["sqlx-macros?/config-macros"]
65+
config-migrate = ["sqlx-macros?/config-migrate"]
6666
config-all = ["config-macros", "config-migrate"]
6767

6868
# intended mainly for CI and docs
@@ -78,6 +78,8 @@ _unstable-all-types = [
7878
"uuid",
7979
"bit-vec",
8080
]
81+
# Render documentation that wouldn't otherwise be shown (e.g. `sqlx_core::config`).
82+
_unstable-doc = ["config-all", "sqlx-core/_unstable-doc"]
8183

8284
# Base runtime features without TLS
8385
runtime-async-std = ["_rt-async-std", "sqlx-core/_rt-async-std", "sqlx-macros?/_rt-async-std"]

sqlx-core/Cargo.toml

+6-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ features = ["offline"]
1212

1313
[features]
1414
default = []
15-
migrate = ["sha2", "crc"]
15+
migrate = ["sha2", "crc", "config-migrate"]
1616

1717
any = []
1818

@@ -30,9 +30,11 @@ _tls-none = []
3030
# support offline/decoupled building (enables serialization of `Describe`)
3131
offline = ["serde", "either/serde"]
3232

33-
config-toml = ["serde", "toml/parse"]
34-
config-macros = ["config-toml"]
35-
config-migrate = ["config-toml"]
33+
config = ["serde", "toml/parse"]
34+
config-macros = ["config"]
35+
config-migrate = ["config"]
36+
37+
_unstable-doc = ["config-macros", "config-migrate"]
3638

3739
[dependencies]
3840
# Runtimes

sqlx-core/src/config/common.rs

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/// Configuration shared by multiple components.
2+
#[derive(Debug, Default, serde::Deserialize)]
3+
pub struct Config {
4+
/// Override the database URL environment variable.
5+
///
6+
/// This is used by both the macros and `sqlx-cli`.
7+
///
8+
/// Case-sensitive. Defaults to `DATABASE_URL`.
9+
///
10+
/// Example: Multi-Database Project
11+
/// -------
12+
/// You can use multiple databases in the same project by breaking it up into multiple crates,
13+
/// then using a different environment variable for each.
14+
///
15+
/// For example, with two crates in the workspace named `foo` and `bar`:
16+
///
17+
/// #### `foo/sqlx.toml`
18+
/// ```toml
19+
/// [macros]
20+
/// database_url_var = "FOO_DATABASE_URL"
21+
/// ```
22+
///
23+
/// #### `bar/sqlx.toml`
24+
/// ```toml
25+
/// [macros]
26+
/// database_url_var = "BAR_DATABASE_URL"
27+
/// ```
28+
///
29+
/// #### `.env`
30+
/// ```text
31+
/// FOO_DATABASE_URL=postgres://postgres@localhost:5432/foo
32+
/// BAR_DATABASE_URL=postgres://postgres@localhost:5432/bar
33+
/// ```
34+
///
35+
/// The query macros used in `foo` will use `FOO_DATABASE_URL`,
36+
/// and the ones used in `bar` will use `BAR_DATABASE_URL`.
37+
pub database_url_var: Option<String>,
38+
}

sqlx-core/src/config/macros.rs

+295-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,296 @@
1-
/// Configuration for the [`sqlx::query!()`] family of macros.
2-
#[derive(Debug, serde::Deserialize)]
1+
use std::collections::BTreeMap;
2+
3+
/// Configuration for the `query!()` family of macros.
4+
#[derive(Debug, Default, serde::Deserialize)]
5+
#[serde(default)]
36
pub struct Config {
4-
/// Override the environment variable
5-
pub database_url_var: Option<String>,
6-
}
7+
/// Specify the crate to use for mapping date/time types to Rust.
8+
///
9+
/// The default behavior is to use whatever crate is enabled,
10+
/// [`chrono`] or [`time`] (the latter takes precedent).
11+
///
12+
/// [`chrono`]: crate::types::chrono
13+
/// [`time`]: crate::types::time
14+
///
15+
/// Example: Always Use Chrono
16+
/// -------
17+
/// Thanks to Cargo's [feature unification], a crate in the dependency graph may enable
18+
/// the `time` feature of SQLx which will force it on for all crates using SQLx,
19+
/// which will result in problems if your crate wants to use types from [`chrono`].
20+
///
21+
/// You can use the type override syntax (see `sqlx::query!` for details),
22+
/// or you can force an override globally by setting this option.
23+
///
24+
/// #### `sqlx.toml`
25+
/// ```toml
26+
/// [macros]
27+
/// datetime_crate = "chrono"
28+
/// ```
29+
///
30+
/// [feature unification]: https://doc.rust-lang.org/cargo/reference/features.html#feature-unification
31+
pub datetime_crate: DateTimeCrate,
32+
33+
/// Specify global overrides for mapping SQL type names to Rust type names.
34+
///
35+
/// Default type mappings are defined by the database driver.
36+
/// Refer to the `sqlx::types` module for details.
37+
///
38+
/// ## Note: Orthogonal to Nullability
39+
/// These overrides do not affect whether `query!()` decides to wrap a column in `Option<_>`
40+
/// or not. They only override the inner type used.
41+
///
42+
/// ## Note: Schema Qualification (Postgres)
43+
/// Type names may be schema-qualified in Postgres. If so, the schema should be part
44+
/// of the type string, e.g. `'foo.bar'` to reference type `bar` in schema `foo`.
45+
///
46+
/// The schema and/or type name may additionally be quoted in the string
47+
/// for a quoted identifier (see next section).
48+
///
49+
/// Schema qualification should not be used for types in the search path.
50+
///
51+
/// ## Note: Quoted Identifiers (Postgres)
52+
/// Type names using [quoted identifiers in Postgres] must also be specified with quotes here.
53+
///
54+
/// Note, however, that the TOML format parses way the outer pair of quotes,
55+
/// so for quoted names in Postgres, double-quoting is necessary,
56+
/// e.g. `'"Foo"'` for SQL type `"Foo"`.
57+
///
58+
/// To reference a schema-qualified type with a quoted name, use double-quotes after the
59+
/// dot, e.g. `'foo."Bar"'` to reference type `"Bar"` of schema `foo`, and vice versa for
60+
/// quoted schema names.
61+
///
62+
/// We recommend wrapping all type names in single quotes, as shown below,
63+
/// to avoid confusion.
64+
///
65+
/// MySQL/MariaDB and SQLite do not support custom types, so quoting type names should
66+
/// never be necessary.
67+
///
68+
/// [quoted identifiers in Postgres]: https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
69+
// Note: we wanted to be able to handle this intelligently,
70+
// but the `toml` crate authors weren't interested: https://github.com/toml-rs/toml/issues/761
71+
//
72+
// We decided to just encourage always quoting type names instead.
73+
/// Example: Custom Wrapper Types
74+
/// -------
75+
/// Does SQLx not support a type that you need? Do you want additional semantics not
76+
/// implemented on the built-in types? You can create a custom wrapper,
77+
/// or use an external crate.
78+
///
79+
/// #### `sqlx.toml`
80+
/// ```toml
81+
/// [macros.type_overrides]
82+
/// # Override a built-in type
83+
/// 'uuid' = "crate::types::MyUuid"
84+
///
85+
/// # Support an external or custom wrapper type (e.g. from the `isn` Postgres extension)
86+
/// # (NOTE: FOR DOCUMENTATION PURPOSES ONLY; THIS CRATE/TYPE DOES NOT EXIST AS OF WRITING)
87+
/// 'isbn13' = "isn_rs::sqlx::ISBN13"
88+
/// ```
89+
///
90+
/// Example: Custom Types in Postgres
91+
/// -------
92+
/// If you have a custom type in Postgres that you want to map without needing to use
93+
/// the type override syntax in `sqlx::query!()` every time, you can specify a global
94+
/// override here.
95+
///
96+
/// For example, a custom enum type `foo`:
97+
///
98+
/// #### Migration or Setup SQL (e.g. `migrations/0_setup.sql`)
99+
/// ```sql
100+
/// CREATE TYPE foo AS ENUM ('Bar', 'Baz');
101+
/// ```
102+
///
103+
/// #### `src/types.rs`
104+
/// ```rust,no_run
105+
/// #[derive(sqlx::Type)]
106+
/// pub enum Foo {
107+
/// Bar,
108+
/// Baz
109+
/// }
110+
/// ```
111+
///
112+
/// If you're not using `PascalCase` in your enum variants then you'll want to use
113+
/// `#[sqlx(rename_all = "<strategy>")]` on your enum.
114+
/// See [`Type`][crate::type::Type] for details.
115+
///
116+
/// #### `sqlx.toml`
117+
/// ```toml
118+
/// [macros.type_overrides]
119+
/// # Map SQL type `foo` to `crate::types::Foo`
120+
/// 'foo' = "crate::types::Foo"
121+
/// ```
122+
///
123+
/// Example: Schema-Qualified Types
124+
/// -------
125+
/// (See `Note` section above for details.)
126+
///
127+
/// ```toml
128+
/// [macros.type_overrides]
129+
/// # Map SQL type `foo.foo` to `crate::types::Foo`
130+
/// 'foo.foo' = "crate::types::Foo"
131+
/// ```
132+
///
133+
/// Example: Quoted Identifiers
134+
/// -------
135+
/// If a type or schema uses quoted identifiers,
136+
/// it must be wrapped in quotes _twice_ for SQLx to know the difference:
137+
///
138+
/// ```toml
139+
/// [macros.type_overrides]
140+
/// # `"Foo"` in SQLx
141+
/// '"Foo"' = "crate::types::Foo"
142+
/// # **NOT** `"Foo"` in SQLx (parses as just `Foo`)
143+
/// "Foo" = "crate::types::Foo"
144+
///
145+
/// # Schema-qualified
146+
/// '"foo".foo' = "crate::types::Foo"
147+
/// 'foo."Foo"' = "crate::types::Foo"
148+
/// '"foo"."Foo"' = "crate::types::Foo"
149+
/// ```
150+
///
151+
/// (See `Note` section above for details.)
152+
pub type_overrides: BTreeMap<SqlType, RustType>,
153+
154+
/// Specify per-column overrides for mapping SQL types to Rust types.
155+
///
156+
/// Default type mappings are defined by the database driver.
157+
/// Refer to the `sqlx::types` module for details.
158+
///
159+
/// The supported syntax is similar to [`type_overrides`][Self::type_overrides],
160+
/// (with the same caveat for quoted names!) but column names must be qualified
161+
/// by a separately quoted table name, which may optionally be schema-qualified.
162+
///
163+
/// Multiple columns for the same SQL table may be written in the same table in TOML
164+
/// (see examples below).
165+
///
166+
/// ## Note: Orthogonal to Nullability
167+
/// These overrides do not affect whether `query!()` decides to wrap a column in `Option<_>`
168+
/// or not. They only override the inner type used.
169+
///
170+
/// ## Note: Schema Qualification
171+
/// Table names may be schema-qualified. If so, the schema should be part
172+
/// of the table name string, e.g. `'foo.bar'` to reference table `bar` in schema `foo`.
173+
///
174+
/// The schema and/or type name may additionally be quoted in the string
175+
/// for a quoted identifier (see next section).
176+
///
177+
/// Postgres users: schema qualification should not be used for tables in the search path.
178+
///
179+
/// ## Note: Quoted Identifiers
180+
/// Schema, table, or column names using quoted identifiers ([MySQL], [Postgres], [SQLite])
181+
/// in SQL must also be specified with quotes here.
182+
///
183+
/// Postgres and SQLite use double-quotes (`"Foo"`) while MySQL uses backticks (`\`Foo\`).
184+
///
185+
/// Note, however, that the TOML format parses way the outer pair of quotes,
186+
/// so for quoted names in Postgres, double-quoting is necessary,
187+
/// e.g. `'"Foo"'` for SQL name `"Foo"`.
188+
///
189+
/// To reference a schema-qualified table with a quoted name, use the appropriate quotation
190+
/// characters after the dot, e.g. `'foo."Bar"'` to reference table `"Bar"` of schema `foo`,
191+
/// and vice versa for quoted schema names.
192+
///
193+
/// We recommend wrapping all table and column names in single quotes, as shown below,
194+
/// to avoid confusion.
195+
///
196+
/// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/identifiers.html
197+
/// [Postgres]: https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
198+
/// [SQLite]: https://sqlite.org/lang_keywords.html
199+
// Note: we wanted to be able to handle this intelligently,
200+
// but the `toml` crate authors weren't interested: https://github.com/toml-rs/toml/issues/761
201+
//
202+
// We decided to just encourage always quoting type names instead.
203+
///
204+
/// Example
205+
/// -------
206+
///
207+
/// #### `sqlx.toml`
208+
/// ```toml
209+
/// [macros.column_overrides.'foo']
210+
/// # Map column `bar` of table `foo` to Rust type `crate::types::Foo`:
211+
/// 'bar' = "crate::types::Bar"
212+
///
213+
/// # Quoted column name
214+
/// # Note: same quoting requirements as `macros.type_overrides`
215+
/// '"Bar"' = "crate::types::Bar"
216+
///
217+
/// # Note: will NOT work (parses as `Bar`)
218+
/// # "Bar" = "crate::types::Bar"
219+
///
220+
/// # Table name may be quoted (note the wrapping single-quotes)
221+
/// [macros.column_overrides.'"Foo"']
222+
/// 'bar' = "crate::types::Bar"
223+
/// '"Bar"' = "crate::types::Bar"
224+
///
225+
/// # Table name may also be schema-qualified.
226+
/// # Note how the dot is inside the quotes.
227+
/// [macros.column_overrides.'my_schema.my_table']
228+
/// 'my_column' = "crate::types::MyType"
229+
///
230+
/// # Quoted schema, table, and column names
231+
/// [macros.column_overrides.'"My Schema"."My Table"']
232+
/// '"My Column"' = "crate::types::MyType"
233+
/// ```
234+
pub column_overrides: BTreeMap<TableName, BTreeMap<ColumnName, RustType>>,
235+
}
236+
237+
/// The crate to use for mapping date/time types to Rust.
238+
#[derive(Debug, Default, PartialEq, Eq, serde::Deserialize)]
239+
#[serde(rename_all = "snake_case")]
240+
pub enum DateTimeCrate {
241+
/// Use whichever crate is enabled (`time` then `chrono`).
242+
#[default]
243+
Inferred,
244+
245+
/// Always use types from [`chrono`][crate::types::chrono].
246+
///
247+
/// ```toml
248+
/// [macros]
249+
/// datetime_crate = "chrono"
250+
/// ```
251+
Chrono,
252+
253+
/// Always use types from [`time`][crate::types::time].
254+
///
255+
/// ```toml
256+
/// [macros]
257+
/// datetime_crate = "time"
258+
/// ```
259+
Time,
260+
}
261+
262+
/// A SQL type name; may optionally be schema-qualified.
263+
///
264+
/// See [`macros.type_overrides`][Config::type_overrides] for usages.
265+
pub type SqlType = Box<str>;
266+
267+
/// A SQL table name; may optionally be schema-qualified.
268+
///
269+
/// See [`macros.column_overrides`][Config::column_overrides] for usages.
270+
pub type TableName = Box<str>;
271+
272+
/// A column in a SQL table.
273+
///
274+
/// See [`macros.column_overrides`][Config::column_overrides] for usages.
275+
pub type ColumnName = Box<str>;
276+
277+
/// A Rust type name or path.
278+
///
279+
/// Should be a global path (not relative).
280+
pub type RustType = Box<str>;
281+
282+
/// Internal getter methods.
283+
impl Config {
284+
/// Get the override for a given type name (optionally schema-qualified).
285+
pub fn type_override(&self, type_name: &str) -> Option<&str> {
286+
self.type_overrides.get(type_name).map(|s| &**s)
287+
}
288+
289+
/// Get the override for a given column and table name (optionally schema-qualified).
290+
pub fn column_override(&self, table: &str, column: &str) -> Option<&str> {
291+
self.column_overrides
292+
.get(table)
293+
.and_then(|by_column| by_column.get(column))
294+
.map(|s| &**s)
295+
}
296+
}

0 commit comments

Comments
 (0)