Skip to content

Commit e14c737

Browse files
committed
WIP feat: create sqlx.toml format
1 parent 7c7cbee commit e14c737

File tree

11 files changed

+806
-104
lines changed

11 files changed

+806
-104
lines changed

Cargo.toml

+6-4
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ authors.workspace = true
4848
repository.workspace = true
4949

5050
[package.metadata.docs.rs]
51-
features = ["all-databases", "_unstable-all-types"]
51+
features = ["all-databases", "_unstable-all-types", "_unstable-doc"]
5252
rustdoc-args = ["--cfg", "docsrs"]
5353

5454
[features]
@@ -58,9 +58,9 @@ 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"]
61+
# Enable parsing of `sqlx.toml` for configuring macros, migrations, or both.
62+
config-macros = ["sqlx-macros?/config-macros"]
63+
config-migrate = ["sqlx-macros?/config-migrate"]
6464
config-all = ["config-macros", "config-migrate"]
6565

6666
# intended mainly for CI and docs
@@ -76,6 +76,8 @@ _unstable-all-types = [
7676
"uuid",
7777
"bit-vec",
7878
]
79+
# Render documentation that wouldn't otherwise be shown (e.g. `sqlx_core::config`).
80+
_unstable-doc = ["config-all", "sqlx-core/_unstable-doc"]
7981

8082
# Base runtime features without TLS
8183
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

@@ -28,9 +28,11 @@ _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"]
31+
config = ["serde", "toml/parse"]
32+
config-macros = ["config"]
33+
config-migrate = ["config"]
34+
35+
_unstable-doc = ["config-macros", "config-migrate"]
3436

3537
[dependencies]
3638
# Runtimes

sqlx-core/src/config/macros.rs

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

0 commit comments

Comments
 (0)