Skip to content

Commit 46dd260

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

File tree

11 files changed

+649
-40
lines changed

11 files changed

+649
-40
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"]
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

+4-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,9 @@ _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"]
3434

3535
[dependencies]
3636
# Runtimes

sqlx-core/src/config/macros.rs

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

0 commit comments

Comments
 (0)