Skip to content

Commit 6c886f8

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

File tree

11 files changed

+784
-102
lines changed

11 files changed

+784
-102
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 = []
3436

3537
[dependencies]
3638
# Runtimes

sqlx-core/src/config/macros.rs

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

0 commit comments

Comments
 (0)