mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-10-07 01:34:35 +00:00

* feat: create `sqlx.toml` format * feat: add support for ignored_chars config to sqlx_core::migrate * chore: test ignored_chars with `U+FEFF` (ZWNBSP/BOM) https://en.wikipedia.org/wiki/Byte_order_mark * refactor: make `Config` always compiled simplifies usage while still making parsing optional for less generated code * refactor: add origin information to `Column` * feat(macros): implement `type_override` and `column_override` from `sqlx.toml` * refactor(sqlx.toml): make all keys kebab-case, create `macros.preferred-crates` * feat: make macros aware of `macros.preferred-crates` * feat: make `sqlx-cli` aware of `database-url-var` * feat: teach macros about `migrate.table-name`, `migrations-dir` * feat: teach macros about `migrate.ignored-chars` * chore: delete unused source file `sqlx-cli/src/migration.rs` * feat: teach `sqlx-cli` about `migrate.defaults` * feat: teach `sqlx-cli` about `migrate.migrations-dir` * feat: teach `sqlx-cli` about `migrate.table-name` * feat: introduce `migrate.create-schemas` * WIP feat: create multi-tenant database example * fix(postgres): don't fetch `ColumnOrigin` for transparently-prepared statements * feat: progress on axum-multi-tenant example * feat(config): better errors for mislabeled fields * WIP feat: filling out axum-multi-tenant example * feat: multi-tenant example No longer Axum-based because filling out the request routes would have distracted from the purpose of the example. * chore(ci): test multi-tenant example * fixup after merge * fix(ci): enable `sqlx-toml` in CLI build for examples * fix: CI, README for `multi-tenant` * fix: clippy warnings * fix: multi-tenant README * fix: sequential versioning inference for migrations * fix: migration versioning with explicit overrides * fix: only warn on ambiguous crates if the invocation relies on it * fix: remove unused imports * fix: doctest * fix: `sqlx mig add` behavior and tests * fix: restore original type-checking order * fix: deprecation warning in `tests/postgres/macros.rs` * feat: create postgres/multi-database example * fix: examples/postgres/multi-database * fix: cargo fmt * chore: add tests for config `migrate.defaults` * fix: sqlx-cli/tests/add.rs * feat(cli): add `--config` override to all relevant commands * chore: run `sqlx mig add` test with `RUST_BACKTRACE=1` * fix: properly canonicalize config path for `sqlx mig add` test * fix: get `sqlx mig add` test passing * fix(cli): test `migrate.ignored-chars`, fix bugs * feat: create `macros.preferred-crates` example * fix(examples): use workspace `sqlx` * fix: examples * fix(sqlite): unexpected feature flags in `type_checking.rs` * fix: run `cargo fmt` * fix: more example fixes * fix(ci): preferred-crates setup * fix(examples): enable default-features for workspace `sqlx` * fix(examples): issues in `preferred-crates` * chore: adjust error message for missing param type in `query!()` * doc: mention new `sqlx.toml` configuration * chore: add `CHANGELOG` entry Normally I generate these when cutting the release, but I wanted to take time to editorialize this one. * doc: fix new example titles * refactor: make `sqlx-toml` feature non-default, improve errors * refactor: eliminate panics in `Config` read path * chore: remove unused `axum` dependency from new examples * fix(config): restore fallback to default config for macros * chore(config): remove use of `once_cell` (to match `main`)
663 lines
17 KiB
Rust
663 lines
17 KiB
Rust
use sqlx::{Connection, PgConnection, Postgres, Transaction};
|
|
use sqlx_postgres::types::PgHstore;
|
|
use sqlx_test::new;
|
|
|
|
use futures_util::TryStreamExt;
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_query() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
let account = sqlx::query!(
|
|
"SELECT * from (VALUES (1, 'Herp Derpinson')) accounts(id, name) where id = $1",
|
|
1i32
|
|
)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(account.id, Some(1));
|
|
assert_eq!(account.name.as_deref(), Some("Herp Derpinson"));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_non_null() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
let mut tx = conn.begin().await?;
|
|
|
|
let _ = sqlx::query!("INSERT INTO tweet (text) VALUES ('Hello')")
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
let row = sqlx::query!("SELECT id, text, owner_id FROM tweet LIMIT 1")
|
|
.fetch_one(&mut *tx)
|
|
.await?;
|
|
|
|
assert!(row.id > 0);
|
|
assert_eq!(row.text, "Hello");
|
|
assert_eq!(row.owner_id, None);
|
|
|
|
// let the transaction rollback so we don't actually insert the tweet
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_no_result() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
let mut tx = conn.begin().await?;
|
|
|
|
let _ = sqlx::query!("DELETE FROM tweet").execute(&mut *tx).await?;
|
|
|
|
// let the transaction rollback so we don't actually delete the tweets
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_text_var_char_char_n() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
// TEXT
|
|
// we cannot infer nullability from an expression
|
|
let rec = sqlx::query!("SELECT 'Hello'::text as greeting")
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(rec.greeting.as_deref(), Some("Hello"));
|
|
|
|
// VARCHAR(N)
|
|
|
|
let rec = sqlx::query!("SELECT 'Hello'::varchar(5) as greeting")
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(rec.greeting.as_deref(), Some("Hello"));
|
|
|
|
// CHAR(N)
|
|
|
|
let rec = sqlx::query!("SELECT 'Hello'::char(5) as greeting")
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(rec.greeting.as_deref(), Some("Hello"));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_void() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
let _ = sqlx::query!(r#"select pg_notify('chan', 'message')"#)
|
|
.execute(&mut conn)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_call_procedure() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
let row = sqlx::query!(r#"CALL forty_two(null)"#)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(row.forty_two, Some(42));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_query_file() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
// keep trailing comma as a test
|
|
let account = sqlx::query_file!("tests/postgres/test-query.sql")
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(account.id, 1);
|
|
assert_eq!(account.name, Option::<String>::None);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Account {
|
|
id: i32,
|
|
name: Option<String>,
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_query_as() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
let name: Option<&str> = None;
|
|
let account = sqlx::query_as!(
|
|
Account,
|
|
r#"SELECT id "id!", name from (VALUES (1, $1)) accounts(id, name)"#,
|
|
name
|
|
)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(1, account.id);
|
|
assert_eq!(None, account.name);
|
|
|
|
println!("{account:?}");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct RawAccount {
|
|
r#type: i32,
|
|
name: Option<String>,
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_query_as_raw() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
let account = sqlx::query_as!(
|
|
RawAccount,
|
|
r#"SELECT type "type!", name from (VALUES (1, null)) accounts(type, name)"#
|
|
)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(None, account.name);
|
|
assert_eq!(1, account.r#type);
|
|
|
|
println!("{account:?}");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_query_file_as() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
let account = sqlx::query_file_as!(Account, "tests/postgres/test-query.sql")
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
println!("{account:?}");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_query_scalar() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
let id = sqlx::query_scalar!("select 1").fetch_one(&mut conn).await?;
|
|
// nullability inference can't handle expressions
|
|
assert_eq!(id, Some(1i32));
|
|
|
|
// invalid column names are ignored
|
|
let id = sqlx::query_scalar!(r#"select 1 as "&foo""#)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
assert_eq!(id, Some(1i32));
|
|
|
|
let id = sqlx::query_scalar!(r#"select 1 as "foo!""#)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
assert_eq!(id, 1i32);
|
|
|
|
let id = sqlx::query_scalar!(r#"select 1 as "foo?""#)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(id, Some(1i32));
|
|
|
|
let id = sqlx::query_scalar!(r#"select 1 as "foo: MyInt4""#)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(id, Some(MyInt4(1i32)));
|
|
|
|
let id = sqlx::query_scalar!(r#"select 1 as "foo?: MyInt4""#)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(id, Some(MyInt4(1i32)));
|
|
|
|
let id = sqlx::query_scalar!(r#"select 1 as "foo!: MyInt4""#)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(id, MyInt4(1i32));
|
|
|
|
let id: MyInt4 = sqlx::query_scalar!(r#"select 1 as "foo: _""#)
|
|
.fetch_one(&mut conn)
|
|
.await?
|
|
// don't hint that it should be `Option<MyInt4>`
|
|
.unwrap();
|
|
|
|
assert_eq!(id, MyInt4(1i32));
|
|
|
|
let id: MyInt4 = sqlx::query_scalar!(r#"select 1 as "foo?: _""#)
|
|
.fetch_one(&mut conn)
|
|
.await?
|
|
// don't hint that it should be `Option<MyInt4>`
|
|
.unwrap();
|
|
|
|
assert_eq!(id, MyInt4(1i32));
|
|
|
|
let id: MyInt4 = sqlx::query_scalar!(r#"select 1 as "foo!: _""#)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(id, MyInt4(1i32));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn query_by_string() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
let string = "Hello, world!".to_string();
|
|
let ref tuple = ("Hello, world!".to_string(),);
|
|
|
|
let result = sqlx::query!(
|
|
"SELECT * from (VALUES('Hello, world!')) strings(string)\
|
|
where string in ($1, $2, $3, $4, $5, $6, $7)",
|
|
string, // make sure we don't actually take ownership here
|
|
&string[..],
|
|
Some(&string),
|
|
Some(&string[..]),
|
|
Option::<String>::None,
|
|
string.clone(),
|
|
tuple.0 // make sure we're not trying to move out of a field expression
|
|
)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(result.string, Some(string));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
#[cfg(feature = "bigdecimal")]
|
|
async fn query_by_bigdecimal() -> anyhow::Result<()> {
|
|
use sqlx::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 = ("51245.121232".parse::<BigDecimal>()?,);
|
|
|
|
#[cfg_attr(feature = "rust_decimal", allow(deprecated))] // TODO: upgrade to `expect`
|
|
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<()> {
|
|
#[allow(dead_code)]
|
|
#[derive(Debug)]
|
|
struct Account {
|
|
id: i32,
|
|
name: String,
|
|
}
|
|
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
let err = sqlx::query_as!(
|
|
Account,
|
|
r#"SELECT id "id!", name "name!" from (VALUES (1, null::text)) accounts(id, name)"#
|
|
)
|
|
.fetch_one(&mut conn)
|
|
.await
|
|
.unwrap_err();
|
|
|
|
if let sqlx::Error::ColumnDecode { source, .. } = &err {
|
|
if let Some(sqlx::error::UnexpectedNullError) = source.downcast_ref() {
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
panic!("expected `UnexpectedNullError`, got {err}")
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_many_args() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
// previous implementation would only have supported 10 bind parameters
|
|
// (this is really gross to test in MySQL)
|
|
let rows = sqlx::query!(
|
|
"SELECT * from unnest(array[$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12]::int[]) ids(id);",
|
|
0i32, 1i32, 2i32, 3i32, 4i32, 5i32, 6i32, 7i32, 8i32, 9i32, 10i32, 11i32
|
|
)
|
|
.fetch_all(&mut conn)
|
|
.await?;
|
|
|
|
for (i, row) in rows.iter().enumerate() {
|
|
assert_eq!(Some(i as i32), row.id);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_array_from_slice() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
let list: &[i32] = &[1, 2, 3, 4i32];
|
|
|
|
let result = sqlx::query!("SELECT $1::int[] as my_array", list)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(result.my_array, Some(vec![1, 2, 3, 4]));
|
|
|
|
println!("result ID: {:?}", result.my_array);
|
|
|
|
let account = sqlx::query!("SELECT ARRAY[4,3,2,1] as my_array")
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(account.my_array, Some(vec![4, 3, 2, 1]));
|
|
|
|
println!("account ID: {:?}", account.my_array);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn fetch_is_usable_issue_224() -> anyhow::Result<()> {
|
|
// ensures that the stream returned by `query::Map::fetch()` is usable with `TryStreamExt`
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
let mut stream =
|
|
sqlx::query!("select * from generate_series(1, 3) as series(num);").fetch(&mut conn);
|
|
|
|
// `num` is generated by a function so we can't assume it's non-null
|
|
assert_eq!(stream.try_next().await?.unwrap().num, Some(1));
|
|
assert_eq!(stream.try_next().await?.unwrap().num, Some(2));
|
|
assert_eq!(stream.try_next().await?.unwrap().num, Some(3));
|
|
assert!(stream.try_next().await?.is_none());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_column_override_not_null() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
let record = sqlx::query!(r#"select 1 as "id!""#)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(record.id, 1);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_column_override_nullable() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
// workaround for https://github.com/launchbadge/sqlx/issues/367
|
|
// declare a `NOT NULL` column from a left-joined table to be nullable
|
|
let record = sqlx::query!(
|
|
r#"select text as "text?" from (values (1)) foo(id) left join tweet on false"#
|
|
)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(record.text, None);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn with_test_row<'a>(
|
|
conn: &'a mut PgConnection,
|
|
) -> anyhow::Result<Transaction<'a, Postgres>> {
|
|
let mut transaction = conn.begin().await?;
|
|
sqlx::query!("INSERT INTO tweet(id, text, owner_id) VALUES (1, '#sqlx is pretty cool!', 1)")
|
|
.execute(&mut *transaction)
|
|
.await?;
|
|
Ok(transaction)
|
|
}
|
|
|
|
#[derive(PartialEq, Eq, Debug, sqlx::Type)]
|
|
#[sqlx(transparent)]
|
|
struct MyInt(i64);
|
|
|
|
#[derive(PartialEq, Eq, Debug, sqlx::Type)]
|
|
#[sqlx(transparent)]
|
|
struct MyInt4(i32);
|
|
|
|
struct Record {
|
|
id: MyInt,
|
|
}
|
|
|
|
struct OptionalRecord {
|
|
id: Option<MyInt>,
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_column_override_wildcard() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
let mut conn = with_test_row(&mut conn).await?;
|
|
|
|
let record = sqlx::query_as!(Record, r#"select id as "id: _" from tweet"#)
|
|
.fetch_one(&mut *conn)
|
|
.await?;
|
|
|
|
assert_eq!(record.id, MyInt(1));
|
|
|
|
let record = sqlx::query_as!(OptionalRecord, r#"select owner_id as "id: _" from tweet"#)
|
|
.fetch_one(&mut *conn)
|
|
.await?;
|
|
|
|
assert_eq!(record.id, Some(MyInt(1)));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_column_override_wildcard_not_null() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
let mut conn = with_test_row(&mut conn).await?;
|
|
|
|
let record = sqlx::query_as!(Record, r#"select owner_id as "id!: _" from tweet"#)
|
|
.fetch_one(&mut *conn)
|
|
.await?;
|
|
|
|
assert_eq!(record.id, MyInt(1));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_column_override_wildcard_nullable() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
let mut conn = with_test_row(&mut conn).await?;
|
|
|
|
let record = sqlx::query_as!(OptionalRecord, r#"select id as "id?: _" from tweet"#)
|
|
.fetch_one(&mut *conn)
|
|
.await?;
|
|
|
|
assert_eq!(record.id, Some(MyInt(1)));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_column_override_exact() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
let mut conn = with_test_row(&mut conn).await?;
|
|
|
|
let record = sqlx::query!(r#"select id as "id: MyInt" from tweet"#)
|
|
.fetch_one(&mut *conn)
|
|
.await?;
|
|
|
|
assert_eq!(record.id, MyInt(1));
|
|
|
|
let record = sqlx::query!(r#"select owner_id as "id: MyInt" from tweet"#)
|
|
.fetch_one(&mut *conn)
|
|
.await?;
|
|
|
|
assert_eq!(record.id, Some(MyInt(1)));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_column_override_exact_not_null() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
let mut conn = with_test_row(&mut conn).await?;
|
|
|
|
let record = sqlx::query!(r#"select owner_id as "id!: MyInt" from tweet"#)
|
|
.fetch_one(&mut *conn)
|
|
.await?;
|
|
|
|
assert_eq!(record.id, MyInt(1));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_column_override_exact_nullable() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
let mut conn = with_test_row(&mut conn).await?;
|
|
|
|
let record = sqlx::query!(r#"select id as "id?: MyInt" from tweet"#)
|
|
.fetch_one(&mut *conn)
|
|
.await?;
|
|
|
|
assert_eq!(record.id, Some(MyInt(1)));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_bind_arg_override_exact() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
let my_int = MyInt4(1);
|
|
|
|
// this query should require a bind parameter override as we would otherwise expect the bind
|
|
// to be the same type
|
|
let record = sqlx::query!(
|
|
"select * from (select 1::int4) records(id) where id = $1",
|
|
my_int as MyInt4
|
|
)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(record.id, Some(1i32));
|
|
|
|
// test that we're actually emitting the typecast by requiring the bound type to be the same
|
|
let record = sqlx::query!("select $1::int8 as id", 1i32 as i64)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(record.id, Some(1i64));
|
|
|
|
// test the override with `Option`
|
|
let my_opt_int = Some(MyInt4(1));
|
|
|
|
let record = sqlx::query!(
|
|
"select * from (select 1::int4) records(id) where id = $1",
|
|
my_opt_int as Option<MyInt4>
|
|
)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(record.id, Some(1i32));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_bind_arg_override_wildcard() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
let my_int = MyInt4(1);
|
|
|
|
let record = sqlx::query!(
|
|
"select * from (select 1::int4) records(id) where id = $1",
|
|
my_int as _
|
|
)
|
|
.fetch_one(&mut conn)
|
|
.await?;
|
|
|
|
assert_eq!(record.id, Some(1i32));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn test_to_from_citext() -> anyhow::Result<()> {
|
|
// Ensure that the macros consider `CITEXT` to be compatible with `String` and friends
|
|
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
let mut tx = conn.begin().await?;
|
|
|
|
let foo_in = "Hello, world!";
|
|
|
|
sqlx::query!("insert into test_citext(foo) values ($1)", foo_in)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
let foo_out: String = sqlx::query_scalar!("select foo from test_citext")
|
|
.fetch_one(&mut *tx)
|
|
.await?;
|
|
|
|
assert_eq!(foo_in, foo_out);
|
|
|
|
tx.rollback().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sqlx_macros::test]
|
|
async fn pghstore_tests() -> anyhow::Result<()> {
|
|
let mut conn = new::<Postgres>().await?;
|
|
|
|
let mut store = PgHstore::default();
|
|
let stores = vec![store.clone(), store.clone()];
|
|
|
|
store.insert("key".into(), Some("value".to_string()));
|
|
sqlx::query!(" insert into mytable(f) values ($1)", store)
|
|
.execute(&mut conn)
|
|
.await?;
|
|
|
|
sqlx::query!(
|
|
" insert into mytable(f) select * from unnest($1::hstore[])",
|
|
&stores
|
|
)
|
|
.execute(&mut conn)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|