Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
use sqlx::Any;
use sqlx_test::new;
#[sqlx_macros::test]
async fn it_encodes_bool_with_any() -> anyhow::Result<()> {
sqlx::any::install_default_drivers();
let mut conn = new::<Any>().await?;
let res = sqlx::query("INSERT INTO accounts (name, is_active) VALUES (?, ?)")
.bind("Harrison Ford")
.bind(true)
.execute(&mut conn)
.await
.expect("failed to encode bool");
assert_eq!(res.rows_affected(), 1);
Ok(())
}
#[sqlx_macros::test]
async fn issue_3179() -> anyhow::Result<()> {
sqlx::any::install_default_drivers();
let mut conn = new::<Any>().await?;
// 4294967297 = 2^32
let number: i64 = sqlx::query_scalar("SELECT 4294967296")
.fetch_one(&mut conn)
.await?;
// Previously, the decoding would use `i32` as an intermediate which would overflow to 0.
assert_eq!(number, 4294967296);
Ok(())
}
+34
View File
@@ -0,0 +1,34 @@
use sqlx::Sqlite;
use sqlx_test::test_type;
#[derive(Debug, PartialEq, sqlx::Type)]
#[repr(u32)]
enum Origin {
Foo = 1,
Bar = 2,
}
test_type!(origin_enum<Origin>(Sqlite,
"1" == Origin::Foo,
"2" == Origin::Bar,
));
#[derive(PartialEq, Eq, Debug, sqlx::Type)]
#[sqlx(transparent)]
struct TransparentTuple(i64);
#[derive(PartialEq, Eq, Debug, sqlx::Type)]
#[sqlx(transparent)]
struct TransparentNamed {
field: i64,
}
test_type!(transparent_tuple<TransparentTuple>(Sqlite,
"0" == TransparentTuple(0),
"23523" == TransparentTuple(23523)
));
test_type!(transparent_named<TransparentNamed>(Sqlite,
"0" == TransparentNamed { field: 0 },
"23523" == TransparentNamed { field: 23523 },
));
+1097
View File
File diff suppressed because it is too large Load Diff
+98
View File
@@ -0,0 +1,98 @@
use sqlx::{error::ErrorKind, sqlite::Sqlite, Connection, Error};
use sqlx_test::new;
#[sqlx_macros::test]
async fn it_fails_with_unique_violation() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let mut tx = conn.begin().await?;
let res: Result<_, sqlx::Error> = sqlx::query("INSERT INTO tweet VALUES (1, 'Foo', true, 1);")
.execute(&mut *tx)
.await;
let err = res.unwrap_err();
let err = err.into_database_error().unwrap();
assert_eq!(err.kind(), ErrorKind::UniqueViolation);
Ok(())
}
#[sqlx_macros::test]
async fn it_fails_with_foreign_key_violation() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let mut tx = conn.begin().await?;
let res: Result<_, sqlx::Error> =
sqlx::query("INSERT INTO tweet_reply (id, tweet_id, text) VALUES (2, 2, 'Reply!');")
.execute(&mut *tx)
.await;
let err = res.unwrap_err();
let err = err.into_database_error().unwrap();
assert_eq!(err.kind(), ErrorKind::ForeignKeyViolation);
Ok(())
}
#[sqlx_macros::test]
async fn it_fails_with_not_null_violation() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let mut tx = conn.begin().await?;
let res: Result<_, sqlx::Error> = sqlx::query("INSERT INTO tweet (text) VALUES (null);")
.execute(&mut *tx)
.await;
let err = res.unwrap_err();
let err = err.into_database_error().unwrap();
assert_eq!(err.kind(), ErrorKind::NotNullViolation);
Ok(())
}
#[sqlx_macros::test]
async fn it_fails_with_check_violation() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let mut tx = conn.begin().await?;
let res: Result<_, sqlx::Error> =
sqlx::query("INSERT INTO products VALUES (1, 'Product 1', 0);")
.execute(&mut *tx)
.await;
let err = res.unwrap_err();
let err = err.into_database_error().unwrap();
assert_eq!(err.kind(), ErrorKind::CheckViolation);
Ok(())
}
#[sqlx_macros::test]
async fn it_fails_with_begin_failed() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let res = conn.begin_with("SELECT * FROM tweet").await;
let err = res.unwrap_err();
assert!(matches!(err, Error::BeginFailed), "{err:?}");
Ok(())
}
#[sqlx_macros::test]
async fn it_fails_with_invalid_save_point_statement() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let mut txn = conn.begin().await?;
let txn_conn = sqlx::Acquire::acquire(&mut txn).await?;
let res = txn_conn.begin_with("BEGIN").await;
let err = res.unwrap_err();
assert!(matches!(err, Error::InvalidSavePointStatement), "{err}");
Ok(())
}
+16
View File
@@ -0,0 +1,16 @@
insert into comment(comment_id, post_id, user_id, content, created_at)
values (1,
1,
2,
'lol bet ur still bad, 1v1 me',
datetime('now', '-50 minutes')),
(2,
1,
1,
'you''re on!',
datetime('now', '-45 minutes')),
(3,
2,
1,
'lol you''re just mad you lost :P',
datetime('now', '-15 minutes'));
+9
View File
@@ -0,0 +1,9 @@
insert into post(post_id, user_id, content, created_at)
values (1,
1,
'This new computer is lightning-fast!',
datetime('now', '-1 hour')),
(2,
2,
'@alice is a haxxor :(',
datetime('now', '-30 minutes'));
+2
View File
@@ -0,0 +1,2 @@
insert into "user"(user_id, username)
values (1, 'alice'), (2, 'bob');
+361
View File
@@ -0,0 +1,361 @@
use sqlx::Sqlite;
use sqlx_test::new;
#[sqlx_macros::test]
async fn macro_select() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let account = sqlx::query!("select id, name, is_active from accounts where id = 1")
.fetch_one(&mut conn)
.await?;
assert_eq!(1, account.id);
assert_eq!("Herp Derpinson", account.name);
assert_eq!(account.is_active, Some(true));
Ok(())
}
macro_rules! gen_macro_select_concats {
($param:literal) => {
#[sqlx_macros::test]
async fn macro_select_concat_single() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let account = sqlx::query!("select " + $param + " from accounts where id = 1")
.fetch_one(&mut conn)
.await?;
assert_eq!(1, account.id);
assert_eq!("Herp Derpinson", account.name);
assert_eq!(account.is_active, Some(true));
Ok(())
}
};
}
gen_macro_select_concats!("id, name, is_active");
#[sqlx_macros::test]
async fn macro_select_expression() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let row = sqlx::query!("select 10 as _1, 'Hello' as _2")
.fetch_one(&mut conn)
.await?;
assert_eq!(10, row._1);
assert_eq!("Hello", &*row._2);
Ok(())
}
#[sqlx_macros::test]
async fn macro_select_partial_expression() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let row = sqlx::query!(
"select 10 as _1, 'Hello' as _2, is_active, name, id + 5 as id_p from accounts where id = 1"
)
.fetch_one(&mut conn)
.await?;
assert_eq!(10, row._1);
assert_eq!("Hello", &*row._2);
assert_eq!(6, row.id_p);
assert_eq!("Herp Derpinson", row.name);
assert_eq!(row.is_active, Some(true));
Ok(())
}
#[sqlx_macros::test]
async fn macro_select_bind() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let account = sqlx::query!(
"select id, name, is_active from accounts where id = ?",
1i32
)
.fetch_one(&mut conn)
.await?;
assert_eq!(1, account.id);
assert_eq!("Herp Derpinson", account.name);
assert_eq!(account.is_active, Some(true));
Ok(())
}
#[derive(Debug)]
struct RawAccount {
id: i64,
name: String,
is_active: Option<bool>,
}
#[sqlx_macros::test]
async fn test_query_as_raw() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let account = sqlx::query_as!(RawAccount, "SELECT id, name, is_active from accounts")
.fetch_one(&mut conn)
.await?;
assert_eq!(account.id, 1);
assert_eq!(account.name, "Herp Derpinson");
assert_eq!(account.is_active, Some(true));
Ok(())
}
#[sqlx_macros::test]
async fn test_query_scalar() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let id = sqlx::query_scalar!("select 1").fetch_one(&mut conn).await?;
assert_eq!(id, 1i64);
// invalid column names are ignored
let id = sqlx::query_scalar!(r#"select 1 as "&foo""#)
.fetch_one(&mut conn)
.await?;
assert_eq!(id, 1i64);
let id = sqlx::query_scalar!(r#"select 1 as "foo!""#)
.fetch_one(&mut conn)
.await?;
assert_eq!(id, 1i64);
let id = sqlx::query_scalar!(r#"select 1 as "foo?""#)
.fetch_one(&mut conn)
.await?;
assert_eq!(id, Some(1i64));
let id = sqlx::query_scalar!(r#"select 1 as "foo: MyInt""#)
.fetch_one(&mut conn)
.await?;
assert_eq!(id, MyInt(1i64));
let id = sqlx::query_scalar!(r#"select 1 as "foo?: MyInt""#)
.fetch_one(&mut conn)
.await?;
assert_eq!(id, Some(MyInt(1i64)));
let id = sqlx::query_scalar!(r#"select 1 as "foo!: MyInt""#)
.fetch_one(&mut conn)
.await?;
assert_eq!(id, MyInt(1i64));
let id: MyInt = sqlx::query_scalar!(r#"select 1 as "foo: _""#)
.fetch_one(&mut conn)
.await?;
assert_eq!(id, MyInt(1i64));
let id: MyInt = sqlx::query_scalar!(r#"select 1 as "foo?: _""#)
.fetch_one(&mut conn)
.await?
// don't hint that it should be `Option<MyInt>`
.unwrap();
assert_eq!(id, MyInt(1i64));
let id: MyInt = sqlx::query_scalar!(r#"select 1 as "foo!: _""#)
.fetch_one(&mut conn)
.await?;
assert_eq!(id, MyInt(1i64));
Ok(())
}
#[sqlx_macros::test]
async fn query_by_string() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let string = "Hello, world!".to_string();
let ref tuple = ("Hello, world!".to_string(),);
let result = sqlx::query!(
"SELECT 'Hello, world!' as string where 'Hello, world!' in (?, ?, ?, ?, ?, ?, ?)",
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, string);
Ok(())
}
#[sqlx_macros::test]
async fn macro_select_from_view() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let account = sqlx::query!("SELECT id, name, is_active from accounts_view")
.fetch_one(&mut conn)
.await?;
// SQLite tells us the true origin of these columns even through the view
assert_eq!(account.id, 1);
assert_eq!(account.name, "Herp Derpinson");
assert_eq!(account.is_active, Some(true));
Ok(())
}
#[sqlx_macros::test]
async fn test_column_override_not_null() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let record = sqlx::query!(r#"select owner_id as `owner_id!` from tweet"#)
.fetch_one(&mut conn)
.await?;
assert_eq!(record.owner_id, 1);
Ok(())
}
#[sqlx_macros::test]
async fn test_column_override_nullable() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let record = sqlx::query!(r#"select text as `text?` from tweet"#)
.fetch_one(&mut conn)
.await?;
assert_eq!(record.text.as_deref(), Some("#sqlx is pretty cool!"));
Ok(())
}
#[derive(PartialEq, Eq, Debug, sqlx::Type)]
#[sqlx(transparent)]
struct MyInt(i64);
struct Record {
id: MyInt,
}
struct OptionalRecord {
id: Option<MyInt>,
}
#[sqlx_macros::test]
async fn test_column_override_wildcard() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().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));
// this syntax is also useful for expressions
let record = sqlx::query_as!(Record, r#"select 1 as "id: _""#)
.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::<Sqlite>().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::<Sqlite>().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::<Sqlite>().await?;
let record = sqlx::query!(r#"select id as "id: MyInt" from tweet"#)
.fetch_one(&mut conn)
.await?;
assert_eq!(record.id, MyInt(1));
// we can also support this syntax for expressions
let record = sqlx::query!(r#"select 1 as "id: MyInt""#)
.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::<Sqlite>().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::<Sqlite>().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(())
}
// we don't emit bind parameter typechecks for SQLite so testing the overrides is redundant
+157
View File
@@ -0,0 +1,157 @@
use sqlx::migrate::Migrator;
use sqlx::pool::PoolConnection;
use sqlx::sqlite::{Sqlite, SqliteConnection};
use sqlx::Executor;
use sqlx::Row;
use std::path::Path;
#[sqlx::test(migrations = false)]
async fn simple(mut conn: PoolConnection<Sqlite>) -> anyhow::Result<()> {
clean_up(&mut conn).await?;
let migrator = Migrator::new(Path::new("tests/sqlite/migrations_simple")).await?;
// run migration
migrator.run(&mut conn).await?;
// check outcome
let res: String = conn
.fetch_one("SELECT some_payload FROM migrations_simple_test")
.await?
.get(0);
assert_eq!(res, "110_suffix");
// running it a 2nd time should still work
migrator.run(&mut conn).await?;
Ok(())
}
#[sqlx::test(migrations = false)]
async fn reversible(mut conn: PoolConnection<Sqlite>) -> anyhow::Result<()> {
clean_up(&mut conn).await?;
let migrator = Migrator::new(Path::new("tests/sqlite/migrations_reversible")).await?;
// run migration
migrator.run(&mut conn).await?;
// check outcome
let res: i64 = conn
.fetch_one("SELECT some_payload FROM migrations_reversible_test")
.await?
.get(0);
assert_eq!(res, 101);
// roll back nothing (last version)
migrator.undo(&mut conn, 20220721125033).await?;
// check outcome
let res: i64 = conn
.fetch_one("SELECT some_payload FROM migrations_reversible_test")
.await?
.get(0);
assert_eq!(res, 101);
// roll back one version
migrator.undo(&mut conn, 20220721124650).await?;
// check outcome
let res: i64 = conn
.fetch_one("SELECT some_payload FROM migrations_reversible_test")
.await?
.get(0);
assert_eq!(res, 100);
Ok(())
}
#[sqlx::test(migrations = false)]
async fn skip(mut conn: PoolConnection<Sqlite>) -> anyhow::Result<()> {
clean_up(&mut conn).await?;
let migrator = Migrator::new(Path::new("tests/sqlite/migrations_reversible")).await?;
// get to the state of after the first migration manually
let sql = include_str!("migrations_reversible/20220721124650_add_table.up.sql");
let statements: Vec<&str> = sql.split(';').filter(|s| !s.trim().is_empty()).collect();
for statement in statements {
conn.execute(statement).await?;
}
// skip first migration
migrator.skip(&mut conn, Some(20220721124650)).await?;
// check outcome
let res: i64 = conn
.fetch_one("SELECT some_payload FROM migrations_reversible_test")
.await?
.get(0);
assert_eq!(res, 100);
// run remaining migration
migrator.run(&mut conn).await?;
// check outcome
let res: i64 = conn
.fetch_one("SELECT some_payload FROM migrations_reversible_test")
.await?
.get(0);
assert_eq!(res, 101);
// roll back one version
migrator.undo(&mut conn, 20220721124650).await?;
// check outcome
let res: i64 = conn
.fetch_one("SELECT some_payload FROM migrations_reversible_test")
.await?
.get(0);
assert_eq!(res, 100);
Ok(())
}
#[sqlx::test(migrations = false)]
async fn no_tx(mut conn: PoolConnection<Sqlite>) -> anyhow::Result<()> {
clean_up(&mut conn).await?;
let migrator = Migrator::new(Path::new("tests/sqlite/migrations_no_tx")).await?;
// run migration
migrator.run(&mut conn).await?;
Ok(())
}
#[sqlx::test(migrations = false)]
async fn no_tx_reversible(mut conn: PoolConnection<Sqlite>) -> anyhow::Result<()> {
clean_up(&mut conn).await?;
let migrator = Migrator::new(Path::new("tests/sqlite/migrations_no_tx_reversible")).await?;
// run migration
migrator.run(&mut conn).await?;
// check outcome
let res: String = conn.fetch_one("PRAGMA JOURNAL_MODE").await?.get(0);
assert_eq!(res, "wal".to_string());
// roll back
migrator.undo(&mut conn, -1).await?;
// check outcome
let res: String = conn.fetch_one("PRAGMA JOURNAL_MODE").await?.get(0);
assert_eq!(res, "delete".to_string());
Ok(())
}
/// Ensure that we have a clean initial state.
async fn clean_up(conn: &mut SqliteConnection) -> anyhow::Result<()> {
conn.execute("DROP TABLE migrations_simple_test").await.ok();
conn.execute("DROP TABLE migrations_reversible_test")
.await
.ok();
conn.execute("DROP TABLE _sqlx_migrations").await.ok();
Ok(())
}
+6
View File
@@ -0,0 +1,6 @@
create table user
(
-- integer primary keys are the most efficient in SQLite
user_id integer primary key,
username text unique not null
);
+10
View File
@@ -0,0 +1,10 @@
create table post
(
post_id integer primary key,
user_id integer not null references user (user_id),
content text not null,
-- Defaults have to be wrapped in parenthesis
created_at datetime default (datetime('now'))
);
create index post_created_at on post (created_at desc);
+10
View File
@@ -0,0 +1,10 @@
create table comment
(
comment_id integer primary key,
post_id integer not null references post (post_id),
user_id integer not null references "user" (user_id),
content text not null,
created_at datetime default (datetime('now'))
);
create index comment_created_at on comment (created_at desc);
+3
View File
@@ -0,0 +1,3 @@
-- no-transaction
VACUUM;
@@ -0,0 +1,3 @@
-- no-transaction
PRAGMA JOURNAL_MODE = DELETE;
@@ -0,0 +1,3 @@
-- no-transaction
PRAGMA JOURNAL_MODE = WAL;
@@ -0,0 +1 @@
DROP TABLE migrations_reversible_test;
@@ -0,0 +1,7 @@
CREATE TABLE migrations_reversible_test (
some_id BIGINT NOT NULL PRIMARY KEY,
some_payload BIGINT NOT NUll
);
INSERT INTO migrations_reversible_test (some_id, some_payload)
VALUES (1, 100);
@@ -0,0 +1,2 @@
UPDATE migrations_reversible_test
SET some_payload = some_payload - 1;
@@ -0,0 +1,2 @@
UPDATE migrations_reversible_test
SET some_payload = some_payload + 1;
@@ -0,0 +1,7 @@
CREATE TABLE migrations_simple_test (
some_id BIGINT NOT NULL PRIMARY KEY,
some_payload BIGINT NOT NUll
);
INSERT INTO migrations_simple_test (some_id, some_payload)
VALUES (1, 100);
@@ -0,0 +1,30 @@
-- Perform a tricky conversion of the payload.
--
-- This script will only succeed once and will fail if executed twice.
-- set up temporary target column
ALTER TABLE migrations_simple_test
ADD some_payload_tmp TEXT;
-- perform conversion
-- This will fail if `some_payload` is already a string column due to the addition.
-- We add a suffix after the addition to ensure that the SQL database does not silently cast the string back to an
-- integer.
UPDATE migrations_simple_test
SET some_payload_tmp = CAST((some_payload + 10) AS TEXT) || '_suffix';
-- remove original column including the content
ALTER TABLE migrations_simple_test
DROP COLUMN some_payload;
-- prepare new payload column (nullable, so we can copy over the data)
ALTER TABLE migrations_simple_test
ADD some_payload TEXT;
-- copy new values
UPDATE migrations_simple_test
SET some_payload = some_payload_tmp;
-- clean up
ALTER TABLE migrations_simple_test
DROP COLUMN some_payload_tmp;
+78
View File
@@ -0,0 +1,78 @@
use sqlx::{AssertSqlSafe, Connection, Error, SqliteConnection};
// https://rustsec.org/advisories/RUSTSEC-2024-0363.html
//
// Similar theory to the Postgres exploit in `tests/postgres/rustsec.rs` but much simpler
// since we just want to overflow the query length itself.
#[sqlx::test]
async fn rustsec_2024_0363() -> anyhow::Result<()> {
let overflow_len = 4 * 1024 * 1024 * 1024; // 4 GiB
// `real_query_prefix` plus `fake_message` will be the first query that SQLite "sees"
//
// Rather contrived because this already represents a regular SQL injection,
// but this is the easiest way to demonstrate the exploit for SQLite.
let real_query_prefix = "INSERT INTO injection_target(message) VALUES ('";
let fake_message = "fake_msg') RETURNING id;";
let real_query_suffix = "') RETURNING id";
// Our actual payload is another query
let real_payload =
"\nUPDATE injection_target SET message = 'you''ve been pwned!' WHERE id = 1;\n--";
// This will parse the query up to `real_payload`.
let fake_payload_len = real_query_prefix.len() + fake_message.len();
// Pretty easy to see that this will overflow to `fake_payload_len`
let target_len = overflow_len + fake_payload_len;
let inject_len = target_len - real_query_prefix.len() - real_query_suffix.len();
let pad_len = inject_len - fake_message.len() - real_payload.len();
let mut injected_value = String::with_capacity(inject_len);
injected_value.push_str(fake_message);
injected_value.push_str(real_payload);
let padding = " ".repeat(pad_len);
injected_value.push_str(&padding);
let query = format!("{real_query_prefix}{injected_value}{real_query_suffix}");
assert_eq!(query.len(), target_len);
let mut conn = SqliteConnection::connect("sqlite://:memory:").await?;
sqlx::raw_sql(
"CREATE TABLE injection_target(id INTEGER PRIMARY KEY, message TEXT);\n\
INSERT INTO injection_target(message) VALUES ('existing message');",
)
.execute(&mut conn)
.await?;
let res = sqlx::raw_sql(AssertSqlSafe(query)).execute(&mut conn).await;
if let Err(e) = res {
// Connection rejected the query; we're happy.
if matches!(e, Error::Protocol(_)) {
return Ok(());
}
panic!("unexpected error: {e:?}");
}
let messages: Vec<String> =
sqlx::query_scalar("SELECT message FROM injection_target ORDER BY id")
.fetch_all(&mut conn)
.await?;
// If the injection succeeds, `messages` will look like:
// ["you've been pwned!'.to_string(), "fake_msg".to_string()]
assert_eq!(
messages,
["existing message".to_string(), "fake_msg".to_string()]
);
// Injection didn't affect our database; we're happy.
Ok(())
}
+37
View File
@@ -0,0 +1,37 @@
-- https://github.com/prisma/database-schema-examples/tree/master/postgres/basic-twitter#basic-twitter
CREATE TABLE tweet (
id BIGINT NOT NULL PRIMARY KEY,
text TEXT NOT NULL,
is_sent BOOLEAN NOT NULL DEFAULT TRUE,
owner_id BIGINT
);
INSERT INTO tweet(id, text, owner_id)
VALUES (1, '#sqlx is pretty cool!', 1);
--
CREATE TABLE tweet_reply (
id BIGINT NOT NULL PRIMARY KEY,
tweet_id BIGINT NOT NULL,
text TEXT NOT NULL,
owner_id BIGINT,
CONSTRAINT tweet_id_fk FOREIGN KEY (tweet_id) REFERENCES tweet(id)
);
INSERT INTO tweet_reply(id, tweet_id, text, owner_id)
VALUES (1, 1, 'Yeah! #sqlx is indeed pretty cool!', 1);
--
CREATE TABLE accounts (
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
is_active BOOLEAN
);
INSERT INTO accounts(id, name, is_active)
VALUES (1, 'Herp Derpinson', 1);
CREATE VIEW accounts_view as
SELECT *
FROM accounts;
--
CREATE TABLE products (
product_no INTEGER,
name TEXT,
price NUMERIC,
CONSTRAINT price_greater_than_zero CHECK (price > 0)
);
+205
View File
@@ -0,0 +1,205 @@
#![cfg(sqlite_test_sqlcipher)]
use std::str::FromStr;
use sqlx::sqlite::SqliteQueryResult;
use sqlx::{query, Connection, SqliteConnection};
use sqlx::{sqlite::SqliteConnectOptions, ConnectOptions};
use tempfile::TempDir;
async fn new_db_url() -> anyhow::Result<(String, TempDir)> {
let dir = TempDir::new()?;
let filepath = dir.path().join("database.sqlite3");
Ok((format!("sqlite://{}", filepath.display()), dir))
}
async fn fill_db(conn: &mut SqliteConnection) -> anyhow::Result<SqliteQueryResult> {
conn.transaction(|tx| {
Box::pin(async move {
query(
"
CREATE TABLE Company(
Id INT PRIMARY KEY NOT NULL,
Name TEXT NOT NULL,
Salary REAL
);
",
)
.execute(&mut **tx)
.await?;
query(
r#"
INSERT INTO Company(Id, Name, Salary)
VALUES
(1, "aaa", 111),
(2, "bbb", 222)
"#,
)
.execute(&mut **tx)
.await
})
})
.await
.map_err(|e| e.into())
}
#[sqlx_macros::test]
async fn it_encrypts() -> anyhow::Result<()> {
let (url, _dir) = new_db_url().await?;
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("key", "the_password")
.create_if_missing(true)
.connect()
.await?;
fill_db(&mut conn).await?;
// Create another connection without key, query should fail
let mut conn = SqliteConnectOptions::from_str(&url)?.connect().await?;
assert!(conn
.transaction(|tx| {
Box::pin(async move { query("SELECT * FROM Company;").fetch_all(&mut **tx).await })
})
.await
.is_err());
Ok(())
}
#[sqlx_macros::test]
async fn it_can_store_and_read_encrypted_data() -> anyhow::Result<()> {
let (url, _dir) = new_db_url().await?;
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("key", "the_password")
.create_if_missing(true)
.connect()
.await?;
fill_db(&mut conn).await?;
// Create another connection with valid key
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("key", "the_password")
.connect()
.await?;
let result = conn
.transaction(|tx| {
Box::pin(async move { query("SELECT * FROM Company;").fetch_all(&mut **tx).await })
})
.await?;
assert!(result.len() > 0);
Ok(())
}
#[sqlx_macros::test]
async fn it_fails_if_password_is_incorrect() -> anyhow::Result<()> {
let (url, _dir) = new_db_url().await?;
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("key", "the_password")
.create_if_missing(true)
.connect()
.await?;
fill_db(&mut conn).await?;
// Connection with invalid key should not allow to execute queries
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("key", "BADBADBAD")
.connect()
.await?;
assert!(conn
.transaction(|tx| {
Box::pin(async move { query("SELECT * FROM Company;").fetch_all(&mut **tx).await })
})
.await
.is_err());
Ok(())
}
#[sqlx_macros::test]
async fn it_honors_order_of_encryption_pragmas() -> anyhow::Result<()> {
let (url, _dir) = new_db_url().await?;
// Make call of cipher configuration mixed with other pragmas,
// it should have no effect, encryption related pragmas should be
// executed first and allow to establish valid connection
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("cipher_kdf_algorithm", "PBKDF2_HMAC_SHA1")
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
.pragma("cipher_page_size", "1024")
.pragma("key", "the_password")
.foreign_keys(true)
.pragma("kdf_iter", "64000")
.auto_vacuum(sqlx::sqlite::SqliteAutoVacuum::Incremental)
.pragma("cipher_hmac_algorithm", "HMAC_SHA1")
.create_if_missing(true)
.connect()
.await?;
fill_db(&mut conn).await?;
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("dummy", "pragma")
// The cipher configuration set on first connection is
// version 3 of SQLCipher, so for second it's enough to set
// the compatibility mode.
.pragma("cipher_compatibility", "3")
.pragma("key", "the_password")
.connect()
.await?;
let result = conn
.transaction(|tx| {
Box::pin(async move { query("SELECT * FROM COMPANY;").fetch_all(&mut **tx).await })
})
.await?;
assert!(result.len() > 0);
Ok(())
}
#[sqlx_macros::test]
async fn it_allows_to_rekey_the_db() -> anyhow::Result<()> {
let (url, _dir) = new_db_url().await?;
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("key", "the_password")
.create_if_missing(true)
.connect()
.await?;
fill_db(&mut conn).await?;
// The 'pragma rekey' can be called at any time
query("PRAGMA rekey = new_password;")
.execute(&mut conn)
.await?;
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("dummy", "pragma")
.pragma("key", "new_password")
.connect()
.await?;
let result = conn
.transaction(|tx| {
Box::pin(async move { query("SELECT * FROM COMPANY;").fetch_all(&mut **tx).await })
})
.await?;
assert!(result.len() > 0);
Ok(())
}
Binary file not shown.
+1439
View File
File diff suppressed because it is too large Load Diff
+107
View File
@@ -0,0 +1,107 @@
// The no-arg variant is covered by other tests already.
use sqlx::{Row, SqlitePool};
const MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!("tests/sqlite/migrations");
#[sqlx::test]
async fn it_gets_a_pool(pool: SqlitePool) -> sqlx::Result<()> {
let mut conn = pool.acquire().await?;
// https://www.sqlite.org/pragma.html#pragma_database_list
let db = sqlx::query("PRAGMA database_list")
.fetch_one(&mut *conn)
.await?;
let db_name = db.get::<String, _>(2);
assert!(
db_name.ends_with("target/sqlx/test-dbs/sqlite_test_attr/it_gets_a_pool.sqlite"),
"db_name: {:?}",
db_name
);
Ok(())
}
// This should apply migrations and then `fixtures/users.sql`
#[sqlx::test(migrations = "tests/sqlite/migrations", fixtures("users"))]
async fn it_gets_users(pool: SqlitePool) -> sqlx::Result<()> {
let usernames: Vec<String> =
sqlx::query_scalar(r#"SELECT username FROM "user" ORDER BY username"#)
.fetch_all(&pool)
.await?;
assert_eq!(usernames, ["alice", "bob"]);
let post_exists: bool = sqlx::query_scalar("SELECT exists(SELECT 1 FROM post)")
.fetch_one(&pool)
.await?;
assert!(!post_exists);
let comment_exists: bool = sqlx::query_scalar("SELECT exists(SELECT 1 FROM comment)")
.fetch_one(&pool)
.await?;
assert!(!comment_exists);
Ok(())
}
#[sqlx::test(migrations = "tests/sqlite/migrations", fixtures("users", "posts"))]
async fn it_gets_posts(pool: SqlitePool) -> sqlx::Result<()> {
let post_contents: Vec<String> =
sqlx::query_scalar("SELECT content FROM post ORDER BY created_at")
.fetch_all(&pool)
.await?;
assert_eq!(
post_contents,
[
"This new computer is lightning-fast!",
"@alice is a haxxor :("
]
);
let comment_exists: bool = sqlx::query_scalar("SELECT exists(SELECT 1 FROM comment)")
.fetch_one(&pool)
.await?;
assert!(!comment_exists);
Ok(())
}
// Try `migrator`
#[sqlx::test(migrator = "MIGRATOR", fixtures("users", "posts", "comments"))]
async fn it_gets_comments(pool: SqlitePool) -> sqlx::Result<()> {
let post_1_comments: Vec<String> =
sqlx::query_scalar("SELECT content FROM comment WHERE post_id = ? ORDER BY created_at")
.bind(&1)
.fetch_all(&pool)
.await?;
assert_eq!(
post_1_comments,
["lol bet ur still bad, 1v1 me", "you're on!"]
);
let post_2_comments: Vec<String> =
sqlx::query_scalar("SELECT content FROM comment WHERE post_id = ? ORDER BY created_at")
.bind(&2)
.fetch_all(&pool)
.await?;
assert_eq!(post_2_comments, ["lol you're just mad you lost :P"]);
Ok(())
}
#[sqlx::test(
migrations = "tests/sqlite/migrations",
fixtures(path = "./fixtures", scripts("users", "posts"))
)]
async fn this_should_compile(_pool: SqlitePool) -> sqlx::Result<()> {
Ok(())
}
+282
View File
@@ -0,0 +1,282 @@
extern crate time_ as time;
use sqlx::sqlite::{Sqlite, SqliteRow};
use sqlx::Type;
use sqlx_core::executor::Executor;
use sqlx_core::row::Row;
use sqlx_core::types::Text;
use sqlx_test::new;
use sqlx_test::test_type;
use std::borrow::Cow;
use std::net::SocketAddr;
use std::rc::Rc;
use std::sync::Arc;
test_type!(null<Option<i32>>(Sqlite,
"NULL" == None::<i32>
));
test_type!(bool(Sqlite, "FALSE" == false, "TRUE" == true));
test_type!(i32(Sqlite, "94101" == 94101_i32));
test_type!(i64(Sqlite, "9358295312" == 9358295312_i64));
// NOTE: This behavior can be surprising. Floating-point parameters are widening to double which can
// result in strange rounding.
test_type!(f32(Sqlite, "3.1410000324249268" == 3.141f32 as f64 as f32));
test_type!(f64(Sqlite, "939399419.1225182" == 939399419.1225182_f64));
test_type!(str<String>(Sqlite,
"'this is foo'" == "this is foo",
"cast(x'7468697320006973206E756C2D636F6E7461696E696E67' as text)" == "this \0is nul-containing",
"''" == ""
));
test_type!(null_str<Option<String>>(Sqlite,
"NULL" == None::<String>
));
test_type!(bytes<Vec<u8>>(Sqlite,
"X'DEADBEEF'"
== vec![0xDE_u8, 0xAD, 0xBE, 0xEF],
"X''"
== Vec::<u8>::new(),
"X'0000000052'"
== vec![0_u8, 0, 0, 0, 0x52]
));
#[cfg(feature = "json")]
mod json_tests {
use super::*;
use serde_json::{json, Value as JsonValue};
use sqlx::types::Json;
use sqlx_test::test_type;
test_type!(json<JsonValue>(
Sqlite,
"'\"Hello, World\"'" == json!("Hello, World"),
"'\"😎\"'" == json!("😎"),
"'\"🙋‍♀️\"'" == json!("🙋‍♀️"),
"'[\"Hello\",\"World!\"]'" == json!(["Hello", "World!"])
));
#[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq)]
struct Friend {
name: String,
age: u32,
}
test_type!(json_struct<Json<Friend>>(
Sqlite,
"\'{\"name\":\"Joe\",\"age\":33}\'" == Json(Friend { name: "Joe".to_string(), age: 33 })
));
// NOTE: This is testing recursive (and transparent) usage of the `Json` wrapper. You don't
// need to wrap the Vec in Json<_> to make the example work.
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
struct Customer {
json_column: Json<Vec<i64>>,
}
test_type!(json_struct_json_column<Json<Customer>>(
Sqlite,
"\'{\"json_column\":[1,2]}\'" == Json(Customer { json_column: Json(vec![1, 2]) })
));
#[sqlx_macros::test]
async fn it_json_extracts() -> anyhow::Result<()> {
let mut conn = new::<Sqlite>().await?;
let value = sqlx::query("select JSON_EXTRACT(JSON('{ \"number\": 42 }'), '$.number') = ?1")
.bind(42_i32)
.try_map(|row: SqliteRow| row.try_get::<bool, _>(0))
.fetch_one(&mut conn)
.await?;
assert!(value);
Ok(())
}
}
#[cfg(feature = "chrono")]
mod chrono {
use super::*;
use sqlx::types::chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, TimeZone, Utc};
test_type!(chrono_naive_date_time<NaiveDateTime>(Sqlite, "SELECT datetime({0}) is datetime(?), {0}, ?",
"'2019-01-02 05:10:20'" == NaiveDate::from_ymd_opt(2019, 1, 2).unwrap().and_hms_opt(5, 10, 20).unwrap()
));
test_type!(chrono_date_time_utc<DateTime::<Utc>>(Sqlite, "SELECT datetime({0}) is datetime(?), {0}, ?",
"'1996-12-20T00:39:57+00:00'" == Utc.with_ymd_and_hms(1996, 12, 20, 0, 39, 57).unwrap()
));
test_type!(chrono_date_time_fixed_offset<DateTime::<FixedOffset>>(Sqlite, "SELECT datetime({0}) is datetime(?), {0}, ?",
"'2016-11-08T03:50:23-05:00'" == DateTime::<Utc>::from(FixedOffset::west_opt(5 * 3600).unwrap().with_ymd_and_hms(2016, 11, 08, 3, 50, 23).unwrap())
));
}
#[cfg(feature = "time")]
mod time_tests {
use super::*;
use sqlx::types::time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
use time::macros::{date, datetime, time};
test_type!(time_offset_date_time<OffsetDateTime>(
Sqlite,
"SELECT datetime({0}) is datetime(?), {0}, ?",
"'2015-11-19 01:01:39+01:00'" == datetime!(2015 - 11 - 19 1:01:39 +1),
"'2014-10-18 00:00:38.697+00:00'" == datetime!(2014 - 10 - 18 00:00:38.697 +0),
"'2013-09-17 23:59-01:00'" == datetime!(2013 - 9 - 17 23:59 -1),
"'2016-03-07T22:36:55.135+03:30'" == datetime!(2016 - 3 - 7 22:36:55.135 +3:30),
"'2017-04-11T14:35+02:00'" == datetime!(2017 - 4 - 11 14:35 +2),
));
test_type!(time_primitive_date_time<PrimitiveDateTime>(
Sqlite,
"SELECT datetime({0}) is datetime(?), {0}, ?",
"'2019-01-02 05:10:20'" == datetime!(2019 - 1 - 2 5:10:20),
"'2018-12-01 04:09:19.543'" == datetime!(2018 - 12 - 1 4:09:19.543),
"'2017-11-30 03:08'" == datetime!(2017 - 11 - 30 3:08),
"'2016-10-29T02:07:17'" == datetime!(2016 - 10 - 29 2:07:17),
"'2015-09-28T01:06:16.432'" == datetime!(2015 - 9 - 28 1:06:16.432),
"'2014-08-27T00:05'" == datetime!(2014 - 8 - 27 0:05),
"'2013-07-26 23:04:14Z'" == datetime!(2013 - 7 - 26 23:04:14),
"'2012-06-25 22:03:13.321Z'" == datetime!(2012 - 6 - 25 22:03:13.321),
"'2011-05-24 21:02Z'" == datetime!(2011 - 5 - 24 21:02),
"'2010-04-23T20:01:11Z'" == datetime!(2010 - 4 - 23 20:01:11),
"'2009-03-22T19:00:10.21Z'" == datetime!(2009 - 3 - 22 19:00:10.21),
"'2008-02-21T18:59Z'" == datetime!(2008 - 2 - 21 18:59:00),
));
test_type!(time_date<Date>(
Sqlite,
"SELECT date({0}) is date(?), {0}, ?",
"'2002-06-04'" == date!(2002 - 6 - 4),
));
test_type!(time_time<Time>(
Sqlite,
"SELECT time({0}) is time(?), {0}, ?",
"'21:46:32'" == time!(21:46:32),
"'20:45:31.133'" == time!(20:45:31.133),
"'19:44'" == time!(19:44),
));
}
#[cfg(feature = "bstr")]
mod bstr {
use super::*;
use sqlx::types::bstr::BString;
test_type!(bstring<BString>(Sqlite,
"cast('abc123' as blob)" == BString::from(&b"abc123"[..]),
"x'0001020304'" == BString::from(&b"\x00\x01\x02\x03\x04"[..])
));
}
#[cfg(feature = "uuid")]
test_type!(uuid<sqlx::types::Uuid>(Sqlite,
"x'b731678f636f4135bc6f19440c13bd19'"
== sqlx::types::Uuid::parse_str("b731678f-636f-4135-bc6f-19440c13bd19").unwrap(),
"x'00000000000000000000000000000000'"
== sqlx::types::Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap()
));
#[cfg(feature = "uuid")]
test_type!(uuid_hyphenated<sqlx::types::uuid::fmt::Hyphenated>(Sqlite,
"'b731678f-636f-4135-bc6f-19440c13bd19'"
== sqlx::types::Uuid::parse_str("b731678f-636f-4135-bc6f-19440c13bd19").unwrap().hyphenated(),
"'00000000-0000-0000-0000-000000000000'"
== sqlx::types::Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap().hyphenated()
));
#[cfg(feature = "uuid")]
test_type!(uuid_simple<sqlx::types::uuid::fmt::Simple>(Sqlite,
"'b731678f636f4135bc6f19440c13bd19'"
== sqlx::types::Uuid::parse_str("b731678f636f4135bc6f19440c13bd19").unwrap().simple(),
"'00000000000000000000000000000000'"
== sqlx::types::Uuid::parse_str("00000000000000000000000000000000").unwrap().simple()
));
test_type!(test_arc<Arc<i32>>(Sqlite, "1" == Arc::new(1i32)));
test_type!(test_cow<Cow<'_, i32>>(Sqlite, "1" == Cow::<i32>::Owned(1i32)));
test_type!(test_box<Box<i32>>(Sqlite, "1" == Box::new(1i32)));
test_type!(test_rc<Rc<i32>>(Sqlite, "1" == Rc::new(1i32)));
test_type!(test_box_str<Box<str>>(Sqlite, "'John'" == Box::<str>::from("John")));
test_type!(test_cow_str<Cow<'_, str>>(Sqlite, "'Phil'" == Cow::<'static, str>::from("Phil")));
test_type!(test_arc_str<Arc<str>>(Sqlite, "'1234'" == Arc::<str>::from("1234")));
test_type!(test_rc_str<Rc<str>>(Sqlite, "'5678'" == Rc::<str>::from("5678")));
test_type!(test_box_slice<Box<[u8]>>(Sqlite, "X'01020304'" == Box::<[u8]>::from([1,2,3,4])));
test_type!(test_cow_slice<Cow<'_, [u8]>>(Sqlite, "X'01020304'" == Cow::<'static, [u8]>::from(&[1,2,3,4])));
test_type!(test_arc_slice<Arc<[u8]>>(Sqlite, "X'01020304'" == Arc::<[u8]>::from([1,2,3,4])));
test_type!(test_rc_slice<Rc<[u8]>>(Sqlite, "X'01020304'" == Rc::<[u8]>::from([1,2,3,4])));
#[sqlx_macros::test]
async fn test_text_adapter() -> anyhow::Result<()> {
#[derive(sqlx::FromRow, Debug, PartialEq, Eq)]
struct Login {
user_id: i32,
socket_addr: Text<SocketAddr>,
#[cfg(feature = "time")]
login_at: time::OffsetDateTime,
}
let mut conn = new::<Sqlite>().await?;
conn.execute(
r#"
CREATE TEMPORARY TABLE user_login (
user_id INT PRIMARY KEY,
socket_addr TEXT NOT NULL,
login_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
"#,
)
.await?;
let user_id = 1234;
let socket_addr: SocketAddr = "198.51.100.47:31790".parse().unwrap();
sqlx::query("INSERT INTO user_login (user_id, socket_addr) VALUES (?, ?)")
.bind(user_id)
.bind(Text(socket_addr))
.execute(&mut conn)
.await?;
let last_login: Login =
sqlx::query_as("SELECT * FROM user_login ORDER BY login_at DESC LIMIT 1")
.fetch_one(&mut conn)
.await?;
assert_eq!(last_login.user_id, user_id);
assert_eq!(*last_login.socket_addr, socket_addr);
Ok(())
}
#[sqlx_macros::test]
async fn it_binds_with_borrowed_data() -> anyhow::Result<()> {
#[derive(Debug, Type, Clone)]
#[sqlx(rename_all = "lowercase")]
enum Status {
New,
Open,
Closed,
}
let owned = Status::New;
let mut conn = new::<Sqlite>().await?;
sqlx::query("select ?")
.bind(Cow::Borrowed(&owned))
.fetch_one(&mut conn)
.await?;
Ok(())
}