mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-12-29 21:00:54 +00:00
Merge remote-tracking branch 'origin/ab/macro-refresh'
This commit is contained in:
commit
86d41f17aa
@ -16,15 +16,6 @@ mod database;
|
||||
mod derives;
|
||||
mod query;
|
||||
|
||||
fn macro_result(tokens: proc_macro2::TokenStream) -> TokenStream {
|
||||
quote!(
|
||||
macro_rules! macro_result {
|
||||
($($args:tt)*) => (#tokens)
|
||||
}
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn expand_query(input: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(input as query::QueryMacroInput);
|
||||
@ -33,10 +24,10 @@ pub fn expand_query(input: TokenStream) -> TokenStream {
|
||||
Ok(ts) => ts.into(),
|
||||
Err(e) => {
|
||||
if let Some(parse_err) = e.downcast_ref::<syn::Error>() {
|
||||
macro_result(parse_err.to_compile_error())
|
||||
parse_err.to_compile_error().into()
|
||||
} else {
|
||||
let msg = e.to_string();
|
||||
macro_result(quote!(compile_error!(#msg)))
|
||||
quote!(compile_error!(#msg)).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,10 +2,10 @@ use crate::database::DatabaseExt;
|
||||
use crate::query::QueryMacroInput;
|
||||
use either::Either;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, quote_spanned};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use sqlx_core::describe::Describe;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Expr, Type};
|
||||
use syn::{Expr, ExprCast, ExprGroup, ExprType, Type};
|
||||
|
||||
/// Returns a tokenstream which typechecks the arguments passed to the macro
|
||||
/// and binds them to `DB::Arguments` with the ident `query_args`.
|
||||
@ -15,13 +15,22 @@ pub fn quote_args<DB: DatabaseExt>(
|
||||
) -> crate::Result<TokenStream> {
|
||||
let db_path = DB::db_path();
|
||||
|
||||
if input.arg_names.is_empty() {
|
||||
if input.arg_exprs.is_empty() {
|
||||
return Ok(quote! {
|
||||
let query_args = <#db_path as sqlx::database::HasArguments>::Arguments::default();
|
||||
});
|
||||
}
|
||||
|
||||
let arg_name = &input.arg_names;
|
||||
let arg_names = (0..input.arg_exprs.len())
|
||||
.map(|i| format_ident!("arg{}", i))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let arg_name = &arg_names;
|
||||
let arg_expr = input.arg_exprs.iter().cloned().map(strip_wildcard);
|
||||
|
||||
let arg_bindings = quote! {
|
||||
#(let ref #arg_name = #arg_expr;)*
|
||||
};
|
||||
|
||||
let args_check = match info.parameters() {
|
||||
None | Some(Either::Right(_)) => {
|
||||
@ -29,22 +38,20 @@ pub fn quote_args<DB: DatabaseExt>(
|
||||
TokenStream::new()
|
||||
}
|
||||
|
||||
Some(Either::Left(_)) if !input.checked => {
|
||||
// this is an `*_unchecked!()` macro invocation
|
||||
TokenStream::new()
|
||||
}
|
||||
|
||||
Some(Either::Left(params)) => {
|
||||
params
|
||||
.iter()
|
||||
.zip(input.arg_names.iter().zip(&input.arg_exprs))
|
||||
.zip(arg_names.iter().zip(&input.arg_exprs))
|
||||
.enumerate()
|
||||
.map(|(i, (param_ty, (name, expr)))| -> crate::Result<_> {
|
||||
let param_ty = match get_type_override(expr) {
|
||||
// TODO: enable this in 1.45 when we can strip `as _`
|
||||
// without stripping these we get some pretty nasty type errors
|
||||
Some(Type::Infer(_)) => return Err(
|
||||
syn::Error::new_spanned(
|
||||
expr,
|
||||
"casts to `_` are not allowed in bind parameters yet"
|
||||
).into()
|
||||
),
|
||||
// cast or type ascription will fail to compile if the type does not match
|
||||
// and we strip casts to wildcard
|
||||
Some(_) => return Ok(quote!()),
|
||||
None => {
|
||||
DB::param_type_for_id(¶m_ty)
|
||||
@ -72,14 +79,14 @@ pub fn quote_args<DB: DatabaseExt>(
|
||||
use sqlx::ty_match::{WrapSameExt as _, MatchBorrowExt as _};
|
||||
|
||||
// evaluate the expression only once in case it contains moves
|
||||
let _expr = sqlx::ty_match::dupe_value(&$#name);
|
||||
let _expr = sqlx::ty_match::dupe_value(#name);
|
||||
|
||||
// if `_expr` is `Option<T>`, get `Option<$ty>`, otherwise `$ty`
|
||||
let ty_check = sqlx::ty_match::WrapSame::<#param_ty, _>::new(&_expr).wrap_same();
|
||||
// if `_expr` is `&str`, convert `String` to `&str`
|
||||
let (mut ty_check, match_borrow) = sqlx::ty_match::MatchBorrow::new(ty_check, &_expr);
|
||||
let (mut _ty_check, match_borrow) = sqlx::ty_match::MatchBorrow::new(ty_check, &_expr);
|
||||
|
||||
ty_check = match_borrow.match_borrow();
|
||||
_ty_check = match_borrow.match_borrow();
|
||||
|
||||
// this causes move-analysis to effectively ignore this block
|
||||
panic!();
|
||||
@ -90,13 +97,13 @@ pub fn quote_args<DB: DatabaseExt>(
|
||||
}
|
||||
};
|
||||
|
||||
let args_count = input.arg_names.len();
|
||||
let args_count = input.arg_exprs.len();
|
||||
|
||||
Ok(quote! {
|
||||
#arg_bindings
|
||||
|
||||
#args_check
|
||||
|
||||
// bind as a local expression, by-ref
|
||||
#(let #arg_name = &$#arg_name;)*
|
||||
let mut query_args = <#db_path as sqlx::database::HasArguments>::Arguments::default();
|
||||
query_args.reserve(
|
||||
#args_count,
|
||||
@ -114,3 +121,36 @@ fn get_type_override(expr: &Expr) -> Option<&Type> {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn strip_wildcard(expr: Expr) -> Expr {
|
||||
match expr {
|
||||
Expr::Group(ExprGroup {
|
||||
attrs,
|
||||
group_token,
|
||||
expr,
|
||||
}) => Expr::Group(ExprGroup {
|
||||
attrs,
|
||||
group_token,
|
||||
expr: Box::new(strip_wildcard(*expr)),
|
||||
}),
|
||||
// type ascription syntax is experimental so we always strip it
|
||||
Expr::Type(ExprType { expr, .. }) => *expr,
|
||||
// we want to retain casts if they semantically matter
|
||||
Expr::Cast(ExprCast {
|
||||
attrs,
|
||||
expr,
|
||||
as_token,
|
||||
ty,
|
||||
}) => match *ty {
|
||||
// cast to wildcard `_` will produce weird errors; we interpret it as taking the value as-is
|
||||
Type::Infer(_) => *expr,
|
||||
_ => Expr::Cast(ExprCast {
|
||||
attrs,
|
||||
expr,
|
||||
as_token,
|
||||
ty,
|
||||
}),
|
||||
},
|
||||
_ => expr,
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ use std::env;
|
||||
use std::fs;
|
||||
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::format_ident;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{Expr, LitBool, LitStr, Token};
|
||||
@ -17,8 +16,6 @@ pub struct QueryMacroInput {
|
||||
|
||||
pub(super) record_type: RecordType,
|
||||
|
||||
// `arg0 .. argN` for N arguments
|
||||
pub(super) arg_names: Vec<Ident>,
|
||||
pub(super) arg_exprs: Vec<Expr>,
|
||||
|
||||
pub(super) checked: bool,
|
||||
@ -82,15 +79,11 @@ impl Parse for QueryMacroInput {
|
||||
query_src.ok_or_else(|| input.error("expected `source` or `source_file` key"))?;
|
||||
|
||||
let arg_exprs = args.unwrap_or_default();
|
||||
let arg_names = (0..arg_exprs.len())
|
||||
.map(|i| format_ident!("arg{}", i))
|
||||
.collect();
|
||||
|
||||
Ok(QueryMacroInput {
|
||||
src: src.resolve(src_span)?,
|
||||
src_span,
|
||||
record_type,
|
||||
arg_names,
|
||||
arg_exprs,
|
||||
checked,
|
||||
})
|
||||
|
||||
@ -195,10 +195,10 @@ where
|
||||
};
|
||||
|
||||
if let Some(num) = num_parameters {
|
||||
if num != input.arg_names.len() {
|
||||
if num != input.arg_exprs.len() {
|
||||
return Err(syn::Error::new(
|
||||
Span::call_site(),
|
||||
format!("expected {} parameters, got {}", num, input.arg_names.len()),
|
||||
format!("expected {} parameters, got {}", num, input.arg_exprs.len()),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
@ -260,19 +260,13 @@ where
|
||||
record_tokens
|
||||
};
|
||||
|
||||
let arg_names = &input.arg_names;
|
||||
let ret_tokens = quote! {{
|
||||
use sqlx::Arguments as _;
|
||||
|
||||
let ret_tokens = quote! {
|
||||
macro_rules! macro_result {
|
||||
(#($#arg_names:expr),*) => {{
|
||||
use sqlx::Arguments as _;
|
||||
#args_tokens
|
||||
|
||||
#args_tokens
|
||||
|
||||
#output
|
||||
}}
|
||||
}
|
||||
};
|
||||
#output
|
||||
}};
|
||||
|
||||
#[cfg(feature = "offline")]
|
||||
{
|
||||
|
||||
@ -98,16 +98,21 @@ pub fn quote_query_as<DB: DatabaseExt>(
|
||||
match (input.checked, type_) {
|
||||
// we guarantee the type is valid so we can skip the runtime check
|
||||
(true, Some(type_)) => quote! {
|
||||
#ident: row.try_get_unchecked::<#type_, _>(#i).try_unwrap_optional()?
|
||||
// binding to a `let` avoids confusing errors about
|
||||
// "try expression alternatives have incompatible types"
|
||||
// it doesn't seem to hurt inference in the other branches
|
||||
let #ident = row.try_get_unchecked::<#type_, _>(#i)?;
|
||||
},
|
||||
// type was overridden to be a wildcard so we fallback to the runtime check
|
||||
(true, None) => quote! ( #ident: row.try_get(#i)? ),
|
||||
(true, None) => quote! ( let #ident = row.try_get(#i)?; ),
|
||||
// macro is the `_unchecked!()` variant so this will die in decoding if it's wrong
|
||||
(false, _) => quote!( #ident: row.try_get_unchecked(#i)? ),
|
||||
(false, _) => quote!( let #ident = row.try_get_unchecked(#i)?; ),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let ident = columns.iter().map(|col| &col.ident);
|
||||
|
||||
let db_path = DB::db_path();
|
||||
let row_path = DB::row_path();
|
||||
let sql = &input.src;
|
||||
@ -115,9 +120,10 @@ pub fn quote_query_as<DB: DatabaseExt>(
|
||||
quote! {
|
||||
sqlx::query_with::<#db_path, _>(#sql, #bind_args).try_map(|row: #row_path| {
|
||||
use sqlx::Row as _;
|
||||
use sqlx::result_ext::ResultExt as _;
|
||||
|
||||
Ok(#out_ty { #(#instantiations),* })
|
||||
#(#instantiations)*
|
||||
|
||||
Ok(#out_ty { #(#ident: #ident),* })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,10 +71,6 @@ mod macros;
|
||||
#[doc(hidden)]
|
||||
pub mod ty_match;
|
||||
|
||||
#[cfg(feature = "macros")]
|
||||
#[doc(hidden)]
|
||||
pub mod result_ext;
|
||||
|
||||
/// Conversions between Rust and SQL types.
|
||||
///
|
||||
/// To see how each SQL type maps to a Rust type, see the corresponding `types` module for each
|
||||
|
||||
248
src/macros.rs
248
src/macros.rs
@ -1,7 +1,7 @@
|
||||
/// Statically checked SQL query with `println!()` style syntax.
|
||||
///
|
||||
/// This expands to an instance of [`QueryAs`](sqlx_core::query_as::QueryAs) that outputs an ad-hoc anonymous struct type,
|
||||
/// if the query has output columns, or `()` (unit) otherwise:
|
||||
/// This expands to an instance of [`query::Map`][crate::query::Map] that outputs an ad-hoc anonymous
|
||||
/// struct type, if the query has output columns, or `()` (unit) otherwise:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// # use sqlx::Connect;
|
||||
@ -36,8 +36,9 @@
|
||||
/// * Or, `sqlx-data.json` must exist at the workspace root. See [Offline Mode](#offline-mode)
|
||||
/// below.
|
||||
///
|
||||
/// * The query must be a string literal or else it cannot be introspected (and thus cannot
|
||||
/// be dynamic or the result of another macro).
|
||||
/// * The query must be a string literal, or concatenation of string literals using `+` (useful
|
||||
/// for queries generated by macro), or else it cannot be introspected (and thus cannot be dynamic
|
||||
/// or the result of another macro).
|
||||
///
|
||||
/// * The `QueryAs` instance will be bound to the same database type as `query!()` was compiled
|
||||
/// against (e.g. you cannot build against a Postgres database and then run the query against
|
||||
@ -81,7 +82,7 @@
|
||||
/// Bind parameters in the SQL string are specific to the database backend:
|
||||
///
|
||||
/// * Postgres: `$N` where `N` is the 1-based positional argument index
|
||||
/// * MySQL: `?` which matches arguments in order that it appears in the query
|
||||
/// * MySQL/SQLite: `?` which matches arguments in order that it appears in the query
|
||||
///
|
||||
/// ## Nullability: Bind Parameters
|
||||
/// For a given expected type `T`, both `T` and `Option<T>` are allowed (as well as either
|
||||
@ -90,14 +91,17 @@
|
||||
///
|
||||
/// Note, however, if binding in a `where` clause, that equality comparisons with `NULL` may not
|
||||
/// work as expected; instead you must use `IS NOT NULL` or `IS NULL` to check if a column is not
|
||||
/// null or is null, respectively. Note that `IS [NOT] NULL` cannot be bound as a parameter either;
|
||||
/// you must modify your query string instead.
|
||||
/// null or is null, respectively.
|
||||
///
|
||||
/// In Postgres and MySQL you may also use `IS [NOT] DISTINCT FROM` to compare with a possibly
|
||||
/// `NULL` value. In MySQL `IS NOT DISTINCT FROM` can be shortened to `<=>`.
|
||||
/// In SQLite you can us `IS` or `IS NOT`. Note that operator precedence may be different.
|
||||
///
|
||||
/// ## Nullability: Output Columns
|
||||
/// In most cases, the database engine can tell us whether or not a column may be `NULL`, and
|
||||
/// the `query!()` macro adjusts the field types of the returned struct accordingly.
|
||||
///
|
||||
/// For Postgres and SQLite, this only works for columns which come directly from actual tables,
|
||||
/// For Postgres, this only works for columns which come directly from actual tables,
|
||||
/// as the implementation will need to query the table metadata to find if a given column
|
||||
/// has a `NOT NULL` constraint. Columns that do not have a `NOT NULL` constraint or are the result
|
||||
/// of an expression are assumed to be nullable and so `Option<T>` is used instead of `T`.
|
||||
@ -111,7 +115,11 @@
|
||||
/// `NULL` which then depends on the semantics of what functions are used. Consult the MySQL
|
||||
/// manual for the functions you are using to find the cases in which they return `NULL`.
|
||||
///
|
||||
/// To override the nullability of an output column, use [query_as!], or see below.
|
||||
/// For SQLite we perform a similar check to Postgres, looking for `NOT NULL` constraints
|
||||
/// on columns that come from tables. However, for SQLite we also can step through the output
|
||||
/// of `EXPLAIN` to identify columns that may or may not be `NULL`.
|
||||
///
|
||||
/// To override the nullability of an output column, [see below](#type-overrides-output-columns).
|
||||
///
|
||||
/// ## Type Overrides: Bind Parameters (Postgres only)
|
||||
/// For typechecking of bind parameters, casts using `as` are treated as overrides for the inferred
|
||||
@ -136,7 +144,8 @@
|
||||
/// Type overrides are also available for output columns, utilizing the SQL standard's support
|
||||
/// for arbitrary text in column names:
|
||||
///
|
||||
/// * selecting a column `foo as "foo!"` (Postgres / SQLite) or `` foo as `foo!` `` overrides
|
||||
/// ##### Force Not-Null
|
||||
/// Selecting a column `foo as "foo!"` (Postgres / SQLite) or `` foo as `foo!` `` (MySQL) overrides
|
||||
/// inferred nullability and forces the column to be treated as `NOT NULL`; this is useful e.g. for
|
||||
/// selecting expressions in Postgres where we cannot infer nullability:
|
||||
///
|
||||
@ -154,7 +163,9 @@
|
||||
/// # }
|
||||
///
|
||||
/// ```
|
||||
/// * selecting a column `foo as "foo?"` (Postgres / SQLite) or `` foo as `foo?` `` overrides
|
||||
///
|
||||
/// ##### Force Nullable
|
||||
/// Selecting a column `foo as "foo?"` (Postgres / SQLite) or `` foo as `foo?` `` (MySQL) overrides
|
||||
/// inferred nullability and forces the column to be treated as nullable; this is provided mainly
|
||||
/// for symmetry with `!`, but also because nullability inference currently has some holes and false
|
||||
/// negatives that may not be completely fixable without doing our own complex analysis on the given
|
||||
@ -200,7 +211,8 @@
|
||||
///
|
||||
/// See [launchbadge/sqlx#367](https://github.com/launchbadge/sqlx/issues/367) for more details on this issue.
|
||||
///
|
||||
/// * selecting a column `foo as "foo: T"` (Postgres / SQLite) or `` foo as `foo: T` `` (MySQL)
|
||||
/// ##### Force a Different/Custom Type
|
||||
/// Selecting a column `foo as "foo: T"` (Postgres / SQLite) or `` foo as `foo: T` `` (MySQL)
|
||||
/// overrides the inferred type which is useful when selecting user-defined custom types
|
||||
/// (dynamic type checking is still done so if the types are incompatible this will be an error
|
||||
/// at runtime instead of compile-time):
|
||||
@ -219,15 +231,11 @@
|
||||
/// .fetch_one(&mut conn)
|
||||
/// .await?;
|
||||
///
|
||||
/// // For Postgres this would have been inferred to be `Option<i32>`, MySQL `i32`
|
||||
/// // and SQLite it wouldn't have worked at all because we couldn't know the type.
|
||||
/// // For Postgres this would have been inferred to be `Option<i32>`, MySQL/SQLite `i32`
|
||||
/// assert_eq!(record.id, MyInt4(1));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// As mentioned, this allows specifying the type of a pure expression column which is normally
|
||||
/// forbidden for SQLite as there's no way we can ask SQLite what type the column is expected to be.
|
||||
///
|
||||
/// ## Offline Mode (requires the `offline` feature)
|
||||
/// The macros can be configured to not require a live database connection for compilation,
|
||||
/// but it requires a couple extra steps:
|
||||
@ -255,21 +263,20 @@
|
||||
#[macro_export]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
||||
macro_rules! query (
|
||||
// by emitting a macro definition from our proc-macro containing the result tokens,
|
||||
// we no longer have a need for `proc-macro-hack`
|
||||
// in Rust 1.45 we can now invoke proc macros in expression position
|
||||
($query:expr) => ({
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::expand_query!(source = $query);
|
||||
}
|
||||
macro_result!()
|
||||
$crate::sqlx_macros::expand_query!(source = $query)
|
||||
});
|
||||
($query:expr, $($args:expr),*$(,)?) => ({
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::expand_query!(source = $query, args = [$($args),*]);
|
||||
}
|
||||
macro_result!($($args),*)
|
||||
// RFC: this semantically should be `$($args:expr),*` (with `$(,)?` to allow trailing comma)
|
||||
// but that doesn't work in 1.45 because `expr` fragments get wrapped in a way that changes
|
||||
// their hygiene, which is fixed in 1.46 so this is technically just a temp. workaround.
|
||||
// My question is: do we care?
|
||||
// I was hoping using the `expr` fragment might aid code completion but it doesn't in my
|
||||
// experience, at least not with IntelliJ-Rust at the time of writing (version 0.3.126.3220-201)
|
||||
// so really the only benefit is making the macros _slightly_ self-documenting, but it's
|
||||
// not like it makes them magically understandable at-a-glance.
|
||||
($query:expr, $($args:tt)*) => ({
|
||||
$crate::sqlx_macros::expand_query!(source = $query, args = [$($args)*])
|
||||
})
|
||||
);
|
||||
|
||||
@ -279,18 +286,10 @@ macro_rules! query (
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
||||
macro_rules! query_unchecked (
|
||||
($query:expr) => ({
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::expand_query!(source = $query, checked = false);
|
||||
}
|
||||
macro_result!()
|
||||
$crate::sqlx_macros::expand_query!(source = $query, checked = false)
|
||||
});
|
||||
($query:expr, $($args:expr),*$(,)?) => ({
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::expand_query!(source = $query, args = [$($args),*], checked = false);
|
||||
}
|
||||
macro_result!($($args),*)
|
||||
($query:expr, $($args:tt)*) => ({
|
||||
$crate::sqlx_macros::expand_query!(source = $query, args = [$($args)*], checked = false)
|
||||
})
|
||||
);
|
||||
|
||||
@ -339,19 +338,11 @@ macro_rules! query_unchecked (
|
||||
#[macro_export]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
||||
macro_rules! query_file (
|
||||
($path:literal) => (#[allow(dead_code)]{
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::expand_query!(source_file = $path);
|
||||
}
|
||||
macro_result!()
|
||||
($path:literal) => ({
|
||||
$crate::sqlx_macros::expand_query!(source_file = $path)
|
||||
});
|
||||
($path:literal, $($args:expr),*$(,)?) => (#[allow(dead_code)]{
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::expand_query!(source_file = $path, args = [$($args),*]);
|
||||
}
|
||||
macro_result!($($args),*)
|
||||
($path:literal, $($args:tt)*) => ({
|
||||
$crate::sqlx_macros::expand_query!(source_file = $path, args = [$($args)*])
|
||||
})
|
||||
);
|
||||
|
||||
@ -360,19 +351,11 @@ macro_rules! query_file (
|
||||
#[macro_export]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
||||
macro_rules! query_file_unchecked (
|
||||
($path:literal) => (#[allow(dead_code)]{
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::expand_query!(source_file = $path, checked = false);
|
||||
}
|
||||
macro_result!()
|
||||
($path:literal) => ({
|
||||
$crate::sqlx_macros::expand_query!(source_file = $path, checked = false)
|
||||
});
|
||||
($path:literal, $($args:expr),*$(,)?) => (#[allow(dead_code)]{
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::expand_query!(source_file = $path, args = [$($args),*], checked = false);
|
||||
}
|
||||
macro_result!($($args),*)
|
||||
($path:literal, $($args:tt)*) => ({
|
||||
$crate::sqlx_macros::expand_query!(source_file = $path, args = [$($args)*], checked = false)
|
||||
})
|
||||
);
|
||||
|
||||
@ -380,7 +363,7 @@ macro_rules! query_file_unchecked (
|
||||
///
|
||||
/// This lets you return the struct from a function or add your own trait implementations.
|
||||
///
|
||||
/// No trait implementations are required; the macro maps rows using a struct literal
|
||||
/// **No trait implementations are required**; the macro maps rows using a struct literal
|
||||
/// where the names of columns in the query are expected to be the same as the fields of the struct
|
||||
/// (but the order does not need to be the same). The types of the columns are based on the
|
||||
/// query and not the corresponding fields of the struct, so this is type-safe as well.
|
||||
@ -388,9 +371,17 @@ macro_rules! query_file_unchecked (
|
||||
/// This enforces a few things:
|
||||
/// * The query must output at least one column.
|
||||
/// * The column names of the query must match the field names of the struct.
|
||||
/// * The field types must be the Rust equivalent of their SQL counterparts; see the corresponding
|
||||
/// module for your database for mappings:
|
||||
/// * Postgres: [crate::postgres::types]
|
||||
/// * MySQL: [crate::mysql::types]
|
||||
/// * SQLite: [crate::sqlite::types]
|
||||
/// * MSSQL: [crate::mssql::types]
|
||||
/// * If a column may be `NULL`, the corresponding field's type must be wrapped in `Option<_>`.
|
||||
/// * Neither the query nor the struct may have unused fields.
|
||||
///
|
||||
/// The only modification to the syntax is that the struct name is given before the SQL string:
|
||||
/// The only modification to the `query!()` syntax is that the struct name is given before the SQL
|
||||
/// string:
|
||||
/// ```rust,ignore
|
||||
/// # use sqlx::Connect;
|
||||
/// # #[cfg(all(feature = "mysql", feature = "runtime-async-std"))]
|
||||
@ -425,11 +416,7 @@ macro_rules! query_file_unchecked (
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
///
|
||||
/// ## Nullability
|
||||
/// Use `Option` for columns which may be `NULL` in order to avoid a runtime error being returned
|
||||
/// from `.fetch_*()`.
|
||||
///
|
||||
/// ### Additional Column Type Override Option
|
||||
/// ### Column Type Override: Infer from Struct Field
|
||||
/// In addition to the column type overrides supported by [query!], `query_as!()` supports an
|
||||
/// additional override option:
|
||||
///
|
||||
@ -452,28 +439,71 @@ macro_rules! query_file_unchecked (
|
||||
/// let my_int = MyInt4(1);
|
||||
///
|
||||
/// // Postgres/SQLite
|
||||
/// sqlx::query!(r#"select 1 as "id: _""#) // MySQL: use "select 1 as `id: _`" instead
|
||||
/// sqlx::query_as!(Record, r#"select 1 as "id: _""#) // MySQL: use "select 1 as `id: _`" instead
|
||||
/// .fetch_one(&mut conn)
|
||||
/// .await?;
|
||||
///
|
||||
/// assert_eq!(record.id, MyInt4(1));
|
||||
/// ```
|
||||
///
|
||||
/// ### Troubleshooting: "error: mismatched types"
|
||||
/// If you get a "mismatched types" error from an invocation of this macro and the error
|
||||
/// isn't pointing specifically at a parameter.
|
||||
///
|
||||
/// For example, code like this (using a Postgres database):
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// struct Account {
|
||||
/// id: i32,
|
||||
/// name: Option<String>,
|
||||
/// }
|
||||
///
|
||||
/// let account = sqlx::query_as!(
|
||||
/// Account,
|
||||
/// r#"SELECT id, name from (VALUES (1, 'Herp Derpinson')) accounts(id, name)"#,
|
||||
/// )
|
||||
/// .fetch_one(&mut conn)
|
||||
/// .await?;
|
||||
/// ```
|
||||
///
|
||||
/// Might produce an error like this:
|
||||
/// ```text,ignore
|
||||
/// error[E0308]: mismatched types
|
||||
/// --> tests/postgres/macros.rs:126:19
|
||||
/// |
|
||||
/// 126 | let account = sqlx::query_as!(
|
||||
/// | ___________________^
|
||||
/// 127 | | Account,
|
||||
/// 128 | | r#"SELECT id, name from (VALUES (1, 'Herp Derpinson')) accounts(id, name)"#,
|
||||
/// 129 | | )
|
||||
/// | |_____^ expected `i32`, found enum `std::option::Option`
|
||||
/// |
|
||||
/// = note: expected type `i32`
|
||||
/// found enum `std::option::Option<i32>`
|
||||
/// ```
|
||||
///
|
||||
/// This means that you need to check that any field of the "expected" type (here, `i32`) matches
|
||||
/// the Rust type mapping for its corresponding SQL column (see the `types` module of your database,
|
||||
/// listed above, for mappings). The "found" type is the SQL->Rust mapping that the macro chose.
|
||||
///
|
||||
/// In the above example, the returned column is inferred to be nullable because it's being
|
||||
/// returned from a `VALUES` statement in Postgres, so the macro inferred the field to be nullable
|
||||
/// and so used `Option<i32>` instead of `i32`. **In this specific case** we could use
|
||||
/// `select id as "id!"` to override the inferred nullability because we know in practice
|
||||
/// that column will never be `NULL` and it will fix the error.
|
||||
///
|
||||
/// Nullability inference and type overrides are discussed in detail in the docs for [query!].
|
||||
///
|
||||
/// It unfortunately doesn't appear to be possible right now to make the error specifically mention
|
||||
/// the field; this probably requires the `const-panic` feature (still unstable as of Rust 1.45).
|
||||
#[macro_export]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
||||
macro_rules! query_as (
|
||||
($out_struct:path, $query:expr) => (#[allow(dead_code)] {
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::expand_query!(record = $out_struct, source = $query);
|
||||
}
|
||||
macro_result!()
|
||||
($out_struct:path, $query:expr) => ( {
|
||||
$crate::sqlx_macros::expand_query!(record = $out_struct, source = $query)
|
||||
});
|
||||
($out_struct:path, $query:expr, $($args:expr),*$(,)?) => (#[allow(dead_code)] {
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::expand_query!(record = $out_struct, source = $query, args = [$($args),*]);
|
||||
}
|
||||
macro_result!($($args),*)
|
||||
($out_struct:path, $query:expr, $($args:tt)*) => ( {
|
||||
$crate::sqlx_macros::expand_query!(record = $out_struct, source = $query, args = [$($args)*])
|
||||
})
|
||||
);
|
||||
|
||||
@ -513,19 +543,11 @@ macro_rules! query_as (
|
||||
#[macro_export]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
||||
macro_rules! query_file_as (
|
||||
($out_struct:path, $path:literal) => (#[allow(dead_code)] {
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::expand_query!(record = $out_struct, source_file = $path);
|
||||
}
|
||||
macro_result!()
|
||||
($out_struct:path, $path:literal) => ( {
|
||||
$crate::sqlx_macros::expand_query!(record = $out_struct, source_file = $path)
|
||||
});
|
||||
($out_struct:path, $path:literal, $($args:tt),*$(,)?) => (#[allow(dead_code)] {
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::expand_query!(record = $out_struct, source_file = $path, args = [$($args),*]);
|
||||
}
|
||||
macro_result!($($args),*)
|
||||
($out_struct:path, $path:literal, $($args:tt)*) => ( {
|
||||
$crate::sqlx_macros::expand_query!(record = $out_struct, source_file = $path, args = [$($args)*])
|
||||
})
|
||||
);
|
||||
|
||||
@ -534,20 +556,12 @@ macro_rules! query_file_as (
|
||||
#[macro_export]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
||||
macro_rules! query_as_unchecked (
|
||||
($out_struct:path, $query:expr) => (#[allow(dead_code)] {
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::expand_query!(record = $out_struct, source = $query, checked = false);
|
||||
}
|
||||
macro_result!()
|
||||
($out_struct:path, $query:expr) => ( {
|
||||
$crate::sqlx_macros::expand_query!(record = $out_struct, source = $query, checked = false)
|
||||
});
|
||||
|
||||
($out_struct:path, $query:expr, $($args:expr),*$(,)?) => (#[allow(dead_code)] {
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::expand_query!(record = $out_struct, source = $query, args = [$($args),*], checked = false);
|
||||
}
|
||||
macro_result!($($args),*)
|
||||
($out_struct:path, $query:expr, $($args:tt)*) => ( {
|
||||
$crate::sqlx_macros::expand_query!(record = $out_struct, source = $query, args = [$($args)*], checked = false)
|
||||
})
|
||||
);
|
||||
|
||||
@ -557,19 +571,11 @@ macro_rules! query_as_unchecked (
|
||||
#[macro_export]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
|
||||
macro_rules! query_file_as_unchecked (
|
||||
($out_struct:path, $path:literal) => (#[allow(dead_code)] {
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::expand_query!(record = $out_struct, source_file = $path, checked = false);
|
||||
}
|
||||
macro_result!()
|
||||
($out_struct:path, $path:literal) => ( {
|
||||
$crate::sqlx_macros::expand_query!(record = $out_struct, source_file = $path, checked = false)
|
||||
});
|
||||
|
||||
($out_struct:path, $path:literal, $($args:tt),*$(,)?) => (#[allow(dead_code)] {
|
||||
#[macro_use]
|
||||
mod _macro_result {
|
||||
$crate::sqlx_macros::expand_query!(record = $out_struct, source_file = $path, args = [$($args),*], checked = false);
|
||||
}
|
||||
macro_result!($($args),*)
|
||||
($out_struct:path, $path:literal, $($args:tt)*) => ( {
|
||||
$crate::sqlx_macros::expand_query!(record = $out_struct, source_file = $path, args = [$($args)*], checked = false)
|
||||
})
|
||||
);
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
use crate::error::{Error, UnexpectedNullError};
|
||||
|
||||
pub trait ResultExt<T>: Sized {
|
||||
fn try_unwrap_optional(self) -> crate::Result<T>;
|
||||
}
|
||||
|
||||
impl<T> ResultExt<T> for crate::Result<T> {
|
||||
fn try_unwrap_optional(self) -> crate::Result<T> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ResultExt<T> for crate::Result<Option<T>> {
|
||||
fn try_unwrap_optional(self) -> crate::Result<T> {
|
||||
self?.ok_or_else(|| crate::Error::Decode(UnexpectedNullError.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ResultExt<Option<T>> for crate::Result<T> {
|
||||
fn try_unwrap_optional(self) -> crate::Result<Option<T>> {
|
||||
match self {
|
||||
Ok(val) => Ok(Some(val)),
|
||||
|
||||
Err(Error::Decode(error)) => {
|
||||
if let Some(UnexpectedNullError) = error.downcast_ref() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(Error::Decode(error))
|
||||
}
|
||||
}
|
||||
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -81,6 +81,10 @@ impl<'a> MatchBorrowExt for MatchBorrow<&'a [u8], Vec<u8>> {
|
||||
type Matched = &'a [u8];
|
||||
}
|
||||
|
||||
impl<'a, T> MatchBorrowExt for MatchBorrow<&'a T, T> {
|
||||
type Matched = T;
|
||||
}
|
||||
|
||||
impl<T, U> MatchBorrowExt for &'_ MatchBorrow<T, U> {
|
||||
type Matched = U;
|
||||
}
|
||||
@ -118,5 +122,8 @@ fn test_match_borrow() {
|
||||
if false {
|
||||
let (_, match_borrow) = MatchBorrow::new("", &String::new());
|
||||
let _: &str = match_borrow.match_borrow();
|
||||
|
||||
let (_, match_borrow) = MatchBorrow::new(&&0i64, &0i64);
|
||||
let _: i64 = match_borrow.match_borrow();
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,7 +125,7 @@ async fn test_query_as() -> anyhow::Result<()> {
|
||||
let name: Option<&str> = None;
|
||||
let account = sqlx::query_as!(
|
||||
Account,
|
||||
"SELECT * from (VALUES (1, $1)) accounts(id, name)",
|
||||
r#"SELECT id "id!", name from (VALUES (1, $1)) accounts(id, name)"#,
|
||||
name
|
||||
)
|
||||
.fetch_one(&mut conn)
|
||||
@ -150,7 +150,7 @@ async fn test_query_as_raw() -> anyhow::Result<()> {
|
||||
|
||||
let account = sqlx::query_as!(
|
||||
RawAccount,
|
||||
"SELECT * from (VALUES (1, null)) accounts(type, name)"
|
||||
r#"SELECT type "type!", name from (VALUES (1, null)) accounts(type, name)"#
|
||||
)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
@ -202,6 +202,36 @@ async fn query_by_string() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
#[cfg(feature = "bigdecimal")]
|
||||
async fn query_by_bigdecimal() -> anyhow::Result<()> {
|
||||
use sqlx_core::types::BigDecimal;
|
||||
let mut conn = new::<Postgres>().await?;
|
||||
|
||||
// this tests querying by a non-`Copy` type that doesn't have special reborrow semantics
|
||||
|
||||
let decimal = "1234".parse::<BigDecimal>()?;
|
||||
let ref tuple = ("Hello, world!".to_string(),);
|
||||
|
||||
let result = sqlx::query!(
|
||||
"SELECT * from (VALUES(1234.0)) decimals(decimal)\
|
||||
where decimal in ($1, $2, $3, $4, $5, $6, $7)",
|
||||
decimal, // make sure we don't actually take ownership here
|
||||
&decimal, // allow query-by-reference
|
||||
Some(&decimal),
|
||||
Some(&decimal),
|
||||
Option::<BigDecimal>::None,
|
||||
decimal.clone(),
|
||||
tuple.0 // make sure we're not trying to move out of a field expression
|
||||
)
|
||||
.fetch_one(&mut conn)
|
||||
.await?;
|
||||
|
||||
assert_eq!(result.decimal, Some(decimal));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx_macros::test]
|
||||
async fn test_nullable_err() -> anyhow::Result<()> {
|
||||
#[derive(Debug)]
|
||||
@ -214,7 +244,7 @@ async fn test_nullable_err() -> anyhow::Result<()> {
|
||||
|
||||
let err = sqlx::query_as!(
|
||||
Account,
|
||||
"SELECT * from (VALUES (1, null::text)) accounts(id, name)"
|
||||
r#"SELECT id "id!", name "name!" from (VALUES (1, null::text)) accounts(id, name)"#
|
||||
)
|
||||
.fetch_one(&mut conn)
|
||||
.await
|
||||
|
||||
@ -1 +1 @@
|
||||
SELECT * from (VALUES (1, null)) accounts(id, name)
|
||||
SELECT id "id!", name from (VALUES (1, null)) accounts(id, name)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user