Skip to content

Commit

Permalink
WIP feat: create sqlx.toml format
Browse files Browse the repository at this point in the history
  • Loading branch information
abonander committed Jul 28, 2024
1 parent 7c7cbee commit 46dd260
Show file tree
Hide file tree
Showing 11 changed files with 649 additions and 40 deletions.
10 changes: 6 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ authors.workspace = true
repository.workspace = true

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

[features]
Expand All @@ -58,9 +58,9 @@ derive = ["sqlx-macros/derive"]
macros = ["derive", "sqlx-macros/macros"]
migrate = ["sqlx-core/migrate", "sqlx-macros?/migrate", "sqlx-mysql?/migrate", "sqlx-postgres?/migrate", "sqlx-sqlite?/migrate"]

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

# intended mainly for CI and docs
Expand All @@ -76,6 +76,8 @@ _unstable-all-types = [
"uuid",
"bit-vec",
]
# Render documentation that wouldn't otherwise be shown (e.g. `sqlx_core::config`).
_unstable-doc = ["config-all"]

# Base runtime features without TLS
runtime-async-std = ["_rt-async-std", "sqlx-core/_rt-async-std", "sqlx-macros?/_rt-async-std"]
Expand Down
8 changes: 4 additions & 4 deletions sqlx-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ features = ["offline"]

[features]
default = []
migrate = ["sha2", "crc"]
migrate = ["sha2", "crc", "config-migrate"]

any = []

Expand All @@ -28,9 +28,9 @@ _tls-none = []
# support offline/decoupled building (enables serialization of `Describe`)
offline = ["serde", "either/serde"]

config-toml = ["serde", "toml/parse"]
config-macros = ["config-toml"]
config-migrate = ["config-toml"]
config = ["serde", "toml/parse"]
config-macros = ["config"]
config-migrate = ["config"]

[dependencies]
# Runtimes
Expand Down
316 changes: 313 additions & 3 deletions sqlx-core/src/config/macros.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,316 @@
use std::collections::BTreeMap;

/// Configuration for the [`sqlx::query!()`] family of macros.
#[derive(Debug, serde::Deserialize)]
#[derive(Debug, Default, serde::Deserialize)]
#[serde(default)]
pub struct Config {
/// Override the environment variable
/// Override the database URL environment variable used by the macros.
///
/// Case-sensitive. Defaults to `DATABASE_URL`.
///
/// ### Example: Multi-Database Project
/// You can use multiple databases in the same project by breaking it up into multiple crates,
/// then using a different environment variable for each.
///
/// For example, with two crates in the workspace named `foo` and `bar`:
///
/// ##### `foo/sqlx.toml`
/// ```toml
/// [macros]
/// database_url_var = "FOO_DATABASE_URL"
/// ```
///
/// ##### `bar/sqlx.toml`
/// ```toml
/// [macros]
/// database_url_var = "BAR_DATABASE_URL"
/// ```
///
/// ##### `.env`
/// ```text
/// FOO_DATABASE_URL=postgres://postgres@localhost:5432/foo
/// BAR_DATABASE_URL=postgres://postgres@localhost:5432/bar
/// ```
///
/// The query macros used in `foo` will use `FOO_DATABASE_URL`,
/// and the ones used in `bar` will use `BAR_DATABASE_URL`.
pub database_url_var: Option<String>,
}

/// Specify the crate to use for mapping date/time types to Rust.
///
/// The default behavior is to use whatever crate is enabled,
/// [`chrono`] or [`time`] (the latter takes precedent).
///
/// [`chrono`]: crate::types::chrono
/// [`time`]: crate::types::time
///
/// ### Example: Always Use Chrono
/// Thanks to Cargo's [feature unification], a crate in the dependency graph may enable
/// the `time` feature of SQLx which will force it on for all crates using SQLx,
/// which will result in problems if your crate wants to use types from [`chrono`].
///
/// You can use the type override syntax (see `sqlx::query!` for details),
/// or you can force an override globally by setting this option.
///
/// ##### `sqlx.toml`
/// ```toml
/// [macros]
/// datetime_crate = "chrono"
/// ```
///
/// [feature unification]: https://doc.rust-lang.org/cargo/reference/features.html#feature-unification
pub datetime_crate: DateTimeCrate,

/// Specify global overrides for mapping SQL type names to Rust type names.
///
/// ### Note: Orthogonal to Nullability
/// These overrides do not affect whether `query!()` decides to wrap a column in `Option<_>`
/// or not. They only override the inner type used.
///
/// ### Note: Schema Qualification (Postgres)
/// Type names may be schema-qualified in Postgres. If so, the schema should be part
/// of the type string, e.g. `'foo.bar'` to reference type `bar` in schema `foo`.
///
/// The schema and/or type name may additionally be quoted in the string
/// for a quoted identifier (see next section).
///
/// Postgres users: schema qualification should not be used for types in the search path.
///
/// ### Note: Quoted Identifiers (Postgres)
/// Type names using [quoted identifiers] in SQL must also be specified with quotes here.
///
/// Note, however, that the TOML format parses way the outer pair of quotes,
/// so for quoted names in SQL, double-quoting is necessary,
/// e.g. `'"Foo"'` for SQL type `"Foo"`.
///
/// To reference a schema-qualified type with a quoted name, use double-quotes after the
/// dot, e.g. `'foo."Bar"'` to reference type `"Bar"` of schema `foo`, and vice versa for
/// quoted schema names.
///
/// We recommend wrapping all type names in single quotes, as shown below,
/// to avoid confusion.
///
/// [quoted identifiers]: https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
// Note: we wanted to be able to handle this intelligently,
// but the `toml` crate authors weren't interested: https://github.com/toml-rs/toml/issues/761
//
// We decided to just encourage always quoting type names instead.
/// ### Example: Custom Wrapper Types
/// Does SQLx not support a type that you need? Do you want additional semantics not
/// implemented on the built-in types? You can create a custom wrapper,
/// or use an external crate.
///
/// ##### `sqlx.toml`
/// ```toml
/// [macros.type_overrides]
/// # Override a built-in type
/// 'uuid' = "crate::types::MyUuid"
///
/// # Support an external or custom wrapper type (e.g. from the `isn` Postgres extension)
/// # (NOTE: FOR DOCUMENTATION PURPOSES ONLY; THIS CRATE/TYPE DOES NOT EXIST AS OF WRITING)
/// 'isbn13' = "isn_rs::sqlx::ISBN13"
///
/// ### Example: Custom Types in Postgres
/// If you have a custom type in Postgres that you want to map without needing to use
/// the type override syntax in `sqlx::query!()` every time, you can specify a global
/// override here.
///
/// For example, a custom enum type `foo`:
///
/// ##### Migration or Setup SQL (e.g. `migrations/0_setup.sql`)
/// ```sql
/// CREATE TYPE foo AS ENUM ('Bar', 'Baz');
/// ```
///
/// ##### `src/types.rs`
/// ```rust,no_run
/// #[derive(sqlx::Type)]
/// pub enum Foo {
/// Bar,
/// Baz
/// }
/// ```
///
/// If you're not using `PascalCase` in your enum variants then you'll want to use
/// `#[sqlx(rename_all = "<strategy>")]` on your enum.
/// See [`Type`][crate::type::Type] for details.
///
/// ##### `sqlx.toml`
/// ```toml
/// [macros.type_overrides]
/// # Map SQL type `foo` to `crate::types::Foo`
/// 'foo' = "crate::types::Foo"
/// ```
///
/// ### Example: Schema-Qualified Types
/// (See `Note` section above for details.)
///
/// ```toml
/// [macros.type_overrides]
/// # Map SQL type `foo.foo` to `crate::types::Foo`
/// 'foo.foo' = "crate::types::Foo"
/// ```
///
/// ### Example: Quoted Identifiers
/// If a type or schema uses quoted identifiers,
/// it must be wrapped in quotes _twice_ for SQLx to know the difference:
///
/// ```toml
/// [macros.type_overrides]
/// # `"Foo"` in SQLx
/// '"Foo"' = "crate::types::Foo"
/// # **NOT** `"Foo"` in SQLx (parses as just `Foo`)
/// "Foo" = "crate::types::Foo"
///
/// # Schema-qualified
/// '"foo".foo' = "crate::types::Foo"
/// 'foo."Foo"' = "crate::types::Foo"
/// '"foo"."Foo"' = "crate::types::Foo"
/// ```
///
/// (See `Note` section above for details.)
pub type_overrides: BTreeMap<SqlType, RustType>,

/// Specify overrides for mapping SQL types to Rust types, per table and column.
///
/// The supported syntax is similar to [`type_overrides`][Self::type_overrides],
/// (with the same caveat for quoted names!) but column names must be qualified
/// by a separately quoted table name, which may optionally be schema-qualified.
///
/// For example, column `bar` of table `foo` should be written `'foo'.'bar'`.
///
/// Multiple columns for the same SQL table may be written in the same table in TOML
/// (see examples below).
///
/// ### Note: Orthogonal to Nullability
/// These overrides do not affect whether `query!()` decides to wrap a column in `Option<_>`
/// or not. They only override the inner type used.
///
/// ### Note: Schema Qualification (Postgres)
/// Table names may be schema-qualified in Postgres. If so, the schema should be part
/// of the table name string, e.g. `'foo.bar'` to reference table `bar` in schema `foo`.
///
/// The schema and/or type name may additionally be quoted in the string
/// for a quoted identifier (see next section).
///
/// Postgres users: schema qualification should not be used for types in the search path.
///
/// ### Note: Quoted Identifiers (Postgres)
/// Schema, table, or column names using [quoted identifiers] in SQL
/// must also be specified with quotes here.
///
/// Note, however, that the TOML format parses way the outer pair of quotes,
/// so for quoted names in SQL, double-quoting is necessary,
/// e.g. `'"Foo"'` for SQL name `"Foo"`.
///
/// To reference a schema-qualified table with a quoted name, use double-quotes after the
/// dot, e.g. `'foo."Bar"'` to reference table `"Bar"` of schema `foo`, and vice versa for
/// quoted schema names.
///
/// We recommend wrapping all table and column names in single quotes, as shown below,
/// to avoid confusion.
///
/// [quoted identifiers]: https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
// Note: we wanted to be able to handle this intelligently,
// but the `toml` crate authors weren't interested: https://github.com/toml-rs/toml/issues/761
//
// We decided to just encourage always quoting type names instead.
///
/// ### Example
///
/// ##### `sqlx.toml`
/// ```toml
/// [macros.table_overrides]
/// # Map column `bar` of table `foo` to Rust type `crate::types::Foo`:
/// 'foo'.'bar' = "crate::types::Bar"
///
/// # Quoted column name
/// # Note: same quoting requirements as `macros.type_overrides`
/// 'foo'.'"Bar"' = "crate::types::Bar"
///
/// # Schema-qualified
/// # Note how the schema and table are together in a single string.
/// # Map column `baz` of table `foo.bar` to Rust type `crate::types::Baz`
/// 'foo.bar'.'baz' = "crate::types::Baz"
///
/// # Schema, table and/or column names may be quoted
/// '"Foo"."Bar"'.'"Baz"' = "crate::types::Baz"
///
/// # Multiple columns in the same table (`bar`)
/// [macros.table_overrides.'bar']
/// # bar.foo
/// 'foo' = "crate::schema::bar::Foo"
/// # bar."Bar"
/// '"Bar"' = "crate::schema::bar::Bar"
///
/// # May also be schema-qualified
/// # Note how the schema and table are together in a single string.
/// [macros.column_overrides.'my_schema.my_table']
/// # my_schema.my_table.my_column
/// 'my_column' = "crate::types::MyType"
///
/// # And quoted
/// [macros.column_overrides.'"My Schema"."My Table"']
/// # "My Schema"."My Table"."My Column"
/// '"My Column"' = "crate::types::MyType"
/// ```
pub table_overrides: BTreeMap<TableName, BTreeMap<ColumnName, RustType>>,
}

/// The crate to use for mapping date/time types to Rust.
#[derive(Debug, Default, PartialEq, Eq, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DateTimeCrate {
/// Use whichever crate is enabled (`time` then `chrono`).
#[default]
Inferred,

/// Always use types from [`chrono`][crate::types::chrono].
///
/// ```toml
/// [macros]
/// datetime_crate = "chrono"
/// ```
Chrono,

/// Always use types from [`time`][crate::types::time].
///
/// ```toml
/// [macros]
/// datetime_crate = "time"
/// ```
Time
}

/// A SQL type name; may optionally be schema-qualified.
///
/// See [`macros.type_overrides`][Config::type_overrides] for usages.
pub type SqlType = Box<str>;

/// A SQL table name; may optionally be schema-qualified.
///
/// See [`macros.table_overrides`][Config::table_overrides] for usages.
pub type TableName = Box<str>;

/// A column in a SQL table.
///
/// See [`macros.table_overrides`][Config::table_overrides] for usages.
pub type ColumnName = Box<str>;

/// A Rust type name or path.
///
/// Should be a global path (not relative).
pub type RustType = Box<str>;

#[doc(hidden)]
impl Config {
pub fn type_override(&self, type_name: &str) -> Option<&str> {
self.type_overrides.get(type_name).map(|s| &**s)
}

pub fn table_override(&self, table: &str, column: &str) -> Option<&str> {
self.table_overrides.get(table)
.and_then(|by_column| by_column.get(column))
.map(|s| &**s)
}
}
Loading

0 comments on commit 46dd260

Please sign in to comment.