Skip to content

Commit fd557ad

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

File tree

11 files changed

+697
-98
lines changed

11 files changed

+697
-98
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

+303-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,306 @@
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 per-column overrides for mapping SQL types to Rust types.
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+
/// Multiple columns for the same SQL table may be written in the same table in TOML
181+
/// (see examples below).
182+
///
183+
/// ### Note: Orthogonal to Nullability
184+
/// These overrides do not affect whether `query!()` decides to wrap a column in `Option<_>`
185+
/// or not. They only override the inner type used.
186+
///
187+
/// ### Note: Schema Qualification (Postgres)
188+
/// Table names may be schema-qualified in Postgres. If so, the schema should be part
189+
/// of the table name string, e.g. `'foo.bar'` to reference table `bar` in schema `foo`.
190+
///
191+
/// The schema and/or type name may additionally be quoted in the string
192+
/// for a quoted identifier (see next section).
193+
///
194+
/// Postgres users: schema qualification should not be used for tables in the search path.
195+
///
196+
/// ### Note: Quoted Identifiers (Postgres)
197+
/// Schema, table, or column names using [quoted identifiers] in SQL
198+
/// must also be specified with quotes here.
199+
///
200+
/// Note, however, that the TOML format parses way the outer pair of quotes,
201+
/// so for quoted names in SQL, double-quoting is necessary,
202+
/// e.g. `'"Foo"'` for SQL name `"Foo"`.
203+
///
204+
/// To reference a schema-qualified table with a quoted name, use double-quotes after the
205+
/// dot, e.g. `'foo."Bar"'` to reference table `"Bar"` of schema `foo`, and vice versa for
206+
/// quoted schema names.
207+
///
208+
/// We recommend wrapping all table and column names in single quotes, as shown below,
209+
/// to avoid confusion.
210+
///
211+
/// [quoted identifiers]: https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
212+
// Note: we wanted to be able to handle this intelligently,
213+
// but the `toml` crate authors weren't interested: https://github.com/toml-rs/toml/issues/761
214+
//
215+
// We decided to just encourage always quoting type names instead.
216+
///
217+
/// ### Example
218+
///
219+
/// ##### `sqlx.toml`
220+
/// ```toml
221+
/// [macros.column_overrides.'foo']
222+
/// # Map column `bar` of table `foo` to Rust type `crate::types::Foo`:
223+
/// 'bar' = "crate::types::Bar"
224+
///
225+
/// # Quoted column name
226+
/// # Note: same quoting requirements as `macros.type_overrides`
227+
/// '"Bar"' = "crate::types::Bar"
228+
///
229+
/// # Note: will NOT work (parses as `Bar`)
230+
/// # "Bar" = "crate::types::Bar"
231+
///
232+
/// # Table name may be quoted (note the wrapping single-quotes)
233+
/// [macros.column_overrides.'"Foo"']
234+
/// 'bar' = "crate::types::Bar"
235+
/// '"Bar"' = "crate::types::Bar"
236+
///
237+
/// # Table name may also be schema-qualified.
238+
/// # Note how the dot is inside the quotes.
239+
/// [macros.column_overrides.'my_schema.my_table']
240+
/// 'my_column' = "crate::types::MyType"
241+
///
242+
/// # Quoted schema, table, and column names
243+
/// [macros.column_overrides.'"My Schema"."My Table"']
244+
/// '"My Column"' = "crate::types::MyType"
245+
/// ```
246+
pub column_overrides: BTreeMap<TableName, BTreeMap<ColumnName, RustType>>,
247+
}
248+
249+
/// The crate to use for mapping date/time types to Rust.
250+
#[derive(Debug, Default, PartialEq, Eq, serde::Deserialize)]
251+
#[serde(rename_all = "snake_case")]
252+
pub enum DateTimeCrate {
253+
/// Use whichever crate is enabled (`time` then `chrono`).
254+
#[default]
255+
Inferred,
256+
257+
/// Always use types from [`chrono`][crate::types::chrono].
258+
///
259+
/// ```toml
260+
/// [macros]
261+
/// datetime_crate = "chrono"
262+
/// ```
263+
Chrono,
264+
265+
/// Always use types from [`time`][crate::types::time].
266+
///
267+
/// ```toml
268+
/// [macros]
269+
/// datetime_crate = "time"
270+
/// ```
271+
Time,
272+
}
273+
274+
/// A SQL type name; may optionally be schema-qualified.
275+
///
276+
/// See [`macros.type_overrides`][Config::type_overrides] for usages.
277+
pub type SqlType = Box<str>;
278+
279+
/// A SQL table name; may optionally be schema-qualified.
280+
///
281+
/// See [`macros.table_overrides`][Config::table_overrides] for usages.
282+
pub type TableName = Box<str>;
283+
284+
/// A column in a SQL table.
285+
///
286+
/// See [`macros.table_overrides`][Config::table_overrides] for usages.
287+
pub type ColumnName = Box<str>;
288+
289+
/// A Rust type name or path.
290+
///
291+
/// Should be a global path (not relative).
292+
pub type RustType = Box<str>;
293+
294+
#[doc(hidden)]
295+
impl Config {
296+
pub fn type_override(&self, type_name: &str) -> Option<&str> {
297+
self.type_overrides.get(type_name).map(|s| &**s)
298+
}
299+
300+
pub fn column_override(&self, table: &str, column: &str) -> Option<&str> {
301+
self.column_overrides
302+
.get(table)
303+
.and_then(|by_column| by_column.get(column))
304+
.map(|s| &**s)
305+
}
306+
}

0 commit comments

Comments
 (0)