├── .gitattributes ├── sqlx-cli ├── LICENSE-MIT ├── LICENSE-APACHE ├── tests │ ├── ignored-chars │ │ ├── LF │ │ │ ├── .gitattributes │ │ │ ├── 1_user.sql │ │ │ ├── 2_post.sql │ │ │ └── 3_comment.sql │ │ ├── BOM │ │ │ ├── .gitattributes │ │ │ ├── 1_user.sql │ │ │ ├── 2_post.sql │ │ │ └── 3_comment.sql │ │ ├── CRLF │ │ │ ├── .gitattributes │ │ │ ├── 1_user.sql │ │ │ ├── 2_post.sql │ │ │ └── 3_comment.sql │ │ ├── oops-all-tabs │ │ │ ├── .gitattributes │ │ │ ├── 1_user.sql │ │ │ ├── 2_post.sql │ │ │ └── 3_comment.sql │ │ └── sqlx.toml │ ├── migrations_reversible │ │ ├── 20230101000000_test1.down.sql │ │ ├── 20230201000000_test2.down.sql │ │ ├── 20230301000000_test3.down.sql │ │ ├── 20230401000000_test4.down.sql │ │ ├── 20230501000000_test5.down.sql │ │ ├── 20230101000000_test1.up.sql │ │ ├── 20230201000000_test2.up.sql │ │ ├── 20230301000000_test3.up.sql │ │ ├── 20230401000000_test4.up.sql │ │ └── 20230501000000_test5.up.sql │ └── assets │ │ ├── config_default_type_reversible.toml │ │ ├── config_default_versioning_sequential.toml │ │ └── config_default_versioning_timestamp.toml └── src │ ├── completions.rs │ └── bin │ ├── sqlx.rs │ └── cargo-sqlx.rs ├── sqlx-core ├── LICENSE-MIT ├── LICENSE-APACHE └── src │ ├── ext │ └── mod.rs │ ├── rt │ ├── rt_async_io │ │ ├── mod.rs │ │ └── timeout.rs │ └── rt_tokio │ │ └── mod.rs │ ├── net │ └── mod.rs │ ├── io │ ├── buf_mut.rs │ ├── encode.rs │ ├── mod.rs │ ├── decode.rs │ ├── read_buf.rs │ └── write_and_flush.rs │ ├── any │ ├── error.rs │ ├── column.rs │ ├── query_result.rs │ ├── types │ │ └── bool.rs │ ├── transaction.rs │ ├── database.rs │ └── type_info.rs │ ├── migrate │ └── mod.rs │ ├── common │ └── mod.rs │ ├── type_info.rs │ ├── pool │ └── maybe.rs │ └── types │ └── bstr.rs ├── sqlx-macros ├── LICENSE-MIT └── LICENSE-APACHE ├── sqlx-mysql ├── LICENSE-MIT ├── LICENSE-APACHE └── src │ ├── io │ ├── mod.rs │ └── buf.rs │ ├── protocol │ ├── text │ │ ├── mod.rs │ │ ├── ping.rs │ │ ├── quit.rs │ │ └── query.rs │ ├── statement │ │ ├── mod.rs │ │ ├── prepare.rs │ │ ├── stmt_close.rs │ │ ├── execute.rs │ │ └── prepare_ok.rs │ ├── mod.rs │ ├── response │ │ ├── mod.rs │ │ └── eof.rs │ ├── row.rs │ ├── connect │ │ ├── mod.rs │ │ └── ssl_request.rs │ └── auth.rs │ ├── types │ ├── rust_decimal.rs │ ├── bigdecimal.rs │ └── bool.rs │ ├── column.rs │ ├── database.rs │ ├── query_result.rs │ ├── statement.rs │ └── row.rs ├── sqlx-sqlite ├── LICENSE-MIT ├── LICENSE-APACHE └── src │ ├── column.rs │ ├── types │ ├── bool.rs │ ├── text.rs │ ├── json.rs │ └── float.rs │ ├── options │ ├── auto_vacuum.rs │ ├── locking_mode.rs │ └── synchronous.rs │ ├── database.rs │ ├── transaction.rs │ └── query_result.rs ├── sqlx-test ├── LICENSE-MIT ├── LICENSE-APACHE └── Cargo.toml ├── sqlx-macros-core ├── LICENSE-MIT ├── LICENSE-APACHE ├── clippy.toml └── src │ ├── common.rs │ └── derives │ └── mod.rs ├── sqlx-postgres ├── LICENSE-MIT ├── LICENSE-APACHE └── src │ ├── types │ ├── chrono │ │ └── mod.rs │ ├── geometry │ │ └── mod.rs │ ├── ipnetwork │ │ └── mod.rs │ ├── time │ │ └── mod.rs │ ├── ipnet │ │ └── mod.rs │ ├── void.rs │ ├── rust_decimal-range.md │ ├── uuid.rs │ ├── text.rs │ ├── bigdecimal-range.md │ ├── bool.rs │ └── mac_address.rs │ ├── message │ ├── parse_complete.rs │ ├── sync.rs │ ├── terminate.rs │ ├── flush.rs │ ├── query.rs │ ├── notification.rs │ ├── close.rs │ ├── ssl_request.rs │ ├── backend_key_data.rs │ └── ready_for_query.rs │ ├── query_result.rs │ ├── options │ └── connect.rs │ └── database.rs ├── tests ├── .gitignore ├── sqlite │ ├── .gitignore │ ├── migrations_no_tx │ │ └── 0_vacuum.sql │ ├── sqlite.db │ ├── fixtures │ │ ├── users.sql │ │ ├── posts.sql │ │ └── comments.sql │ ├── migrations_reversible │ │ ├── 20220721124650_add_table.down.sql │ │ ├── 20220721125033_modify_column.down.sql │ │ ├── 20220721125033_modify_column.up.sql │ │ └── 20220721124650_add_table.up.sql │ ├── migrations_no_tx_reversible │ │ ├── 0_vacuum.up.sql │ │ └── 0_vacuum.down.sql │ ├── migrations │ │ ├── 1_user.sql │ │ ├── 2_post.sql │ │ └── 3_comment.sql │ ├── migrations_simple │ │ ├── 20220721115250_add_test_table.sql │ │ └── 20220721115524_convert_type.sql │ ├── derives.rs │ ├── any.rs │ └── setup.sql ├── migrate │ ├── migrations_symlink │ ├── migrations_reversible │ │ ├── 20220721124650_add_table.down.sql │ │ ├── 20220721125033_modify_column.down.sql │ │ ├── 20220721125033_modify_column.up.sql │ │ └── 20220721124650_add_table.up.sql │ ├── migrations_simple │ │ ├── 20220721115250_add_test_table.sql │ │ └── 20220721115524_convert_type.sql │ └── macro.rs ├── .env ├── postgres │ ├── test-query.sql │ ├── migrations_no_tx │ │ └── 0_create_db.sql │ ├── migrations_reversible │ │ ├── 20220721124650_add_table.down.sql │ │ ├── 20220721125033_modify_column.down.sql │ │ ├── 20220721125033_modify_column.up.sql │ │ └── 20220721124650_add_table.up.sql │ ├── migrations │ │ ├── 0_setup.sql │ │ ├── 1_user.sql │ │ ├── 2_post.sql │ │ └── 3_comment.sql │ ├── fixtures │ │ ├── users.sql │ │ ├── rustsec │ │ │ └── 2024_0363.sql │ │ ├── posts.sql │ │ └── comments.sql │ ├── pg_hba.conf │ ├── migrations_simple │ │ ├── 20220721115250_add_test_table.sql │ │ └── 20220721115524_convert_type.sql │ └── Dockerfile ├── fixtures │ ├── mysql │ │ ├── users.sql │ │ └── posts.sql │ └── postgres │ │ ├── users.sql │ │ └── posts.sql ├── mysql │ ├── fixtures │ │ ├── users.sql │ │ ├── posts.sql │ │ └── comments.sql │ ├── migrations_reversible │ │ ├── 20220721124650_add_table.down.sql │ │ ├── 20220721125033_modify_column.up.sql │ │ ├── 20220721125033_modify_column.down.sql │ │ └── 20220721124650_add_table.up.sql │ ├── my.cnf │ ├── migrations_simple │ │ ├── 20220721115250_add_test_table.sql │ │ └── 20220721115524_convert_type.sql │ ├── migrations │ │ ├── 1_user.sql │ │ ├── 2_post.sql │ │ └── 3_comment.sql │ ├── setup-mariadb.sql │ ├── Dockerfile │ └── setup.sql ├── ui │ ├── postgres │ │ ├── issue_30.rs │ │ ├── gated │ │ │ ├── uuid.rs │ │ │ ├── ipnetwork.rs │ │ │ ├── chrono.rs │ │ │ ├── uuid.stderr │ │ │ └── ipnetwork.stderr │ │ ├── unsupported-type.rs │ │ ├── deprecated_rename.rs │ │ ├── issue_30.stderr │ │ ├── wrong_param_type.rs │ │ ├── deprecated_rename.stderr │ │ └── unsupported-type.stderr │ ├── sqlite │ │ ├── expression-column-type.rs │ │ └── expression-column-type.stderr │ └── mysql │ │ └── gated │ │ ├── chrono.rs │ │ └── chrono.stderr ├── .dockerignore ├── certs │ ├── keys │ │ ├── ca.key │ │ ├── client.key │ │ └── server.key │ ├── .gitignore │ ├── server.crt │ ├── client.crt │ └── ca.crt ├── mssql │ ├── entrypoint.sh │ ├── configure-db.sh │ ├── setup.sql │ ├── mssql-2017.dockerfile │ ├── macros.rs │ ├── Dockerfile │ ├── types.rs │ └── describe.rs ├── README.md └── ui-tests.rs ├── examples ├── .gitignore ├── postgres │ ├── axum-social-with-tests │ │ ├── src │ │ │ ├── lib.rs │ │ │ ├── main.rs │ │ │ ├── http │ │ │ │ └── mod.rs │ │ │ └── password.rs │ │ ├── migrations │ │ │ ├── 1_user.sql │ │ │ ├── 2_post.sql │ │ │ └── 3_comment.sql │ │ ├── README.md │ │ ├── tests │ │ │ └── fixtures │ │ │ │ ├── posts.sql │ │ │ │ ├── users.sql │ │ │ │ └── comments.sql │ │ └── Cargo.toml │ ├── mockable-todos │ │ ├── .env.example │ │ ├── migrations │ │ │ └── 20200718111257_todos.sql │ │ ├── docker-compose.yml │ │ ├── Cargo.toml │ │ └── README.md │ ├── multi-database │ │ ├── sqlx.toml │ │ ├── accounts │ │ │ ├── migrations │ │ │ │ ├── 03_session.sql │ │ │ │ ├── 02_account.sql │ │ │ │ └── 01_setup.sql │ │ │ ├── sqlx.toml │ │ │ └── Cargo.toml │ │ ├── payments │ │ │ ├── sqlx.toml │ │ │ ├── Cargo.toml │ │ │ └── migrations │ │ │ │ └── 01_setup.sql │ │ ├── src │ │ │ └── migrations │ │ │ │ ├── 02_purchase.sql │ │ │ │ └── 01_setup.sql │ │ └── Cargo.toml │ ├── multi-tenant │ │ ├── sqlx.toml │ │ ├── accounts │ │ │ ├── migrations │ │ │ │ ├── 03_session.sql │ │ │ │ ├── 02_account.sql │ │ │ │ └── 01_setup.sql │ │ │ ├── sqlx.toml │ │ │ └── Cargo.toml │ │ ├── payments │ │ │ ├── sqlx.toml │ │ │ ├── Cargo.toml │ │ │ └── migrations │ │ │ │ └── 01_setup.sql │ │ ├── src │ │ │ └── migrations │ │ │ │ ├── 02_purchase.sql │ │ │ │ └── 01_setup.sql │ │ └── Cargo.toml │ ├── json │ │ ├── migrations │ │ │ └── 20200824190010_json.sql │ │ ├── Cargo.toml │ │ └── README.md │ ├── todos │ │ ├── migrations │ │ │ └── 20200718111257_todos.sql │ │ ├── Cargo.toml │ │ └── README.md │ ├── transaction │ │ ├── migrations │ │ │ └── 20200718111257_todos.sql │ │ ├── Cargo.toml │ │ └── README.md │ ├── files │ │ ├── queries │ │ │ ├── list_all_posts.sql │ │ │ └── insert_seed_data.sql │ │ ├── migrations │ │ │ └── 20220712221654_files.sql │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src │ │ │ └── main.rs │ ├── preferred-crates │ │ ├── sqlx.toml │ │ ├── src │ │ │ └── migrations │ │ │ │ ├── 02_users.sql │ │ │ │ └── 01_setup.sql │ │ ├── uses-time │ │ │ └── Cargo.toml │ │ ├── uses-rust-decimal │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── lib.rs │ │ └── Cargo.toml │ ├── listen │ │ ├── Cargo.toml │ │ └── README.md │ └── chat │ │ ├── Cargo.toml │ │ └── README.md ├── mysql │ └── todos │ │ ├── migrations │ │ └── 20200718111257_todos.sql │ │ ├── Cargo.toml │ │ └── README.md └── sqlite │ ├── todos │ ├── migrations │ │ └── 20200718111257_todos.sql │ ├── Cargo.toml │ └── README.md │ └── extension │ ├── Cargo.toml │ ├── download-extension.sh │ ├── sqlx.toml │ └── migrations │ └── 20250203094951_addresses.sql ├── contrib └── ide │ └── vscode │ └── settings.json ├── rust-toolchain.toml ├── .editorconfig ├── .gitignore ├── src └── any │ └── install_drivers_note.md ├── clippy.toml ├── .github └── ISSUE_TEMPLATE │ ├── config.yml │ └── feature_request.yml └── LICENSE-MIT /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /sqlx-cli/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /sqlx-core/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /sqlx-macros/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /sqlx-mysql/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /sqlx-sqlite/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /sqlx-test/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /sqlx-cli/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /sqlx-core/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /sqlx-macros-core/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /sqlx-macros/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /sqlx-mysql/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /sqlx-postgres/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /sqlx-sqlite/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /sqlx-test/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | !.env 2 | __pycache__ 3 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | sqlite/todos/todos.db 2 | -------------------------------------------------------------------------------- /sqlx-macros-core/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /sqlx-postgres/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /tests/sqlite/.gitignore: -------------------------------------------------------------------------------- 1 | sqlite.test.db 2 | 3 | -------------------------------------------------------------------------------- /tests/migrate/migrations_symlink: -------------------------------------------------------------------------------- 1 | migrations_simple -------------------------------------------------------------------------------- /sqlx-cli/tests/ignored-chars/LF/.gitattributes: -------------------------------------------------------------------------------- 1 | *.sql text eol=lf 2 | -------------------------------------------------------------------------------- /sqlx-cli/tests/ignored-chars/BOM/.gitattributes: -------------------------------------------------------------------------------- 1 | *.sql text eol=lf 2 | -------------------------------------------------------------------------------- /sqlx-cli/tests/ignored-chars/CRLF/.gitattributes: -------------------------------------------------------------------------------- 1 | *.sql text eol=crlf 2 | -------------------------------------------------------------------------------- /sqlx-cli/tests/ignored-chars/oops-all-tabs/.gitattributes: -------------------------------------------------------------------------------- 1 | *.sql text eol=lf 2 | -------------------------------------------------------------------------------- /sqlx-postgres/src/types/chrono/mod.rs: -------------------------------------------------------------------------------- 1 | mod date; 2 | mod datetime; 3 | mod time; 4 | -------------------------------------------------------------------------------- /tests/sqlite/migrations_no_tx/0_vacuum.sql: -------------------------------------------------------------------------------- 1 | -- no-transaction 2 | 3 | VACUUM; 4 | -------------------------------------------------------------------------------- /sqlx-cli/tests/migrations_reversible/20230101000000_test1.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE test1; 2 | -------------------------------------------------------------------------------- /sqlx-cli/tests/migrations_reversible/20230201000000_test2.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE test2; 2 | -------------------------------------------------------------------------------- /sqlx-cli/tests/migrations_reversible/20230301000000_test3.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE test3; 2 | -------------------------------------------------------------------------------- /sqlx-cli/tests/migrations_reversible/20230401000000_test4.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE test4; 2 | -------------------------------------------------------------------------------- /sqlx-cli/tests/migrations_reversible/20230501000000_test5.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE test5; 2 | -------------------------------------------------------------------------------- /tests/.env: -------------------------------------------------------------------------------- 1 | # environment values for docker-compose 2 | COMPOSE_PROJECT_NAME=sqlx 3 | -------------------------------------------------------------------------------- /examples/postgres/axum-social-with-tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod http; 2 | 3 | mod password; 4 | -------------------------------------------------------------------------------- /sqlx-core/src/ext/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ustr; 2 | 3 | #[macro_use] 4 | pub mod async_stream; 5 | -------------------------------------------------------------------------------- /sqlx-core/src/rt/rt_async_io/mod.rs: -------------------------------------------------------------------------------- 1 | mod socket; 2 | 3 | mod timeout; 4 | pub use timeout::*; 5 | -------------------------------------------------------------------------------- /tests/postgres/test-query.sql: -------------------------------------------------------------------------------- 1 | SELECT id "id!", name from (VALUES (1, null)) accounts(id, name) 2 | -------------------------------------------------------------------------------- /tests/sqlite/sqlite.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchbadge/sqlx/HEAD/tests/sqlite/sqlite.db -------------------------------------------------------------------------------- /tests/postgres/migrations_no_tx/0_create_db.sql: -------------------------------------------------------------------------------- 1 | -- no-transaction 2 | 3 | CREATE DATABASE test_db; 4 | -------------------------------------------------------------------------------- /contrib/ide/vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.assist.importMergeBehaviour": "last" 3 | } 4 | -------------------------------------------------------------------------------- /tests/fixtures/mysql/users.sql: -------------------------------------------------------------------------------- 1 | insert into user(user_id, username) 2 | values (1, 'alice'), (2, 'bob'); 3 | -------------------------------------------------------------------------------- /tests/mysql/fixtures/users.sql: -------------------------------------------------------------------------------- 1 | insert into user(user_id, username) 2 | values (1, 'alice'), (2, 'bob'); 3 | -------------------------------------------------------------------------------- /tests/ui/postgres/issue_30.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let query = sqlx::query!("select 1 as \"'1\""); 3 | } 4 | -------------------------------------------------------------------------------- /examples/postgres/mockable-todos/.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL="postgres://postgres:password@localhost/todos" 2 | -------------------------------------------------------------------------------- /sqlx-cli/tests/assets/config_default_type_reversible.toml: -------------------------------------------------------------------------------- 1 | [migrate.defaults] 2 | migration-type = "reversible" -------------------------------------------------------------------------------- /sqlx-cli/tests/migrations_reversible/20230101000000_test1.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE test1(x INTEGER PRIMARY KEY); 2 | -------------------------------------------------------------------------------- /sqlx-cli/tests/migrations_reversible/20230201000000_test2.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE test2(x INTEGER PRIMARY KEY); 2 | -------------------------------------------------------------------------------- /sqlx-cli/tests/migrations_reversible/20230301000000_test3.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE test3(x INTEGER PRIMARY KEY); 2 | -------------------------------------------------------------------------------- /sqlx-cli/tests/migrations_reversible/20230401000000_test4.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE test4(x INTEGER PRIMARY KEY); 2 | -------------------------------------------------------------------------------- /sqlx-cli/tests/migrations_reversible/20230501000000_test5.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE test5(x INTEGER PRIMARY KEY); 2 | -------------------------------------------------------------------------------- /tests/migrate/migrations_reversible/20220721124650_add_table.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE migrations_reversible_test; 2 | -------------------------------------------------------------------------------- /tests/mysql/migrations_reversible/20220721124650_add_table.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE migrations_reversible_test; 2 | -------------------------------------------------------------------------------- /tests/postgres/migrations_reversible/20220721124650_add_table.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE migrations_reversible_test; 2 | -------------------------------------------------------------------------------- /tests/sqlite/fixtures/users.sql: -------------------------------------------------------------------------------- 1 | insert into "user"(user_id, username) 2 | values (1, 'alice'), (2, 'bob'); 3 | -------------------------------------------------------------------------------- /tests/sqlite/migrations_reversible/20220721124650_add_table.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE migrations_reversible_test; 2 | -------------------------------------------------------------------------------- /tests/ui/sqlite/expression-column-type.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _ = sqlx::query!("select 1 as id"); 3 | } 4 | -------------------------------------------------------------------------------- /tests/sqlite/migrations_no_tx_reversible/0_vacuum.up.sql: -------------------------------------------------------------------------------- 1 | -- no-transaction 2 | 3 | PRAGMA JOURNAL_MODE = WAL; 4 | -------------------------------------------------------------------------------- /sqlx-cli/tests/assets/config_default_versioning_sequential.toml: -------------------------------------------------------------------------------- 1 | [migrate.defaults] 2 | migration-versioning = "sequential" -------------------------------------------------------------------------------- /sqlx-cli/tests/assets/config_default_versioning_timestamp.toml: -------------------------------------------------------------------------------- 1 | [migrate.defaults] 2 | migration-versioning = "timestamp" -------------------------------------------------------------------------------- /tests/sqlite/migrations_no_tx_reversible/0_vacuum.down.sql: -------------------------------------------------------------------------------- 1 | -- no-transaction 2 | 3 | PRAGMA JOURNAL_MODE = DELETE; 4 | -------------------------------------------------------------------------------- /tests/.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !certs/* 3 | !certs/keys/* 4 | !mysql/my.cnf 5 | !mssql/*.sh 6 | !postgres/pg_hba.conf 7 | !*/*.sql 8 | -------------------------------------------------------------------------------- /tests/mysql/my.cnf: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | ssl-ca=/etc/mysql/ssl/ca.crt 3 | ssl-cert=/etc/mysql/ssl/server.crt 4 | ssl-key=/etc/mysql/ssl/server.key 5 | -------------------------------------------------------------------------------- /tests/postgres/migrations/0_setup.sql: -------------------------------------------------------------------------------- 1 | -- `gen_random_uuid()` wasn't added until Postgres 13 2 | create extension if not exists "uuid-ossp"; 3 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | # Note: should NOT increase during a minor/patch release cycle 2 | [toolchain] 3 | channel = "1.86" 4 | profile = "minimal" 5 | -------------------------------------------------------------------------------- /sqlx-core/src/rt/rt_tokio/mod.rs: -------------------------------------------------------------------------------- 1 | mod socket; 2 | 3 | pub fn available() -> bool { 4 | tokio::runtime::Handle::try_current().is_ok() 5 | } 6 | -------------------------------------------------------------------------------- /tests/mysql/migrations_reversible/20220721125033_modify_column.up.sql: -------------------------------------------------------------------------------- 1 | UPDATE migrations_reversible_test 2 | SET some_payload = some_payload + 1; 3 | -------------------------------------------------------------------------------- /tests/migrate/migrations_reversible/20220721125033_modify_column.down.sql: -------------------------------------------------------------------------------- 1 | UPDATE migrations_reversible_test 2 | SET some_payload = some_payload - 1; 3 | -------------------------------------------------------------------------------- /tests/migrate/migrations_reversible/20220721125033_modify_column.up.sql: -------------------------------------------------------------------------------- 1 | UPDATE migrations_reversible_test 2 | SET some_payload = some_payload + 1; 3 | -------------------------------------------------------------------------------- /tests/mysql/migrations_reversible/20220721125033_modify_column.down.sql: -------------------------------------------------------------------------------- 1 | UPDATE migrations_reversible_test 2 | SET some_payload = some_payload - 1; 3 | -------------------------------------------------------------------------------- /tests/postgres/migrations_reversible/20220721125033_modify_column.down.sql: -------------------------------------------------------------------------------- 1 | UPDATE migrations_reversible_test 2 | SET some_payload = some_payload - 1; 3 | -------------------------------------------------------------------------------- /tests/postgres/migrations_reversible/20220721125033_modify_column.up.sql: -------------------------------------------------------------------------------- 1 | UPDATE migrations_reversible_test 2 | SET some_payload = some_payload + 1; 3 | -------------------------------------------------------------------------------- /tests/sqlite/migrations_reversible/20220721125033_modify_column.down.sql: -------------------------------------------------------------------------------- 1 | UPDATE migrations_reversible_test 2 | SET some_payload = some_payload - 1; 3 | -------------------------------------------------------------------------------- /tests/sqlite/migrations_reversible/20220721125033_modify_column.up.sql: -------------------------------------------------------------------------------- 1 | UPDATE migrations_reversible_test 2 | SET some_payload = some_payload + 1; 3 | -------------------------------------------------------------------------------- /tests/certs/keys/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MC4CAQAwBQYDK2VwBCIEILnh8afwKgHjlFlCqooToMmzrI3DAifXtxhUtnCElFtn 3 | -----END PRIVATE KEY----- 4 | -------------------------------------------------------------------------------- /tests/certs/keys/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MC4CAQAwBQYDK2VwBCIEICauTALD/rWma9h+oROYjcxDrzI9KZlaVzwmhiTpSItd 3 | -----END PRIVATE KEY----- 4 | -------------------------------------------------------------------------------- /tests/certs/keys/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MC4CAQAwBQYDK2VwBCIEIAbYyLPV13NEkja/UUn+MB00kdTMivtC9DFysTjtZOXb 3 | -----END PRIVATE KEY----- 4 | -------------------------------------------------------------------------------- /examples/postgres/multi-database/sqlx.toml: -------------------------------------------------------------------------------- 1 | [migrate] 2 | # Move `migrations/` to under `src/` to separate it from subcrates. 3 | migrations-dir = "src/migrations" -------------------------------------------------------------------------------- /examples/postgres/multi-tenant/sqlx.toml: -------------------------------------------------------------------------------- 1 | [migrate] 2 | # Move `migrations/` to under `src/` to separate it from subcrates. 3 | migrations-dir = "src/migrations" -------------------------------------------------------------------------------- /sqlx-mysql/src/io/mod.rs: -------------------------------------------------------------------------------- 1 | mod buf; 2 | mod buf_mut; 3 | 4 | pub use buf::MySqlBufExt; 5 | pub use buf_mut::MySqlBufMutExt; 6 | 7 | pub(crate) use sqlx_core::io::*; 8 | -------------------------------------------------------------------------------- /examples/postgres/json/migrations/20200824190010_json.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS people 2 | ( 3 | id BIGSERIAL PRIMARY KEY, 4 | person JSONB NOT NULL 5 | ); 6 | -------------------------------------------------------------------------------- /tests/postgres/migrations/1_user.sql: -------------------------------------------------------------------------------- 1 | create table "user" 2 | ( 3 | user_id uuid primary key default uuid_generate_v1mc(), 4 | username text unique not null 5 | ); 6 | -------------------------------------------------------------------------------- /sqlx-macros-core/clippy.toml: -------------------------------------------------------------------------------- 1 | [[disallowed-methods]] 2 | path = "std::env::var" 3 | reason = "use `crate::env()` instead, which optionally calls `proc_macro::tracked_env::var()`" 4 | -------------------------------------------------------------------------------- /sqlx-postgres/src/types/geometry/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod r#box; 2 | pub mod circle; 3 | pub mod line; 4 | pub mod line_segment; 5 | pub mod path; 6 | pub mod point; 7 | pub mod polygon; 8 | -------------------------------------------------------------------------------- /tests/fixtures/postgres/users.sql: -------------------------------------------------------------------------------- 1 | insert into "user"(user_id, username) 2 | values ('6592b7c0-b531-4613-ace5-94246b7ce0c3', 'alice'), ('297923c5-a83c-4052-bab0-030887154e52', 'bob'); 3 | -------------------------------------------------------------------------------- /tests/postgres/fixtures/users.sql: -------------------------------------------------------------------------------- 1 | insert into "user"(user_id, username) 2 | values ('6592b7c0-b531-4613-ace5-94246b7ce0c3', 'alice'), ('297923c5-a83c-4052-bab0-030887154e52', 'bob'); 3 | -------------------------------------------------------------------------------- /sqlx-core/src/net/mod.rs: -------------------------------------------------------------------------------- 1 | mod socket; 2 | pub mod tls; 3 | 4 | pub use socket::{ 5 | connect_tcp, connect_uds, BufferedSocket, Socket, SocketIntoBox, WithSocket, WriteBuffer, 6 | }; 7 | -------------------------------------------------------------------------------- /tests/mssql/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Start the script to create the DB and user 4 | /usr/config/configure-db.sh & 5 | 6 | # Start SQL Server 7 | /opt/mssql/bin/sqlservr 8 | -------------------------------------------------------------------------------- /tests/ui/postgres/gated/uuid.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _ = sqlx::query!("select 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid"); 3 | let _ = sqlx::query!("select $1::uuid", ()); 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | 10 | [*.yml] 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /tests/postgres/pg_hba.conf: -------------------------------------------------------------------------------- 1 | # only needed for certificate authentication tests 2 | # omit host to prevent fallback to non certificate authentication 3 | local all all trust 4 | hostssl all all all cert 5 | -------------------------------------------------------------------------------- /tests/sqlite/migrations/1_user.sql: -------------------------------------------------------------------------------- 1 | create table user 2 | ( 3 | -- integer primary keys are the most efficient in SQLite 4 | user_id integer primary key, 5 | username text unique not null 6 | ); 7 | -------------------------------------------------------------------------------- /sqlx-cli/tests/ignored-chars/LF/1_user.sql: -------------------------------------------------------------------------------- 1 | create table user 2 | ( 3 | -- integer primary keys are the most efficient in SQLite 4 | user_id integer primary key, 5 | username text unique not null 6 | ); 7 | -------------------------------------------------------------------------------- /tests/certs/.gitignore: -------------------------------------------------------------------------------- 1 | # Certificate-signing request does not need to be committed 2 | *.csr 3 | # Test certificates aren't meant to be permanent, so tracking what serial number to use is unnecessary. 4 | ca.srl 5 | -------------------------------------------------------------------------------- /sqlx-cli/tests/ignored-chars/BOM/1_user.sql: -------------------------------------------------------------------------------- 1 | create table user 2 | ( 3 | -- integer primary keys are the most efficient in SQLite 4 | user_id integer primary key, 5 | username text unique not null 6 | ); 7 | -------------------------------------------------------------------------------- /sqlx-cli/tests/ignored-chars/oops-all-tabs/1_user.sql: -------------------------------------------------------------------------------- 1 | create table user 2 | ( 3 | -- integer primary keys are the most efficient in SQLite 4 | user_id integer primary key, 5 | username text unique not null 6 | ); 7 | -------------------------------------------------------------------------------- /sqlx-postgres/src/types/ipnetwork/mod.rs: -------------------------------------------------------------------------------- 1 | mod ipaddr; 2 | 3 | // Parent module is named after the `ipnetwork` crate, this is named after the `IpNetwork` type. 4 | #[allow(clippy::module_inception)] 5 | mod ipnetwork; 6 | -------------------------------------------------------------------------------- /sqlx-cli/tests/ignored-chars/CRLF/1_user.sql: -------------------------------------------------------------------------------- 1 | create table user 2 | ( 3 | -- integer primary keys are the most efficient in SQLite 4 | user_id integer primary key, 5 | username text unique not null 6 | ); 7 | -------------------------------------------------------------------------------- /examples/postgres/todos/migrations/20200718111257_todos.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS todos 2 | ( 3 | id BIGSERIAL PRIMARY KEY, 4 | description TEXT NOT NULL, 5 | done BOOLEAN NOT NULL DEFAULT FALSE 6 | ); 7 | -------------------------------------------------------------------------------- /examples/postgres/transaction/migrations/20200718111257_todos.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS todos 2 | ( 3 | id BIGSERIAL PRIMARY KEY, 4 | description TEXT NOT NULL, 5 | done BOOLEAN NOT NULL DEFAULT FALSE 6 | ); 7 | -------------------------------------------------------------------------------- /examples/postgres/axum-social-with-tests/migrations/1_user.sql: -------------------------------------------------------------------------------- 1 | create table "user" 2 | ( 3 | user_id uuid primary key default gen_random_uuid(), 4 | username text unique not null, 5 | password_hash text not null 6 | ); 7 | -------------------------------------------------------------------------------- /examples/postgres/files/queries/list_all_posts.sql: -------------------------------------------------------------------------------- 1 | SELECT p.id as "post_id", 2 | p.title, 3 | p.body, 4 | u.id as "author_id", 5 | u.username as "author_username" 6 | FROM users u 7 | JOIN posts p on u.id = p.user_id; -------------------------------------------------------------------------------- /examples/postgres/mockable-todos/migrations/20200718111257_todos.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS todos 2 | ( 3 | id BIGSERIAL PRIMARY KEY, 4 | description TEXT NOT NULL, 5 | done BOOLEAN NOT NULL DEFAULT FALSE 6 | ); 7 | -------------------------------------------------------------------------------- /tests/ui/postgres/unsupported-type.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // we're probably not going to get around to the geometric types anytime soon 3 | let _ = sqlx::query!("select null::circle"); 4 | let _ = sqlx::query!("select $1::circle", panic!()); 5 | } 6 | -------------------------------------------------------------------------------- /examples/mysql/todos/migrations/20200718111257_todos.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS todos 2 | ( 3 | id BIGINT UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT, 4 | description TEXT NOT NULL, 5 | done BOOLEAN NOT NULL DEFAULT FALSE 6 | ); 7 | -------------------------------------------------------------------------------- /examples/sqlite/todos/migrations/20200718111257_todos.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS todos 2 | ( 3 | id INTEGER PRIMARY KEY NOT NULL, 4 | description TEXT NOT NULL, 5 | done BOOLEAN NOT NULL DEFAULT 0 6 | ); 7 | -------------------------------------------------------------------------------- /tests/ui/mysql/gated/chrono.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _ = sqlx::query!("select CONVERT(now(), DATE) date"); 3 | 4 | let _ = sqlx::query!("select CONVERT(now(), TIME) time"); 5 | 6 | let _ = sqlx::query!("select CONVERT(now(), DATETIME) datetime"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/ui/postgres/deprecated_rename.rs: -------------------------------------------------------------------------------- 1 | #[derive(sqlx::Type)] 2 | #[sqlx(rename = "foo")] 3 | enum Foo { 4 | One, 5 | Two, 6 | Three, 7 | } 8 | 9 | fn main() { 10 | compile_error!("trybuild test needs to fail for stderr checking"); 11 | } 12 | -------------------------------------------------------------------------------- /tests/mssql/configure-db.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Wait 60 seconds for SQL Server to start up 4 | sleep 60 5 | 6 | # Run the setup script to create the DB and the schema in the DB 7 | /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $SA_PASSWORD -d master -i setup.sql 8 | -------------------------------------------------------------------------------- /tests/mysql/migrations_simple/20220721115250_add_test_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE migrations_simple_test ( 2 | some_id BIGINT NOT NULL PRIMARY KEY, 3 | some_payload BIGINT NOT NUll 4 | ); 5 | 6 | INSERT INTO migrations_simple_test (some_id, some_payload) 7 | VALUES (1, 100); 8 | -------------------------------------------------------------------------------- /sqlx-cli/src/completions.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use clap::CommandFactory; 4 | use clap_complete::{generate, Shell}; 5 | 6 | use crate::opt::Command; 7 | 8 | pub fn run(shell: Shell) { 9 | generate(shell, &mut Command::command(), "sqlx", &mut io::stdout()) 10 | } 11 | -------------------------------------------------------------------------------- /tests/migrate/migrations_simple/20220721115250_add_test_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE migrations_simple_test ( 2 | some_id BIGINT NOT NULL PRIMARY KEY, 3 | some_payload BIGINT NOT NUll 4 | ); 5 | 6 | INSERT INTO migrations_simple_test (some_id, some_payload) 7 | VALUES (1, 100); 8 | -------------------------------------------------------------------------------- /tests/mysql/migrations/1_user.sql: -------------------------------------------------------------------------------- 1 | create table user 2 | ( 3 | -- integer primary keys are the most efficient in SQLite 4 | user_id integer primary key auto_increment, 5 | -- indexed text values have to have a max length 6 | username varchar(16) unique not null 7 | ); 8 | -------------------------------------------------------------------------------- /tests/postgres/migrations_simple/20220721115250_add_test_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE migrations_simple_test ( 2 | some_id BIGINT NOT NULL PRIMARY KEY, 3 | some_payload BIGINT NOT NUll 4 | ); 5 | 6 | INSERT INTO migrations_simple_test (some_id, some_payload) 7 | VALUES (1, 100); 8 | -------------------------------------------------------------------------------- /tests/sqlite/migrations_simple/20220721115250_add_test_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE migrations_simple_test ( 2 | some_id BIGINT NOT NULL PRIMARY KEY, 3 | some_payload BIGINT NOT NUll 4 | ); 5 | 6 | INSERT INTO migrations_simple_test (some_id, some_payload) 7 | VALUES (1, 100); 8 | -------------------------------------------------------------------------------- /tests/mysql/migrations_reversible/20220721124650_add_table.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE migrations_reversible_test ( 2 | some_id BIGINT NOT NULL PRIMARY KEY, 3 | some_payload BIGINT NOT NUll 4 | ); 5 | 6 | INSERT INTO migrations_reversible_test (some_id, some_payload) 7 | VALUES (1, 100); 8 | -------------------------------------------------------------------------------- /tests/migrate/migrations_reversible/20220721124650_add_table.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE migrations_reversible_test ( 2 | some_id BIGINT NOT NULL PRIMARY KEY, 3 | some_payload BIGINT NOT NUll 4 | ); 5 | 6 | INSERT INTO migrations_reversible_test (some_id, some_payload) 7 | VALUES (1, 100); 8 | -------------------------------------------------------------------------------- /tests/postgres/migrations_reversible/20220721124650_add_table.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE migrations_reversible_test ( 2 | some_id BIGINT NOT NULL PRIMARY KEY, 3 | some_payload BIGINT NOT NUll 4 | ); 5 | 6 | INSERT INTO migrations_reversible_test (some_id, some_payload) 7 | VALUES (1, 100); 8 | -------------------------------------------------------------------------------- /tests/sqlite/migrations_reversible/20220721124650_add_table.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE migrations_reversible_test ( 2 | some_id BIGINT NOT NULL PRIMARY KEY, 3 | some_payload BIGINT NOT NUll 4 | ); 5 | 6 | INSERT INTO migrations_reversible_test (some_id, some_payload) 7 | VALUES (1, 100); 8 | -------------------------------------------------------------------------------- /examples/postgres/multi-database/accounts/migrations/03_session.sql: -------------------------------------------------------------------------------- 1 | create table session 2 | ( 3 | session_token text primary key, -- random alphanumeric string 4 | account_id uuid not null references account (account_id), 5 | created_at timestamptz not null default now() 6 | ); 7 | -------------------------------------------------------------------------------- /tests/postgres/fixtures/rustsec/2024_0363.sql: -------------------------------------------------------------------------------- 1 | -- https://rustsec.org/advisories/RUSTSEC-2024-0363.html 2 | -- https://github.com/launchbadge/sqlx/issues/3440 3 | CREATE TABLE injection_target(id BIGSERIAL PRIMARY KEY, message TEXT); 4 | INSERT INTO injection_target(message) VALUES ('existing value'); 5 | -------------------------------------------------------------------------------- /tests/postgres/migrations/2_post.sql: -------------------------------------------------------------------------------- 1 | create table post ( 2 | post_id uuid primary key default uuid_generate_v1mc(), 3 | user_id uuid not null references "user"(user_id), 4 | content text not null, 5 | created_at timestamptz default now() 6 | ); 7 | 8 | create index on post(created_at desc); 9 | -------------------------------------------------------------------------------- /sqlx-core/src/io/buf_mut.rs: -------------------------------------------------------------------------------- 1 | use bytes::BufMut; 2 | 3 | pub trait BufMutExt: BufMut { 4 | fn put_str_nul(&mut self, s: &str); 5 | } 6 | 7 | impl BufMutExt for Vec { 8 | fn put_str_nul(&mut self, s: &str) { 9 | self.extend(s.as_bytes()); 10 | self.push(0); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/ui/postgres/gated/ipnetwork.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _ = sqlx::query!("select '127.0.0.1'::inet"); 3 | 4 | let _ = sqlx::query!("select '2001:4f8:3:ba::/64'::cidr"); 5 | 6 | let _ = sqlx::query!("select $1::inet", ()); 7 | 8 | let _ = sqlx::query!("select $1::cidr", ()); 9 | } 10 | -------------------------------------------------------------------------------- /examples/postgres/multi-database/payments/sqlx.toml: -------------------------------------------------------------------------------- 1 | [common] 2 | database-url-var = "PAYMENTS_DATABASE_URL" 3 | 4 | [macros.table-overrides.'payment'] 5 | 'payment_id' = "crate::PaymentId" 6 | 'account_id' = "accounts::AccountId" 7 | 8 | [macros.type-overrides] 9 | 'payment_status' = "crate::PaymentStatus" 10 | -------------------------------------------------------------------------------- /examples/postgres/multi-tenant/accounts/migrations/03_session.sql: -------------------------------------------------------------------------------- 1 | create table accounts.session 2 | ( 3 | session_token text primary key, -- random alphanumeric string 4 | account_id uuid not null references accounts.account (account_id), 5 | created_at timestamptz not null default now() 6 | ); 7 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/text/mod.rs: -------------------------------------------------------------------------------- 1 | mod column; 2 | mod ping; 3 | mod query; 4 | mod quit; 5 | mod row; 6 | 7 | pub(crate) use column::{ColumnDefinition, ColumnFlags, ColumnType}; 8 | pub(crate) use ping::Ping; 9 | pub(crate) use query::Query; 10 | pub(crate) use quit::Quit; 11 | pub(crate) use row::TextRow; 12 | -------------------------------------------------------------------------------- /sqlx-postgres/src/types/time/mod.rs: -------------------------------------------------------------------------------- 1 | mod date; 2 | mod datetime; 3 | 4 | // Parent module is named after the `time` crate, this module is named after the `TIME` SQL type. 5 | #[allow(clippy::module_inception)] 6 | mod time; 7 | 8 | #[rustfmt::skip] 9 | const PG_EPOCH: ::time::Date = ::time::macros::date!(2000-1-1); 10 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/statement/mod.rs: -------------------------------------------------------------------------------- 1 | mod execute; 2 | mod prepare; 3 | mod prepare_ok; 4 | mod row; 5 | mod stmt_close; 6 | 7 | pub(crate) use execute::Execute; 8 | pub(crate) use prepare::Prepare; 9 | pub(crate) use prepare_ok::PrepareOk; 10 | pub(crate) use row::BinaryRow; 11 | pub(crate) use stmt_close::StmtClose; 12 | -------------------------------------------------------------------------------- /tests/fixtures/mysql/posts.sql: -------------------------------------------------------------------------------- 1 | insert into post(post_id, user_id, content, created_at) 2 | values (1, 3 | 1, 4 | 'This new computer is lightning-fast!', 5 | timestamp(now(), '-1:00:00')), 6 | (2, 7 | 2, 8 | '@alice is a haxxor :(', 9 | timestamp(now(), '-0:30:00')); 10 | -------------------------------------------------------------------------------- /tests/mysql/fixtures/posts.sql: -------------------------------------------------------------------------------- 1 | insert into post(post_id, user_id, content, created_at) 2 | values (1, 3 | 1, 4 | 'This new computer is lightning-fast!', 5 | timestamp(now(), '-1:00:00')), 6 | (2, 7 | 2, 8 | '@alice is a haxxor :(', 9 | timestamp(now(), '-0:30:00')); 10 | -------------------------------------------------------------------------------- /tests/sqlite/fixtures/posts.sql: -------------------------------------------------------------------------------- 1 | insert into post(post_id, user_id, content, created_at) 2 | values (1, 3 | 1, 4 | 'This new computer is lightning-fast!', 5 | datetime('now', '-1 hour')), 6 | (2, 7 | 2, 8 | '@alice is a haxxor :(', 9 | datetime('now', '-30 minutes')); 10 | -------------------------------------------------------------------------------- /examples/postgres/axum-social-with-tests/migrations/2_post.sql: -------------------------------------------------------------------------------- 1 | create table post ( 2 | post_id uuid primary key default gen_random_uuid(), 3 | user_id uuid not null references "user"(user_id), 4 | content text not null, 5 | created_at timestamptz not null default now() 6 | ); 7 | 8 | create index on post(created_at desc); 9 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod auth; 2 | mod capabilities; 3 | pub(crate) mod connect; 4 | mod packet; 5 | pub(crate) mod response; 6 | mod row; 7 | pub(crate) mod statement; 8 | pub(crate) mod text; 9 | 10 | pub(crate) use capabilities::Capabilities; 11 | pub(crate) use packet::Packet; 12 | pub(crate) use row::Row; 13 | -------------------------------------------------------------------------------- /sqlx-postgres/src/types/ipnet/mod.rs: -------------------------------------------------------------------------------- 1 | // Prefer `ipnetwork` over `ipnet` because it was implemented first (want to avoid breaking change). 2 | #[cfg(not(feature = "ipnetwork"))] 3 | mod ipaddr; 4 | 5 | // Parent module is named after the `ipnet` crate, this is named after the `IpNet` type. 6 | #[allow(clippy::module_inception)] 7 | mod ipnet; 8 | -------------------------------------------------------------------------------- /sqlx-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-test" 3 | version = "0.1.0" 4 | edition.workspace = true 5 | publish = false 6 | rust-version.workspace = true 7 | 8 | [dependencies] 9 | sqlx = { default-features = false, path = ".." } 10 | env_logger = "0.11" 11 | dotenvy = "0.15.0" 12 | anyhow = "1.0.26" 13 | 14 | [lints] 15 | workspace = true 16 | -------------------------------------------------------------------------------- /examples/postgres/multi-tenant/payments/sqlx.toml: -------------------------------------------------------------------------------- 1 | [migrate] 2 | create-schemas = ["payments"] 3 | table-name = "payments._sqlx_migrations" 4 | 5 | [macros.table-overrides.'payments.payment'] 6 | 'payment_id' = "crate::PaymentId" 7 | 'account_id' = "accounts::AccountId" 8 | 9 | [macros.type-overrides] 10 | 'payments.payment_status' = "crate::PaymentStatus" 11 | -------------------------------------------------------------------------------- /tests/mysql/setup-mariadb.sql: -------------------------------------------------------------------------------- 1 | -- additional SQL to execute for MariaDB databases 2 | 3 | CREATE TABLE tweet_with_uuid 4 | ( 5 | -- UUID is only a bespoke datatype in MariaDB. 6 | id UUID PRIMARY KEY DEFAULT UUID(), 7 | created_at TIMESTAMP NOT NULL DEFAULT NOW(), 8 | text TEXT NOT NULL, 9 | owner_id UUID 10 | ); 11 | -------------------------------------------------------------------------------- /examples/postgres/transaction/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-postgres-transaction" 3 | version = "0.1.0" 4 | edition = "2021" 5 | workspace = "../../../" 6 | 7 | [dependencies] 8 | sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] } 9 | tokio = { version = "1.20.0", features = ["rt-multi-thread", "macros"]} 10 | -------------------------------------------------------------------------------- /examples/postgres/preferred-crates/sqlx.toml: -------------------------------------------------------------------------------- 1 | [migrate] 2 | # Move `migrations/` to under `src/` to separate it from subcrates. 3 | migrations-dir = "src/migrations" 4 | 5 | [macros.preferred-crates] 6 | # Keeps `time` from taking precedent even though it's enabled by a dependency. 7 | date-time = "chrono" 8 | # Same thing with `rust_decimal` 9 | numeric = "bigdecimal" 10 | -------------------------------------------------------------------------------- /sqlx-cli/tests/ignored-chars/oops-all-tabs/2_post.sql: -------------------------------------------------------------------------------- 1 | create table post 2 | ( 3 | post_id integer primary key, 4 | user_id integer not null references user (user_id), 5 | content text not null, 6 | -- Defaults have to be wrapped in parenthesis 7 | created_at datetime default (datetime('now')) 8 | ); 9 | 10 | create index post_created_at on post (created_at desc); 11 | -------------------------------------------------------------------------------- /tests/ui/postgres/issue_30.stderr: -------------------------------------------------------------------------------- 1 | error: column name "\'1" is invalid: "\'1" is not a valid Rust identifier 2 | --> $DIR/issue_30.rs:2:17 3 | | 4 | 2 | let query = sqlx::query!("select 1 as \"'1\""); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /tests/sqlite/migrations/2_post.sql: -------------------------------------------------------------------------------- 1 | create table post 2 | ( 3 | post_id integer primary key, 4 | user_id integer not null references user (user_id), 5 | content text not null, 6 | -- Defaults have to be wrapped in parenthesis 7 | created_at datetime default (datetime('now')) 8 | ); 9 | 10 | create index post_created_at on post (created_at desc); 11 | -------------------------------------------------------------------------------- /sqlx-cli/tests/ignored-chars/BOM/2_post.sql: -------------------------------------------------------------------------------- 1 | create table post 2 | ( 3 | post_id integer primary key, 4 | user_id integer not null references user (user_id), 5 | content text not null, 6 | -- Defaults have to be wrapped in parenthesis 7 | created_at datetime default (datetime('now')) 8 | ); 9 | 10 | create index post_created_at on post (created_at desc); 11 | -------------------------------------------------------------------------------- /sqlx-cli/tests/ignored-chars/LF/2_post.sql: -------------------------------------------------------------------------------- 1 | create table post 2 | ( 3 | post_id integer primary key, 4 | user_id integer not null references user (user_id), 5 | content text not null, 6 | -- Defaults have to be wrapped in parenthesis 7 | created_at datetime default (datetime('now')) 8 | ); 9 | 10 | create index post_created_at on post (created_at desc); 11 | -------------------------------------------------------------------------------- /tests/postgres/migrations/3_comment.sql: -------------------------------------------------------------------------------- 1 | create table comment ( 2 | comment_id uuid primary key default uuid_generate_v1mc(), 3 | post_id uuid not null references post(post_id), 4 | user_id uuid not null references "user"(user_id), 5 | content text not null, 6 | created_at timestamptz not null default now() 7 | ); 8 | 9 | create index on comment(created_at desc); 10 | -------------------------------------------------------------------------------- /examples/postgres/multi-database/accounts/sqlx.toml: -------------------------------------------------------------------------------- 1 | [common] 2 | database-url-var = "ACCOUNTS_DATABASE_URL" 3 | 4 | [macros.table-overrides.'account'] 5 | 'account_id' = "crate::AccountId" 6 | 'password_hash' = "sqlx::types::Text" 7 | 8 | [macros.table-overrides.'session'] 9 | 'session_token' = "crate::SessionToken" 10 | 'account_id' = "crate::AccountId" 11 | -------------------------------------------------------------------------------- /sqlx-cli/tests/ignored-chars/oops-all-tabs/3_comment.sql: -------------------------------------------------------------------------------- 1 | create table comment 2 | ( 3 | comment_id integer primary key, 4 | post_id integer not null references post (post_id), 5 | user_id integer not null references "user" (user_id), 6 | content text not null, 7 | created_at datetime default (datetime('now')) 8 | ); 9 | 10 | create index comment_created_at on comment (created_at desc); 11 | -------------------------------------------------------------------------------- /tests/mysql/migrations/2_post.sql: -------------------------------------------------------------------------------- 1 | create table post 2 | ( 3 | post_id integer primary key auto_increment, 4 | user_id integer not null references user (user_id), 5 | content text not null, 6 | -- Defaults have to be wrapped in parenthesis 7 | created_at datetime default current_timestamp 8 | ); 9 | 10 | create index post_created_at on post (created_at desc); 11 | -------------------------------------------------------------------------------- /examples/postgres/multi-database/accounts/migrations/02_account.sql: -------------------------------------------------------------------------------- 1 | create table account 2 | ( 3 | account_id uuid primary key default gen_random_uuid(), 4 | email text unique not null, 5 | password_hash text not null, 6 | created_at timestamptz not null default now(), 7 | updated_at timestamptz 8 | ); 9 | 10 | select trigger_updated_at('account'); 11 | -------------------------------------------------------------------------------- /sqlx-cli/tests/ignored-chars/CRLF/2_post.sql: -------------------------------------------------------------------------------- 1 | create table post 2 | ( 3 | post_id integer primary key, 4 | user_id integer not null references user (user_id), 5 | content text not null, 6 | -- Defaults have to be wrapped in parenthesis 7 | created_at datetime default (datetime('now')) 8 | ); 9 | 10 | create index post_created_at on post (created_at desc); 11 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/response/mod.rs: -------------------------------------------------------------------------------- 1 | //! Generic Response Packets 2 | //! 3 | //! 4 | //! 5 | 6 | mod eof; 7 | mod err; 8 | mod ok; 9 | mod status; 10 | 11 | pub use eof::EofPacket; 12 | pub use err::ErrPacket; 13 | pub use ok::OkPacket; 14 | pub use status::Status; 15 | -------------------------------------------------------------------------------- /tests/mysql/migrations/3_comment.sql: -------------------------------------------------------------------------------- 1 | create table comment 2 | ( 3 | comment_id integer primary key, 4 | post_id integer not null references post (post_id), 5 | user_id integer not null references user (user_id), 6 | content text not null, 7 | created_at datetime default current_timestamp 8 | ); 9 | 10 | create index comment_created_at on comment (created_at desc); 11 | -------------------------------------------------------------------------------- /examples/mysql/todos/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-mysql-todos" 3 | version = "0.1.0" 4 | edition = "2021" 5 | workspace = "../../../" 6 | 7 | [dependencies] 8 | anyhow = "1.0" 9 | sqlx = { path = "../../../", features = [ "mysql", "runtime-tokio", "tls-native-tls" ] } 10 | clap = { version = "4", features = ["derive"] } 11 | tokio = { version = "1.20.0", features = ["rt", "macros"]} 12 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/row.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | use bytes::Bytes; 4 | 5 | #[derive(Debug)] 6 | pub(crate) struct Row { 7 | pub(crate) storage: Bytes, 8 | pub(crate) values: Vec>>, 9 | } 10 | 11 | impl Row { 12 | pub(crate) fn get(&self, index: usize) -> Option<&[u8]> { 13 | self.values[index].clone().map(|col| &self.storage[col]) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/sqlite/migrations/3_comment.sql: -------------------------------------------------------------------------------- 1 | create table comment 2 | ( 3 | comment_id integer primary key, 4 | post_id integer not null references post (post_id), 5 | user_id integer not null references "user" (user_id), 6 | content text not null, 7 | created_at datetime default (datetime('now')) 8 | ); 9 | 10 | create index comment_created_at on comment (created_at desc); 11 | -------------------------------------------------------------------------------- /examples/sqlite/todos/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-sqlite-todos" 3 | version = "0.1.0" 4 | edition = "2018" 5 | workspace = "../../../" 6 | 7 | [dependencies] 8 | anyhow = "1.0" 9 | sqlx = { path = "../../../", features = [ "sqlite", "runtime-tokio", "tls-native-tls" ] } 10 | clap = { version = "4", features = ["derive"] } 11 | tokio = { version = "1.20.0", features = ["rt", "macros"]} 12 | -------------------------------------------------------------------------------- /examples/postgres/axum-social-with-tests/migrations/3_comment.sql: -------------------------------------------------------------------------------- 1 | create table comment ( 2 | comment_id uuid primary key default gen_random_uuid(), 3 | post_id uuid not null references post(post_id), 4 | user_id uuid not null references "user"(user_id), 5 | content text not null, 6 | created_at timestamptz not null default now() 7 | ); 8 | 9 | create index on comment(post_id, created_at); 10 | -------------------------------------------------------------------------------- /examples/postgres/listen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-postgres-listen" 3 | version = "0.1.0" 4 | edition = "2021" 5 | workspace = "../../../" 6 | 7 | [dependencies] 8 | sqlx = { path = "../../../", features = [ "runtime-tokio", "postgres" ] } 9 | futures-util = { version = "0.3.1", default-features = false } 10 | tokio = { version = "1.20.0", features = ["rt-multi-thread", "macros", "time"]} 11 | -------------------------------------------------------------------------------- /sqlx-cli/tests/ignored-chars/BOM/3_comment.sql: -------------------------------------------------------------------------------- 1 | create table comment 2 | ( 3 | comment_id integer primary key, 4 | post_id integer not null references post (post_id), 5 | user_id integer not null references "user" (user_id), 6 | content text not null, 7 | created_at datetime default (datetime('now')) 8 | ); 9 | 10 | create index comment_created_at on comment (created_at desc); 11 | -------------------------------------------------------------------------------- /sqlx-cli/tests/ignored-chars/LF/3_comment.sql: -------------------------------------------------------------------------------- 1 | create table comment 2 | ( 3 | comment_id integer primary key, 4 | post_id integer not null references post (post_id), 5 | user_id integer not null references "user" (user_id), 6 | content text not null, 7 | created_at datetime default (datetime('now')) 8 | ); 9 | 10 | create index comment_created_at on comment (created_at desc); 11 | -------------------------------------------------------------------------------- /examples/postgres/files/migrations/20220712221654_files.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS users 2 | ( 3 | id BIGSERIAL PRIMARY KEY, 4 | username TEXT NOT NULL 5 | ); 6 | 7 | CREATE TABLE IF NOT EXISTS posts 8 | ( 9 | id BIGSERIAL PRIMARY KEY, 10 | title TEXT NOT NULL, 11 | body TEXT NOT NULL, 12 | user_id BIGINT NOT NULL 13 | REFERENCES users (id) ON DELETE CASCADE 14 | ); 15 | -------------------------------------------------------------------------------- /examples/postgres/multi-tenant/accounts/migrations/02_account.sql: -------------------------------------------------------------------------------- 1 | create table accounts.account 2 | ( 3 | account_id uuid primary key default gen_random_uuid(), 4 | email text unique not null, 5 | password_hash text not null, 6 | created_at timestamptz not null default now(), 7 | updated_at timestamptz 8 | ); 9 | 10 | select accounts.trigger_updated_at('accounts.account'); 11 | -------------------------------------------------------------------------------- /examples/postgres/preferred-crates/src/migrations/02_users.sql: -------------------------------------------------------------------------------- 1 | create table users( 2 | id uuid primary key default gen_random_uuid(), 3 | username text not null, 4 | password_hash text not null, 5 | created_at timestamptz not null default now(), 6 | updated_at timestamptz 7 | ); 8 | 9 | create unique index users_username_unique on users(lower(username)); 10 | 11 | select trigger_updated_at('users'); 12 | -------------------------------------------------------------------------------- /sqlx-cli/tests/ignored-chars/CRLF/3_comment.sql: -------------------------------------------------------------------------------- 1 | create table comment 2 | ( 3 | comment_id integer primary key, 4 | post_id integer not null references post (post_id), 5 | user_id integer not null references "user" (user_id), 6 | content text not null, 7 | created_at datetime default (datetime('now')) 8 | ); 9 | 10 | create index comment_created_at on comment (created_at desc); 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built artifacts 2 | target/ 3 | 4 | # Project and editor files 5 | .vscode/ 6 | .idea/ 7 | *.vim 8 | *.vi 9 | 10 | # Environment 11 | .env 12 | 13 | # Shared-memory and WAL files created by SQLite. 14 | *-shm 15 | *-wal 16 | 17 | # Integration testing extension library for SQLite. 18 | ipaddr.dylib 19 | ipaddr.so 20 | 21 | # Temporary files from running the tests locally like they would be run from CI 22 | .sqlx 23 | -------------------------------------------------------------------------------- /examples/postgres/chat/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-postgres-chat" 3 | version = "0.1.0" 4 | edition = "2021" 5 | workspace = "../../../" 6 | 7 | [dependencies] 8 | sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] } 9 | tokio = { version = "1.20.0", features = [ "rt-multi-thread", "macros" ] } 10 | ratatui = "0.27.0" 11 | crossterm = "0.27.0" 12 | unicode-width = "0.1" 13 | -------------------------------------------------------------------------------- /examples/postgres/multi-database/src/migrations/02_purchase.sql: -------------------------------------------------------------------------------- 1 | create table purchase 2 | ( 3 | purchase_id uuid primary key default gen_random_uuid(), 4 | account_id uuid not null, 5 | payment_id uuid not null, 6 | amount numeric not null, 7 | created_at timestamptz not null default now(), 8 | updated_at timestamptz 9 | ); 10 | 11 | select trigger_updated_at('purchase'); 12 | -------------------------------------------------------------------------------- /examples/postgres/transaction/README.md: -------------------------------------------------------------------------------- 1 | # Postgres Transaction Example 2 | 3 | A simple example demonstrating how to obtain and roll back a transaction with postgres. 4 | 5 | ## Usage 6 | 7 | Declare the database URL. This example does not include any reading or writing of data. 8 | 9 | ``` 10 | export DATABASE_URL="postgres://postgres@localhost/postgres" 11 | ``` 12 | 13 | Run. 14 | 15 | ``` 16 | cargo run 17 | ``` 18 | 19 | -------------------------------------------------------------------------------- /examples/postgres/todos/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-postgres-todos" 3 | version = "0.1.0" 4 | edition = "2018" 5 | workspace = "../../../" 6 | 7 | [dependencies] 8 | anyhow = "1.0" 9 | sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] } 10 | clap = { version = "4", features = ["derive"] } 11 | tokio = { version = "1.20.0", features = ["rt", "macros"]} 12 | dotenvy = "0.15.0" 13 | -------------------------------------------------------------------------------- /tests/ui/sqlite/expression-column-type.stderr: -------------------------------------------------------------------------------- 1 | error: database couldn't tell us the type of column #1 ("id"); this can happen for columns that are the result of an expression 2 | --> $DIR/expression-column-type.rs:2:13 3 | | 4 | 2 | let _ = sqlx::query!("select 1 as id"); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /examples/postgres/multi-tenant/accounts/sqlx.toml: -------------------------------------------------------------------------------- 1 | [migrate] 2 | create-schemas = ["accounts"] 3 | table-name = "accounts._sqlx_migrations" 4 | 5 | [macros.table-overrides.'accounts.account'] 6 | 'account_id' = "crate::AccountId" 7 | 'password_hash' = "sqlx::types::Text" 8 | 9 | [macros.table-overrides.'accounts.session'] 10 | 'session_token' = "crate::SessionToken" 11 | 'account_id' = "crate::AccountId" 12 | -------------------------------------------------------------------------------- /examples/postgres/mockable-todos/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | database: 5 | image: "postgres" 6 | ports: 7 | - "5432:5432" 8 | restart: always 9 | environment: 10 | - POSTGRES_USER=postgres 11 | - POSTGRES_PASSWORD=password 12 | - POSTGRES_DB=todos 13 | volumes: 14 | - database_data:/var/lib/postgresql/data 15 | volumes: 16 | database_data: 17 | driver: local 18 | -------------------------------------------------------------------------------- /sqlx-postgres/src/message/parse_complete.rs: -------------------------------------------------------------------------------- 1 | use crate::message::{BackendMessage, BackendMessageFormat}; 2 | use sqlx_core::bytes::Bytes; 3 | use sqlx_core::Error; 4 | 5 | pub struct ParseComplete; 6 | 7 | impl BackendMessage for ParseComplete { 8 | const FORMAT: BackendMessageFormat = BackendMessageFormat::ParseComplete; 9 | 10 | fn decode_body(_bytes: Bytes) -> Result { 11 | Ok(ParseComplete) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/connect/mod.rs: -------------------------------------------------------------------------------- 1 | //! Connection Phase 2 | //! 3 | //! 4 | 5 | mod auth_switch; 6 | mod handshake; 7 | mod handshake_response; 8 | mod ssl_request; 9 | 10 | pub(crate) use auth_switch::{AuthSwitchRequest, AuthSwitchResponse}; 11 | pub(crate) use handshake::Handshake; 12 | pub(crate) use handshake_response::HandshakeResponse; 13 | pub(crate) use ssl_request::SslRequest; 14 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/text/ping.rs: -------------------------------------------------------------------------------- 1 | use crate::io::ProtocolEncode; 2 | use crate::protocol::Capabilities; 3 | 4 | // https://dev.mysql.com/doc/internals/en/com-ping.html 5 | 6 | #[derive(Debug)] 7 | pub(crate) struct Ping; 8 | 9 | impl ProtocolEncode<'_, Capabilities> for Ping { 10 | fn encode_with(&self, buf: &mut Vec, _: Capabilities) -> Result<(), crate::Error> { 11 | buf.push(0x0e); // COM_PING 12 | Ok(()) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/text/quit.rs: -------------------------------------------------------------------------------- 1 | use crate::io::ProtocolEncode; 2 | use crate::protocol::Capabilities; 3 | 4 | // https://dev.mysql.com/doc/internals/en/com-quit.html 5 | 6 | #[derive(Debug)] 7 | pub(crate) struct Quit; 8 | 9 | impl ProtocolEncode<'_, Capabilities> for Quit { 10 | fn encode_with(&self, buf: &mut Vec, _: Capabilities) -> Result<(), crate::Error> { 11 | buf.push(0x01); // COM_QUIT 12 | Ok(()) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/postgres/files/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-postgres-files" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0" 10 | sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] } 11 | tokio = { version = "1.20.0", features = ["rt", "macros"]} 12 | dotenvy = "0.15.0" 13 | -------------------------------------------------------------------------------- /sqlx-cli/tests/ignored-chars/sqlx.toml: -------------------------------------------------------------------------------- 1 | [migrate] 2 | # Ignore common whitespace characters (beware syntactically significant whitespace!) 3 | # Space, tab, CR, LF, zero-width non-breaking space (U+FEFF) 4 | # 5 | # U+FEFF is added by some editors as a magic number at the beginning of a text file indicating it is UTF-8 encoded, 6 | # where it is known as a byte-order mark (BOM): https://en.wikipedia.org/wiki/Byte_order_mark 7 | ignored-chars = [" ", "\t", "\r", "\n", "\uFEFF"] 8 | -------------------------------------------------------------------------------- /src/any/install_drivers_note.md: -------------------------------------------------------------------------------- 1 | 2 | The underlying database drivers are chosen at runtime from the list set via 3 | [`install_drivers`][crate::any::install_drivers]. Any use of [`AnyConnection`] or [`AnyPool`] 4 | without this will panic. 5 | 6 | It is recommended to use [`install_default_drivers`][crate::any::install_default_drivers] to activate all currently compiled-in drivers. 7 | 8 | [`AnyConnection`]: sqlx_core::any::AnyConnection 9 | [`AnyPool`]: sqlx_core::any::AnyPool 10 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | [[disallowed-methods]] 2 | path = "core::cmp::Ord::min" 3 | reason = ''' 4 | too easy to misread `x.min(y)` as "let the minimum value of `x` be `y`" when it actually means the exact opposite; 5 | use `std::cmp::min` instead. 6 | ''' 7 | 8 | [[disallowed-methods]] 9 | path = "core::cmp::Ord::max" 10 | reason = ''' 11 | too easy to misread `x.max(y)` as "let the maximum value of `x` be `y`" when it actually means the exact opposite; 12 | use `std::cmp::max` instead. 13 | ''' 14 | -------------------------------------------------------------------------------- /examples/postgres/axum-social-with-tests/README.md: -------------------------------------------------------------------------------- 1 | This example demonstrates how to write integration tests for an API build with [Axum] and SQLx using `#[sqlx::test]`. 2 | 3 | See also: https://github.com/tokio-rs/axum/blob/main/examples/testing 4 | 5 | # Warning 6 | 7 | For the sake of brevity, this project omits numerous critical security precautions. You can use it as a starting point, 8 | but deploy to production at your own risk! 9 | 10 | [Axum]: https://github.com/tokio-rs/axum 11 | -------------------------------------------------------------------------------- /examples/postgres/axum-social-with-tests/tests/fixtures/posts.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO public.post (post_id, user_id, content, created_at) 2 | VALUES 3 | -- from: alice 4 | ('d9ca2672-24c5-4442-b32f-cd717adffbaa', '51b374f1-93ae-4c5c-89dd-611bda8412ce', 5 | 'This new computer is blazing fast!', '2022-07-29 01:36:24.679082'), 6 | -- from: bob 7 | ('7e3d4d16-a35e-46ba-8223-b4f1debbfbfe', 'c994b839-84f4-4509-ad49-59119133d6f5', '@alice is a haxxor', 8 | '2022-07-29 01:54:45.823523'); 9 | -------------------------------------------------------------------------------- /tests/mssql/setup.sql: -------------------------------------------------------------------------------- 1 | IF DB_ID('sqlx') IS NULL 2 | BEGIN 3 | CREATE DATABASE sqlx; 4 | END; 5 | GO 6 | 7 | USE sqlx; 8 | GO 9 | 10 | IF OBJECT_ID('tweet') IS NULL 11 | BEGIN 12 | CREATE TABLE tweet 13 | ( 14 | id BIGINT NOT NULL PRIMARY KEY, 15 | text NVARCHAR(4000) NOT NULL, 16 | is_sent TINYINT NOT NULL DEFAULT 1, 17 | owner_id BIGINT 18 | ); 19 | END; 20 | GO 21 | -------------------------------------------------------------------------------- /tests/mysql/fixtures/comments.sql: -------------------------------------------------------------------------------- 1 | insert into comment(comment_id, post_id, user_id, content, created_at) 2 | values (1, 3 | 1, 4 | 2, 5 | 'lol bet ur still bad, 1v1 me', 6 | timestamp(now(), '-0:50:00')), 7 | (2, 8 | 1, 9 | 1, 10 | 'you''re on!', 11 | timestamp(now(), '-0:45:00')), 12 | (3, 13 | 2, 14 | 1, 15 | 'lol you''re just mad you lost :P', 16 | timestamp(now(), '-0:15:00')); 17 | -------------------------------------------------------------------------------- /examples/postgres/json/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-postgres-json" 3 | version = "0.1.0" 4 | edition = "2021" 5 | workspace = "../../../" 6 | 7 | [dependencies] 8 | anyhow = "1.0" 9 | dotenvy = "0.15.0" 10 | serde = { version = "1", features = ["derive"] } 11 | serde_json = "1" 12 | sqlx = { path = "../../../", features = [ "runtime-tokio", "postgres", "json" ] } 13 | clap = { version = "4", features = ["derive"] } 14 | tokio = { version = "1.20.0", features = ["rt", "macros"]} 15 | -------------------------------------------------------------------------------- /examples/postgres/mockable-todos/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-postgres-mockable-todos" 3 | version = "0.1.0" 4 | edition = "2021" 5 | workspace = "../../../" 6 | 7 | [dependencies] 8 | anyhow = "1.0" 9 | sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] } 10 | clap = { version = "4", features = ["derive"] } 11 | tokio = { version = "1.20.0", features = ["rt", "macros"]} 12 | dotenvy = "0.15.0" 13 | async-trait = "0.1.41" 14 | mockall = "0.11" 15 | -------------------------------------------------------------------------------- /tests/sqlite/fixtures/comments.sql: -------------------------------------------------------------------------------- 1 | insert into comment(comment_id, post_id, user_id, content, created_at) 2 | values (1, 3 | 1, 4 | 2, 5 | 'lol bet ur still bad, 1v1 me', 6 | datetime('now', '-50 minutes')), 7 | (2, 8 | 1, 9 | 1, 10 | 'you''re on!', 11 | datetime('now', '-45 minutes')), 12 | (3, 13 | 2, 14 | 1, 15 | 'lol you''re just mad you lost :P', 16 | datetime('now', '-15 minutes')); 17 | -------------------------------------------------------------------------------- /sqlx-core/src/any/error.rs: -------------------------------------------------------------------------------- 1 | use std::any::type_name; 2 | 3 | use crate::any::type_info::AnyTypeInfo; 4 | use crate::any::Any; 5 | use crate::error::BoxDynError; 6 | use crate::type_info::TypeInfo; 7 | use crate::types::Type; 8 | 9 | pub(super) fn mismatched_types>(ty: &AnyTypeInfo) -> BoxDynError { 10 | format!( 11 | "mismatched types; Rust type `{}` is not compatible with SQL type `{}`", 12 | type_name::(), 13 | ty.name() 14 | ) 15 | .into() 16 | } 17 | -------------------------------------------------------------------------------- /tests/ui/postgres/wrong_param_type.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _query = sqlx::query!("select $1::text", 0i32); 3 | 4 | let _query = sqlx::query!("select $1::text", &0i32); 5 | 6 | let _query = sqlx::query!("select $1::text", Some(0i32)); 7 | 8 | let arg = 0i32; 9 | let _query = sqlx::query!("select $1::text", arg); 10 | 11 | let arg = Some(0i32); 12 | let _query = sqlx::query!("select $1::text", arg); 13 | let _query = sqlx::query!("select $1::text", arg.as_ref()); 14 | } 15 | -------------------------------------------------------------------------------- /examples/postgres/multi-tenant/src/migrations/02_purchase.sql: -------------------------------------------------------------------------------- 1 | create table purchase 2 | ( 3 | purchase_id uuid primary key default gen_random_uuid(), 4 | account_id uuid not null references accounts.account (account_id), 5 | payment_id uuid not null references payments.payment (payment_id), 6 | amount numeric not null, 7 | created_at timestamptz not null default now(), 8 | updated_at timestamptz 9 | ); 10 | 11 | select trigger_updated_at('purchase'); 12 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/text/query.rs: -------------------------------------------------------------------------------- 1 | use crate::io::ProtocolEncode; 2 | use crate::protocol::Capabilities; 3 | 4 | // https://dev.mysql.com/doc/internals/en/com-query.html 5 | 6 | #[derive(Debug)] 7 | pub(crate) struct Query<'q>(pub(crate) &'q str); 8 | 9 | impl ProtocolEncode<'_, Capabilities> for Query<'_> { 10 | fn encode_with(&self, buf: &mut Vec, _: Capabilities) -> Result<(), crate::Error> { 11 | buf.push(0x03); // COM_QUERY 12 | buf.extend(self.0.as_bytes()); 13 | Ok(()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/postgres/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG VERSION 2 | FROM postgres:${VERSION}-alpine 3 | 4 | # Copy SSL certificate (and key) 5 | COPY certs/server.crt /var/lib/postgresql/server.crt 6 | COPY certs/ca.crt /var/lib/postgresql/ca.crt 7 | COPY certs/keys/server.key /var/lib/postgresql/server.key 8 | COPY postgres/pg_hba.conf /var/lib/postgresql/pg_hba.conf 9 | 10 | # Fix permissions 11 | RUN chown 70:70 /var/lib/postgresql/server.crt /var/lib/postgresql/server.key 12 | RUN chmod 0600 /var/lib/postgresql/server.crt /var/lib/postgresql/server.key 13 | -------------------------------------------------------------------------------- /tests/ui/postgres/gated/chrono.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _ = sqlx::query!("select now()::date"); 3 | 4 | let _ = sqlx::query!("select now()::time"); 5 | 6 | let _ = sqlx::query!("select now()::timestamp"); 7 | 8 | let _ = sqlx::query!("select now()::timestamptz"); 9 | 10 | let _ = sqlx::query!("select $1::date", ()); 11 | 12 | let _ = sqlx::query!("select $1::time", ()); 13 | 14 | let _ = sqlx::query!("select $1::timestamp", ()); 15 | 16 | let _ = sqlx::query!("select $1::timestamptz", ()); 17 | } 18 | -------------------------------------------------------------------------------- /sqlx-cli/src/bin/sqlx.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use console::style; 3 | use sqlx_cli::Opt; 4 | 5 | #[tokio::main] 6 | async fn main() { 7 | // Checks for `--no-dotenv` before parsing. 8 | sqlx_cli::maybe_apply_dotenv(); 9 | 10 | sqlx::any::install_default_drivers(); 11 | 12 | let opt = Opt::parse(); 13 | 14 | // no special handling here 15 | if let Err(error) = sqlx_cli::run(opt).await { 16 | println!("{} {}", style("error:").bold().red(), error); 17 | std::process::exit(1); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/fixtures/postgres/posts.sql: -------------------------------------------------------------------------------- 1 | insert into post(post_id, user_id, content, created_at) 2 | values 3 | ( 4 | '252c1d98-a9b0-4f18-8298-e59058bdfe16', 5 | '6592b7c0-b531-4613-ace5-94246b7ce0c3', 6 | 'This new computer is lightning-fast!', 7 | now() + '1 hour ago'::interval 8 | ), 9 | ( 10 | '844265f7-2472-4689-9a2e-b21f40dbf401', 11 | '6592b7c0-b531-4613-ace5-94246b7ce0c3', 12 | '@alice is a haxxor :(', 13 | now() + '30 minutes ago'::interval 14 | ); 15 | -------------------------------------------------------------------------------- /tests/mysql/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG IMAGE 2 | FROM ${IMAGE} 3 | 4 | # Copy SSL certificate (and key) 5 | COPY certs/server.crt /etc/mysql/ssl/server.crt 6 | COPY certs/ca.crt /etc/mysql/ssl/ca.crt 7 | COPY certs/keys/server.key /etc/mysql/ssl/server.key 8 | COPY mysql/my.cnf /etc/mysql/my.cnf 9 | 10 | # Fix permissions 11 | RUN chown mysql:mysql /etc/mysql/ssl/server.crt /etc/mysql/ssl/server.key 12 | RUN chmod 0600 /etc/mysql/ssl/server.crt /etc/mysql/ssl/server.key 13 | 14 | # Create dir for secure-file-priv 15 | RUN mkdir -p /var/lib/mysql-files 16 | -------------------------------------------------------------------------------- /tests/postgres/fixtures/posts.sql: -------------------------------------------------------------------------------- 1 | insert into post(post_id, user_id, content, created_at) 2 | values 3 | ( 4 | '252c1d98-a9b0-4f18-8298-e59058bdfe16', 5 | '6592b7c0-b531-4613-ace5-94246b7ce0c3', 6 | 'This new computer is lightning-fast!', 7 | now() + '1 hour ago'::interval 8 | ), 9 | ( 10 | '844265f7-2472-4689-9a2e-b21f40dbf401', 11 | '6592b7c0-b531-4613-ace5-94246b7ce0c3', 12 | '@alice is a haxxor :(', 13 | now() + '30 minutes ago'::interval 14 | ); 15 | -------------------------------------------------------------------------------- /examples/sqlite/extension/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-sqlite-extension" 3 | version = "0.1.0" 4 | license.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | keywords.workspace = true 8 | categories.workspace = true 9 | authors.workspace = true 10 | 11 | [dependencies] 12 | sqlx = { path = "../../../", features = [ "sqlite", "runtime-tokio", "tls-native-tls", "sqlx-toml"] } 13 | tokio = { version = "1.20.0", features = ["rt", "macros"]} 14 | anyhow = "1.0" 15 | 16 | [lints] 17 | workspace = true 18 | -------------------------------------------------------------------------------- /examples/sqlite/extension/download-extension.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This grabs a pre-compiled version of the extension used in this 4 | # example, and stores it in a temporary directory. That's a bit 5 | # unusual. Normally, any extensions you need will be installed into a 6 | # directory on the library search path, either by using the system 7 | # package manager or by compiling and installing it yourself. 8 | 9 | mkdir /tmp/sqlite3-lib && wget -O /tmp/sqlite3-lib/ipaddr.so https://github.com/nalgeon/sqlean/releases/download/0.15.2/ipaddr.so 10 | -------------------------------------------------------------------------------- /sqlx-core/src/migrate/mod.rs: -------------------------------------------------------------------------------- 1 | mod error; 2 | #[allow(clippy::module_inception)] 3 | mod migrate; 4 | mod migration; 5 | mod migration_type; 6 | mod migrator; 7 | mod source; 8 | 9 | pub use error::MigrateError; 10 | pub use migrate::{Migrate, MigrateDatabase}; 11 | pub use migration::{AppliedMigration, Migration}; 12 | pub use migration_type::MigrationType; 13 | pub use migrator::Migrator; 14 | pub use source::{MigrationSource, ResolveConfig, ResolveWith}; 15 | 16 | #[doc(hidden)] 17 | pub use source::{resolve_blocking, resolve_blocking_with_config}; 18 | -------------------------------------------------------------------------------- /examples/postgres/preferred-crates/uses-time/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-postgres-preferred-crates-uses-time" 3 | version.workspace = true 4 | license.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | keywords.workspace = true 8 | categories.workspace = true 9 | authors.workspace = true 10 | 11 | [dependencies] 12 | serde = "1" 13 | time = "0.3" 14 | uuid = "1" 15 | 16 | [dependencies.sqlx] 17 | workspace = true 18 | features = ["runtime-tokio", "postgres", "time", "json", "uuid"] 19 | 20 | [lints] 21 | workspace = true 22 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/statement/prepare.rs: -------------------------------------------------------------------------------- 1 | use crate::io::ProtocolEncode; 2 | use crate::protocol::Capabilities; 3 | 4 | // https://dev.mysql.com/doc/internals/en/com-stmt-prepare.html#packet-COM_STMT_PREPARE 5 | 6 | pub struct Prepare<'a> { 7 | pub query: &'a str, 8 | } 9 | 10 | impl ProtocolEncode<'_, Capabilities> for Prepare<'_> { 11 | fn encode_with(&self, buf: &mut Vec, _: Capabilities) -> Result<(), crate::Error> { 12 | buf.push(0x16); // COM_STMT_PREPARE 13 | buf.extend(self.query.as_bytes()); 14 | Ok(()) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/statement/stmt_close.rs: -------------------------------------------------------------------------------- 1 | use crate::io::ProtocolEncode; 2 | use crate::protocol::Capabilities; 3 | 4 | // https://dev.mysql.com/doc/internals/en/com-stmt-close.html 5 | 6 | #[derive(Debug)] 7 | pub struct StmtClose { 8 | pub statement: u32, 9 | } 10 | 11 | impl ProtocolEncode<'_, Capabilities> for StmtClose { 12 | fn encode_with(&self, buf: &mut Vec, _: Capabilities) -> Result<(), crate::Error> { 13 | buf.push(0x19); // COM_STMT_CLOSE 14 | buf.extend(&self.statement.to_le_bytes()); 15 | Ok(()) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/postgres/listen/README.md: -------------------------------------------------------------------------------- 1 | Postgres LISTEN/NOTIFY 2 | ====================== 3 | 4 | ## Usage 5 | 6 | Declare the database URL. This example does not include any reading or writing of data. 7 | 8 | ``` 9 | export DATABASE_URL="postgres://postgres@localhost/postgres" 10 | ``` 11 | 12 | Run. 13 | 14 | ``` 15 | cargo run 16 | ``` 17 | 18 | The example program should connect to the database, and create a LISTEN loop on a predefined set of channels. A NOTIFY task will be spawned which will connect to the same database and will emit notifications on a 5 second interval. 19 | -------------------------------------------------------------------------------- /sqlx-postgres/src/message/sync.rs: -------------------------------------------------------------------------------- 1 | use crate::message::{FrontendMessage, FrontendMessageFormat}; 2 | use sqlx_core::Error; 3 | use std::num::Saturating; 4 | 5 | #[derive(Debug)] 6 | pub struct Sync; 7 | 8 | impl FrontendMessage for Sync { 9 | const FORMAT: FrontendMessageFormat = FrontendMessageFormat::Sync; 10 | 11 | #[inline(always)] 12 | fn body_size_hint(&self) -> Saturating { 13 | Saturating(0) 14 | } 15 | 16 | #[inline(always)] 17 | fn encode_body(&self, _buf: &mut Vec) -> Result<(), Error> { 18 | Ok(()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sqlx-postgres/src/message/terminate.rs: -------------------------------------------------------------------------------- 1 | use crate::message::{FrontendMessage, FrontendMessageFormat}; 2 | use sqlx_core::Error; 3 | use std::num::Saturating; 4 | 5 | pub struct Terminate; 6 | 7 | impl FrontendMessage for Terminate { 8 | const FORMAT: FrontendMessageFormat = FrontendMessageFormat::Terminate; 9 | 10 | #[inline(always)] 11 | fn body_size_hint(&self) -> Saturating { 12 | Saturating(0) 13 | } 14 | 15 | #[inline(always)] 16 | fn encode_body(&self, _buf: &mut Vec) -> Result<(), Error> { 17 | Ok(()) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/postgres/multi-database/payments/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-postgres-multi-database-payments" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | 8 | sqlx = { workspace = true, features = ["postgres", "time", "uuid", "rust_decimal", "sqlx-toml"] } 9 | 10 | rust_decimal = "1.36.0" 11 | 12 | time = "0.3.37" 13 | uuid = "1.12.1" 14 | 15 | [dependencies.accounts] 16 | path = "../accounts" 17 | package = "sqlx-example-postgres-multi-database-accounts" 18 | 19 | [dev-dependencies] 20 | sqlx = { workspace = true, features = ["runtime-tokio"] } 21 | -------------------------------------------------------------------------------- /tests/ui/postgres/deprecated_rename.stderr: -------------------------------------------------------------------------------- 1 | error: trybuild test needs to fail for stderr checking 2 | --> $DIR/deprecated_rename.rs:10:5 3 | | 4 | 10 | compile_error!("trybuild test needs to fail for stderr checking"); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | 7 | warning: use of deprecated function `sqlx::_rename`: `#[sqlx(rename = "...")]` is now `#[sqlx(type_name = "...")` 8 | --> $DIR/deprecated_rename.rs:2:8 9 | | 10 | 2 | #[sqlx(rename = "foo")] 11 | | ^^^^^^ 12 | | 13 | = note: `#[warn(deprecated)]` on by default 14 | -------------------------------------------------------------------------------- /sqlx-core/src/io/encode.rs: -------------------------------------------------------------------------------- 1 | pub trait ProtocolEncode<'en, Context = ()> { 2 | fn encode(&self, buf: &mut Vec) -> Result<(), crate::Error> 3 | where 4 | Self: ProtocolEncode<'en, ()>, 5 | { 6 | self.encode_with(buf, ()) 7 | } 8 | 9 | fn encode_with(&self, buf: &mut Vec, context: Context) -> Result<(), crate::Error>; 10 | } 11 | 12 | impl ProtocolEncode<'_, C> for &'_ [u8] { 13 | fn encode_with(&self, buf: &mut Vec, _context: C) -> Result<(), crate::Error> { 14 | buf.extend_from_slice(self); 15 | Ok(()) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/postgres/axum-social-with-tests/tests/fixtures/users.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO public."user" (user_id, username, password_hash) 2 | VALUES 3 | -- username: "alice"; password: "rustacean since 2015" 4 | ('51b374f1-93ae-4c5c-89dd-611bda8412ce', 'alice', 5 | '$argon2id$v=19$m=4096,t=3,p=1$3v3ats/tYTXAYs3q9RycDw$ZltwjS3oQwPuNmL9f6DNb+sH5N81dTVZhVNbUQzmmVU'), 6 | -- username: "bob"; password: "pro gamer 1990" 7 | ('c994b839-84f4-4509-ad49-59119133d6f5', 'bob', 8 | '$argon2id$v=19$m=4096,t=3,p=1$1zbkRinUH9WHzkyu8C1Vlg$70pu5Cca/s3d0nh5BYQGkN7+s9cqlNxTE7rFZaUaP4c'); 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/postgres/preferred-crates/uses-rust-decimal/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-postgres-preferred-crates-uses-rust-decimal" 3 | version.workspace = true 4 | license.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | keywords.workspace = true 8 | categories.workspace = true 9 | authors.workspace = true 10 | 11 | [dependencies] 12 | chrono = "0.4" 13 | rust_decimal = "1" 14 | uuid = "1" 15 | 16 | [dependencies.sqlx] 17 | workspace = true 18 | features = ["runtime-tokio", "postgres", "rust_decimal", "chrono", "uuid"] 19 | 20 | [lints] 21 | workspace = true 22 | -------------------------------------------------------------------------------- /examples/postgres/chat/README.md: -------------------------------------------------------------------------------- 1 | # Chat Example 2 | 3 | Note: this example has an interactive TUI which is not trivial to test automatically, 4 | so our CI currently only checks whether or not it compiles. 5 | 6 | ## Description 7 | 8 | This example demonstrates how to use PostgreSQL channels to create a very simple chat application. 9 | 10 | ## Setup 11 | 12 | 1. Declare the database URL 13 | 14 | ``` 15 | export DATABASE_URL="postgres://postgres:password@localhost/files" 16 | ``` 17 | 18 | ## Usage 19 | 20 | Run the project 21 | 22 | ``` 23 | cargo run -p sqlx-examples-postgres-chat 24 | ``` 25 | -------------------------------------------------------------------------------- /examples/sqlite/todos/README.md: -------------------------------------------------------------------------------- 1 | # TODOs Example 2 | 3 | ## Setup 4 | 5 | 1. Declare the database URL 6 | 7 | ``` 8 | export DATABASE_URL="sqlite:todos.db" 9 | ``` 10 | 11 | 2. Create the database. 12 | 13 | ``` 14 | $ sqlx db create 15 | ``` 16 | 17 | 3. Run sql migrations 18 | 19 | ``` 20 | $ sqlx migrate run 21 | ``` 22 | 23 | ## Usage 24 | 25 | Add a todo 26 | 27 | ``` 28 | cargo run -- add "todo description" 29 | ``` 30 | 31 | Complete a todo. 32 | 33 | ``` 34 | cargo run -- done 35 | ``` 36 | 37 | List all todos 38 | 39 | ``` 40 | cargo run 41 | ``` 42 | -------------------------------------------------------------------------------- /tests/mssql/mssql-2017.dockerfile: -------------------------------------------------------------------------------- 1 | # vim: set ft=dockerfile: 2 | ARG VERSION 3 | FROM mcr.microsoft.com/mssql/server:${VERSION} 4 | 5 | # Create a config directory 6 | RUN mkdir -p /usr/config 7 | WORKDIR /usr/config 8 | 9 | # Bundle config source 10 | COPY mssql/entrypoint.sh /usr/config/entrypoint.sh 11 | COPY mssql/configure-db.sh /usr/config/configure-db.sh 12 | COPY mssql/setup.sql /usr/config/setup.sql 13 | 14 | # Grant permissions for to our scripts to be executable 15 | USER root 16 | RUN chmod +x /usr/config/entrypoint.sh 17 | RUN chmod +x /usr/config/configure-db.sh 18 | 19 | ENTRYPOINT ["/usr/config/entrypoint.sh"] 20 | -------------------------------------------------------------------------------- /examples/mysql/todos/README.md: -------------------------------------------------------------------------------- 1 | # TODOs Example 2 | 3 | ## Setup 4 | 5 | 1. Declare the database URL 6 | 7 | ``` 8 | export DATABASE_URL="mysql://root:password@localhost/todos" 9 | ``` 10 | 11 | 2. Create the database. 12 | 13 | ``` 14 | $ sqlx db create 15 | ``` 16 | 17 | 3. Run sql migrations 18 | 19 | ``` 20 | $ sqlx migrate run 21 | ``` 22 | 23 | ## Usage 24 | 25 | Add a todo 26 | 27 | ``` 28 | cargo run -- add "todo description" 29 | ``` 30 | 31 | Complete a todo. 32 | 33 | ``` 34 | cargo run -- done 35 | ``` 36 | 37 | List all todos 38 | 39 | ``` 40 | cargo run 41 | ``` 42 | -------------------------------------------------------------------------------- /examples/postgres/files/queries/insert_seed_data.sql: -------------------------------------------------------------------------------- 1 | -- seed some data to work with 2 | WITH inserted_users_cte AS ( 3 | INSERT INTO users (username) 4 | VALUES ('user1'), 5 | ('user2') 6 | RETURNING id as "user_id" 7 | ) 8 | INSERT INTO posts (title, body, user_id) 9 | VALUES ('user1 post1 title', 'user1 post1 body', (SELECT user_id FROM inserted_users_cte FETCH FIRST ROW ONLY)), 10 | ('user1 post2 title', 'user1 post2 body', (SELECT user_id FROM inserted_users_cte FETCH FIRST ROW ONLY)), 11 | ('user2 post1 title', 'user2 post2 body', (SELECT user_id FROM inserted_users_cte OFFSET 1 LIMIT 1)); -------------------------------------------------------------------------------- /examples/postgres/multi-tenant/payments/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-postgres-multi-tenant-payments" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | 8 | rust_decimal = "1.36.0" 9 | 10 | time = "0.3.37" 11 | uuid = "1.12.1" 12 | 13 | [dependencies.sqlx] 14 | # version = "0.9.0" 15 | workspace = true 16 | features = ["postgres", "time", "uuid", "rust_decimal", "sqlx-toml", "migrate"] 17 | 18 | [dependencies.accounts] 19 | path = "../accounts" 20 | package = "sqlx-example-postgres-multi-tenant-accounts" 21 | 22 | [dev-dependencies] 23 | sqlx = { workspace = true, features = ["runtime-tokio"] } 24 | -------------------------------------------------------------------------------- /examples/postgres/todos/README.md: -------------------------------------------------------------------------------- 1 | # TODOs Example 2 | 3 | ## Setup 4 | 5 | 1. Declare the database URL 6 | 7 | ``` 8 | export DATABASE_URL="postgres://postgres:password@localhost/todos" 9 | ``` 10 | 11 | 2. Create the database. 12 | 13 | ``` 14 | $ sqlx db create 15 | ``` 16 | 17 | 3. Run sql migrations 18 | 19 | ``` 20 | $ sqlx migrate run 21 | ``` 22 | 23 | ## Usage 24 | 25 | Add a todo 26 | 27 | ``` 28 | cargo run -- add "todo description" 29 | ``` 30 | 31 | Complete a todo. 32 | 33 | ``` 34 | cargo run -- done 35 | ``` 36 | 37 | List all todos 38 | 39 | ``` 40 | cargo run 41 | ``` 42 | -------------------------------------------------------------------------------- /sqlx-postgres/src/types/void.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::Decode; 2 | use crate::error::BoxDynError; 3 | use crate::types::Type; 4 | use crate::{PgTypeInfo, PgValueRef, Postgres}; 5 | 6 | impl Type for () { 7 | fn type_info() -> PgTypeInfo { 8 | PgTypeInfo::VOID 9 | } 10 | 11 | fn compatible(ty: &PgTypeInfo) -> bool { 12 | // RECORD is here so we can support the empty tuple 13 | *ty == PgTypeInfo::VOID || *ty == PgTypeInfo::RECORD 14 | } 15 | } 16 | 17 | impl<'r> Decode<'r, Postgres> for () { 18 | fn decode(_value: PgValueRef<'r>) -> Result { 19 | Ok(()) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/mssql/macros.rs: -------------------------------------------------------------------------------- 1 | use sqlx::Mssql; 2 | use sqlx_test::new; 3 | 4 | #[sqlx_macros::test] 5 | async fn test_query_simple() -> anyhow::Result<()> { 6 | let mut conn = new::().await?; 7 | 8 | let account = 9 | sqlx::query!("select * from (select (1) as id, 'Herp Derpinson' as name, cast(null as char) as email, CAST(1 as bit) as deleted) accounts") 10 | .fetch_one(&mut conn) 11 | .await?; 12 | 13 | assert_eq!(account.id, 1); 14 | assert_eq!(account.name, "Herp Derpinson"); 15 | assert_eq!(account.email, None); 16 | assert_eq!(account.deleted, Some(true)); 17 | 18 | Ok(()) 19 | } 20 | -------------------------------------------------------------------------------- /sqlx-core/src/io/mod.rs: -------------------------------------------------------------------------------- 1 | mod buf; 2 | mod buf_mut; 3 | // mod buf_stream; 4 | mod decode; 5 | mod encode; 6 | mod read_buf; 7 | // mod write_and_flush; 8 | 9 | pub use buf::BufExt; 10 | pub use buf_mut::BufMutExt; 11 | //pub use buf_stream::BufStream; 12 | pub use decode::ProtocolDecode; 13 | pub use encode::ProtocolEncode; 14 | pub use read_buf::ReadBuf; 15 | 16 | #[cfg(not(feature = "_rt-tokio"))] 17 | pub use futures_io::AsyncRead; 18 | 19 | #[cfg(feature = "_rt-tokio")] 20 | pub use tokio::io::AsyncRead; 21 | 22 | #[cfg(not(feature = "_rt-tokio"))] 23 | pub use futures_util::io::AsyncReadExt; 24 | 25 | #[cfg(feature = "_rt-tokio")] 26 | pub use tokio::io::AsyncReadExt; 27 | -------------------------------------------------------------------------------- /tests/certs/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBnjCCAVCgAwIBAgIJALbHH0sRwKGPMAUGAytlcDBfMQswCQYDVQQGEwJ1czET 3 | MBEGA1UECAwKY2FsaWZvcm5pYTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ 4 | dHkgTHRkMRgwFgYDVQQDDA9BdXN0aW4gQm9uYW5kZXIwHhcNMjUwNzAxMDUyMTU2 5 | WhcNMzUwNjI5MDUyMTU2WjBGMQswCQYDVQQGEwJ1czETMBEGA1UECAwKY2FsaWZv 6 | cm5pYTEQMA4GA1UECgwHU1FMeC5yczEQMA4GA1UEAwwHc3FseC5yczAqMAUGAytl 7 | cAMhAA33S2qsqpZssUcYrpleMXDj5/mhb56HPaO3CIIgY5c8o0IwQDAdBgNVHQ4E 8 | FgQUPUpn95GHFuMe7+2pG5rbmJS55/wwHwYDVR0jBBgwFoAUCw2pVpGKz2xkIjbV 9 | HYh0LnzdkW4wBQYDK2VwA0EAExEOza9IrSchoQs1NwPxfCdfXMHiXpsgMThDuig+ 10 | 9hauW+b1KlBR3ZeW8AOIwazMhdstBFOhumaWPQ/wZNUkCg== 11 | -----END CERTIFICATE----- 12 | -------------------------------------------------------------------------------- /examples/postgres/axum-social-with-tests/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use sqlx::postgres::PgPoolOptions; 3 | 4 | #[tokio::main] 5 | async fn main() -> anyhow::Result<()> { 6 | let database_url = dotenvy::var("DATABASE_URL") 7 | // The error from `var()` doesn't mention the environment variable. 8 | .context("DATABASE_URL must be set")?; 9 | 10 | let db = PgPoolOptions::new() 11 | .max_connections(20) 12 | .connect(&database_url) 13 | .await 14 | .context("failed to connect to DATABASE_URL")?; 15 | 16 | sqlx::migrate!().run(&db).await?; 17 | 18 | sqlx_example_postgres_axum_social::http::serve(db).await 19 | } 20 | -------------------------------------------------------------------------------- /sqlx-core/src/rt/rt_async_io/timeout.rs: -------------------------------------------------------------------------------- 1 | use std::{future::Future, pin::pin, time::Duration}; 2 | 3 | use futures_util::future::{select, Either}; 4 | 5 | use crate::rt::TimeoutError; 6 | 7 | pub async fn sleep(duration: Duration) { 8 | timeout_future(duration).await; 9 | } 10 | 11 | pub async fn timeout(duration: Duration, future: F) -> Result { 12 | match select(pin!(future), timeout_future(duration)).await { 13 | Either::Left((result, _)) => Ok(result), 14 | Either::Right(_) => Err(TimeoutError), 15 | } 16 | } 17 | 18 | fn timeout_future(duration: Duration) -> impl Future { 19 | async_io::Timer::after(duration) 20 | } 21 | -------------------------------------------------------------------------------- /tests/certs/client.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBqjCCAVygAwIBAgIURmjYOsv10CRGeCFUY35KOsBmD8IwBQYDK2VwMF8xCzAJ 3 | BgNVBAYTAnVzMRMwEQYDVQQIDApjYWxpZm9ybmlhMSEwHwYDVQQKDBhJbnRlcm5l 4 | dCBXaWRnaXRzIFB0eSBMdGQxGDAWBgNVBAMMD0F1c3RpbiBCb25hbmRlcjAeFw0y 5 | NTA3MDEwNjQ2NDdaFw0zNTA2MjkwNjQ2NDdaMEcxCzAJBgNVBAYTAnVzMRMwEQYD 6 | VQQIDApjYWxpZm9ybmlhMRAwDgYDVQQKDAdTUUx4LnJzMREwDwYDVQQDDAhwb3N0 7 | Z3JlczAqMAUGAytlcAMhAKlX7AIHulcVhkBeSN2WtjZbjzde5tlUKwVWyWEhP7sE 8 | o0IwQDAdBgNVHQ4EFgQUf2VV1eVj09YOwgKa1ZX1kq7VAd4wHwYDVR0jBBgwFoAU 9 | Cw2pVpGKz2xkIjbVHYh0LnzdkW4wBQYDK2VwA0EA5WMQjRBwEI/QtLzSAQTy5fSM 10 | FGorJMxGtUpBjFzCEbYku4EHvbJoN707Kf9vYpgxjAIyP2cowxj/Wdd4paegBg== 11 | -----END CERTIFICATE----- 12 | -------------------------------------------------------------------------------- /tests/mssql/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG VERSION 2 | FROM mcr.microsoft.com/mssql/server:${VERSION} 3 | 4 | # Create a config directory 5 | RUN mkdir -p /usr/config 6 | WORKDIR /usr/config 7 | 8 | # Bundle config source 9 | COPY mssql/entrypoint.sh /usr/config/entrypoint.sh 10 | COPY mssql/configure-db.sh /usr/config/configure-db.sh 11 | COPY mssql/setup.sql /usr/config/setup.sql 12 | 13 | # Grant permissions for to our scripts to be executable 14 | USER root 15 | RUN chmod +x /usr/config/entrypoint.sh 16 | RUN chmod +x /usr/config/configure-db.sh 17 | RUN chown 10001 /usr/config/entrypoint.sh 18 | RUN chown 10001 /usr/config/configure-db.sh 19 | USER 10001 20 | 21 | ENTRYPOINT ["/usr/config/entrypoint.sh"] 22 | -------------------------------------------------------------------------------- /examples/postgres/files/README.md: -------------------------------------------------------------------------------- 1 | # Query files Example 2 | 3 | ## Description 4 | 5 | This example demonstrates storing external files to use for querying data. 6 | Encapsulating your SQL queries can be helpful in several ways, assisting with intellisense, 7 | etc. 8 | 9 | 10 | ## Setup 11 | 12 | 1. Declare the database URL 13 | 14 | ``` 15 | export DATABASE_URL="postgres://postgres:password@localhost/files" 16 | ``` 17 | 18 | 2. Create the database. 19 | 20 | ``` 21 | $ sqlx db create 22 | ``` 23 | 24 | 3. Run sql migrations 25 | 26 | ``` 27 | $ sqlx migrate run 28 | ``` 29 | 30 | ## Usage 31 | 32 | Run the project 33 | 34 | ``` 35 | cargo run files 36 | ``` -------------------------------------------------------------------------------- /sqlx-cli/src/bin/cargo-sqlx.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use console::style; 3 | use sqlx_cli::Opt; 4 | use std::process; 5 | 6 | // cargo invokes this binary as `cargo-sqlx sqlx ` 7 | // so the parser below is defined with that in mind 8 | #[derive(Parser, Debug)] 9 | #[clap(bin_name = "cargo")] 10 | enum Cli { 11 | Sqlx(Opt), 12 | } 13 | 14 | #[tokio::main] 15 | async fn main() { 16 | sqlx_cli::maybe_apply_dotenv(); 17 | 18 | sqlx::any::install_default_drivers(); 19 | 20 | let Cli::Sqlx(opt) = Cli::parse(); 21 | 22 | if let Err(error) = sqlx_cli::run(opt).await { 23 | println!("{} {}", style("error:").bold().red(), error); 24 | process::exit(1); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/postgres/axum-social-with-tests/src/http/mod.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use axum::{Extension, Router}; 3 | use sqlx::PgPool; 4 | 5 | mod error; 6 | 7 | mod post; 8 | mod user; 9 | 10 | pub use self::error::Error; 11 | 12 | pub type Result = ::std::result::Result; 13 | 14 | pub fn app(db: PgPool) -> Router { 15 | Router::new() 16 | .merge(user::router()) 17 | .merge(post::router()) 18 | .layer(Extension(db)) 19 | } 20 | 21 | pub async fn serve(db: PgPool) -> anyhow::Result<()> { 22 | axum::Server::bind(&"0.0.0.0:8080".parse().unwrap()) 23 | .serve(app(db).into_make_service()) 24 | .await 25 | .context("failed to serve API") 26 | } 27 | -------------------------------------------------------------------------------- /tests/ui/postgres/unsupported-type.stderr: -------------------------------------------------------------------------------- 1 | error: unsupported type CIRCLE of column #1 ("circle") 2 | --> $DIR/unsupported-type.rs:3:13 3 | | 4 | 3 | let _ = sqlx::query!("select null::circle"); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: unsupported type CIRCLE for param #1 10 | --> $DIR/unsupported-type.rs:4:13 11 | | 12 | 4 | let _ = sqlx::query!("select $1::circle", panic!()); 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 14 | | 15 | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) 16 | -------------------------------------------------------------------------------- /sqlx-core/src/io/decode.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | 3 | use crate::error::Error; 4 | 5 | pub trait ProtocolDecode<'de, Context = ()> 6 | where 7 | Self: Sized, 8 | { 9 | fn decode(buf: Bytes) -> Result 10 | where 11 | Self: ProtocolDecode<'de, ()>, 12 | { 13 | Self::decode_with(buf, ()) 14 | } 15 | 16 | fn decode_with(buf: Bytes, context: Context) -> Result; 17 | } 18 | 19 | impl ProtocolDecode<'_> for Bytes { 20 | fn decode_with(buf: Bytes, _: ()) -> Result { 21 | Ok(buf) 22 | } 23 | } 24 | 25 | impl ProtocolDecode<'_> for () { 26 | fn decode_with(_: Bytes, _: ()) -> Result<(), Error> { 27 | Ok(()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/postgres/json/README.md: -------------------------------------------------------------------------------- 1 | # JSON Example 2 | 3 | ## Setup 4 | 5 | 1. Declare the database URL 6 | 7 | ``` 8 | export DATABASE_URL="postgres://postgres:password@localhost/json" 9 | ``` 10 | 11 | 2. Create the database. 12 | 13 | ``` 14 | $ sqlx db create 15 | ``` 16 | 17 | 3. Run sql migrations 18 | 19 | ``` 20 | $ sqlx migrate run 21 | ``` 22 | 23 | ## Usage 24 | 25 | Add a person 26 | 27 | ``` 28 | echo '{ "name": "John Doe", "age": 30 }' | cargo run -- add 29 | ``` 30 | 31 | or with extra keys 32 | 33 | ``` 34 | echo '{ "name": "Jane Doe", "age": 25, "array": ["string", true, 0] }' | cargo run -- add 35 | ``` 36 | 37 | List all people 38 | 39 | ``` 40 | cargo run 41 | ``` 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Enabling some optional features adds unexpected crates to Cargo.lock 4 | url: https://github.com/launchbadge/sqlx/issues/3211 5 | about: See this issue. 6 | - name: I have a question or problem 7 | url: https://github.com/launchbadge/sqlx/tree/main/FAQ.md 8 | about: See our FAQ. 9 | - name: I have a question or problem not covered in the FAQ 10 | url: https://github.com/launchbadge/sqlx/discussions/new?category=q-a 11 | about: Open a Q&A discussion. 12 | - name: Join SQLx's Discord 13 | url: https://discord.gg/hPm3WqA 14 | about: Join our Discord server for help, discussions and release announcements. 15 | -------------------------------------------------------------------------------- /examples/sqlite/extension/sqlx.toml: -------------------------------------------------------------------------------- 1 | [common.drivers.sqlite] 2 | # Including the full path to the extension is somewhat unusual, 3 | # because normally an extension will be installed in a standard 4 | # directory which is part of the library search path. If that were the 5 | # case here, the unsafe-load-extensions value could just be `["ipaddr"]` 6 | # 7 | # When the extension file is installed in a non-standard location, as 8 | # in this example, there are two options: 9 | # * Provide the full path the the extension, as seen below. 10 | # * Add the non-standard location to the library search path, which on 11 | # Linux means adding it to the LD_LIBRARY_PATH environment variable. 12 | unsafe-load-extensions = ["/tmp/sqlite3-lib/ipaddr"] 13 | -------------------------------------------------------------------------------- /tests/certs/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIB0zCCAYWgAwIBAgIUEFN5G3AUb5d/ZC+q+YtFuMeoWvowBQYDK2VwMF8xCzAJ 3 | BgNVBAYTAnVzMRMwEQYDVQQIDApjYWxpZm9ybmlhMSEwHwYDVQQKDBhJbnRlcm5l 4 | dCBXaWRnaXRzIFB0eSBMdGQxGDAWBgNVBAMMD0F1c3RpbiBCb25hbmRlcjAeFw0y 5 | NTA3MDEwMzA4MTVaFw0zNTA2MjkwMzA4MTVaMF8xCzAJBgNVBAYTAnVzMRMwEQYD 6 | VQQIDApjYWxpZm9ybmlhMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBM 7 | dGQxGDAWBgNVBAMMD0F1c3RpbiBCb25hbmRlcjAqMAUGAytlcAMhAHfjdF5QJ4OW 8 | k/3XLlsxDcP8cwBVmB+ySWKq2JanRS8uo1MwUTAdBgNVHQ4EFgQUCw2pVpGKz2xk 9 | IjbVHYh0LnzdkW4wHwYDVR0jBBgwFoAUCw2pVpGKz2xkIjbVHYh0LnzdkW4wDwYD 10 | VR0TAQH/BAUwAwEB/zAFBgMrZXADQQBA6VMDBPz9x0b5Wvw4D+2UrLdyhzzjqtrX 11 | UQOjCTqcKdEwWvgS6ftiQlQJPDfkVDEMOAJgqRmEGvsKjvwMCPIC 12 | -----END CERTIFICATE----- 13 | -------------------------------------------------------------------------------- /sqlx-core/src/any/column.rs: -------------------------------------------------------------------------------- 1 | use crate::any::{Any, AnyTypeInfo}; 2 | use crate::column::Column; 3 | use crate::ext::ustr::UStr; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct AnyColumn { 7 | // NOTE: these fields are semver-exempt. See crate root docs for details. 8 | #[doc(hidden)] 9 | pub ordinal: usize, 10 | 11 | #[doc(hidden)] 12 | pub name: UStr, 13 | 14 | #[doc(hidden)] 15 | pub type_info: AnyTypeInfo, 16 | } 17 | impl Column for AnyColumn { 18 | type Database = Any; 19 | 20 | fn ordinal(&self) -> usize { 21 | self.ordinal 22 | } 23 | 24 | fn name(&self) -> &str { 25 | &self.name 26 | } 27 | 28 | fn type_info(&self) -> &AnyTypeInfo { 29 | &self.type_info 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /sqlx-postgres/src/types/rust_decimal-range.md: -------------------------------------------------------------------------------- 1 | #### Note: `rust_decimal::Decimal` Has a Smaller Range than `NUMERIC` 2 | `NUMERIC` is can have up to 131,072 digits before the decimal point, and 16,384 digits after it. 3 | See [Section 8.1, Numeric Types] of the Postgres manual for details. 4 | 5 | However, `rust_decimal::Decimal` is limited to a maximum absolute magnitude of 296 - 1, 6 | a number with 67 decimal digits, and a minimum absolute magnitude of 10-28, a number with, unsurprisingly, 7 | 28 decimal digits. 8 | 9 | Thus, in contrast with `BigDecimal`, `NUMERIC` can actually represent every possible value of `rust_decimal::Decimal`, 10 | but not the other way around. This means that encoding should never fail, but decoding can. 11 | -------------------------------------------------------------------------------- /examples/postgres/multi-database/accounts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-postgres-multi-database-accounts" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | sqlx = { workspace = true, features = ["postgres", "time", "uuid", "macros", "sqlx-toml"] } 8 | tokio = { version = "1", features = ["rt", "sync"] } 9 | 10 | argon2 = { version = "0.5.3", features = ["password-hash"] } 11 | password-hash = { version = "0.5", features = ["std"] } 12 | 13 | uuid = { version = "1", features = ["serde"] } 14 | thiserror = "1" 15 | rand = "0.8" 16 | 17 | time = { version = "0.3.37", features = ["serde"] } 18 | 19 | serde = { version = "1.0.218", features = ["derive"] } 20 | 21 | [dev-dependencies] 22 | sqlx = { workspace = true, features = ["runtime-tokio"] } 23 | -------------------------------------------------------------------------------- /tests/ui/postgres/gated/uuid.stderr: -------------------------------------------------------------------------------- 1 | error: optional sqlx feature `uuid` required for type UUID of column #1 ("uuid") 2 | --> $DIR/uuid.rs:2:13 3 | | 4 | 2 | let _ = sqlx::query!("select 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid"); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: optional sqlx feature `uuid` required for type UUID of param #1 10 | --> $DIR/uuid.rs:3:13 11 | | 12 | 3 | let _ = sqlx::query!("select $1::uuid", ()); 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 14 | | 15 | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) 16 | -------------------------------------------------------------------------------- /sqlx-core/src/common/mod.rs: -------------------------------------------------------------------------------- 1 | mod statement_cache; 2 | 3 | pub use statement_cache::StatementCache; 4 | use std::fmt::{Debug, Formatter}; 5 | use std::ops::{Deref, DerefMut}; 6 | 7 | /// A wrapper for `Fn`s that provides a debug impl that just says "Function" 8 | pub struct DebugFn(pub F); 9 | 10 | impl Deref for DebugFn { 11 | type Target = F; 12 | 13 | fn deref(&self) -> &Self::Target { 14 | &self.0 15 | } 16 | } 17 | 18 | impl DerefMut for DebugFn { 19 | fn deref_mut(&mut self) -> &mut Self::Target { 20 | &mut self.0 21 | } 22 | } 23 | 24 | impl Debug for DebugFn { 25 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 26 | f.debug_tuple("Function").finish() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/postgres/axum-social-with-tests/tests/fixtures/comments.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO public.comment (comment_id, post_id, user_id, content, created_at) 2 | VALUES 3 | -- from: bob 4 | ('3a86b8f8-827b-4f14-94a2-34517b4b5bde', 'd9ca2672-24c5-4442-b32f-cd717adffbaa', 5 | 'c994b839-84f4-4509-ad49-59119133d6f5', 'lol bet ur still bad, 1v1 me', '2022-07-29 01:52:31.167673'), 6 | -- from: alice 7 | ('d6f862b5-2b87-4af4-b15e-6b3398729e6d', 'd9ca2672-24c5-4442-b32f-cd717adffbaa', 8 | '51b374f1-93ae-4c5c-89dd-611bda8412ce', 'you''re on!', '2022-07-29 01:53:53.115782'), 9 | -- from: alice 10 | ('1eed85ae-adae-473c-8d05-b1dae0a1df63', '7e3d4d16-a35e-46ba-8223-b4f1debbfbfe', 11 | '51b374f1-93ae-4c5c-89dd-611bda8412ce', 'lol you''re just mad you lost :P', '2022-07-29 01:55:50.116119'); 12 | 13 | -------------------------------------------------------------------------------- /sqlx-core/src/any/query_result.rs: -------------------------------------------------------------------------------- 1 | use std::iter::{Extend, IntoIterator}; 2 | 3 | #[derive(Debug, Default)] 4 | pub struct AnyQueryResult { 5 | #[doc(hidden)] 6 | pub rows_affected: u64, 7 | #[doc(hidden)] 8 | pub last_insert_id: Option, 9 | } 10 | 11 | impl AnyQueryResult { 12 | pub fn rows_affected(&self) -> u64 { 13 | self.rows_affected 14 | } 15 | 16 | pub fn last_insert_id(&self) -> Option { 17 | self.last_insert_id 18 | } 19 | } 20 | 21 | impl Extend for AnyQueryResult { 22 | fn extend>(&mut self, iter: T) { 23 | for elem in iter { 24 | self.rows_affected += elem.rows_affected; 25 | self.last_insert_id = elem.last_insert_id; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/postgres/multi-tenant/accounts/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-postgres-multi-tenant-accounts" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | tokio = { version = "1", features = ["rt", "sync"] } 8 | 9 | argon2 = { version = "0.5.3", features = ["password-hash"] } 10 | password-hash = { version = "0.5", features = ["std"] } 11 | 12 | uuid = { version = "1", features = ["serde"] } 13 | thiserror = "1" 14 | rand = "0.8" 15 | 16 | time = { version = "0.3.37", features = ["serde"] } 17 | 18 | serde = { version = "1.0.218", features = ["derive"] } 19 | 20 | [dependencies.sqlx] 21 | # version = "0.9.0" 22 | workspace = true 23 | features = ["postgres", "time", "uuid", "macros", "sqlx-toml", "migrate"] 24 | 25 | [dev-dependencies] 26 | sqlx = { workspace = true, features = ["runtime-tokio"] } 27 | -------------------------------------------------------------------------------- /tests/postgres/fixtures/comments.sql: -------------------------------------------------------------------------------- 1 | insert into comment(comment_id, post_id, user_id, content, created_at) 2 | values ('fbbbb7dc-dc6f-4649-b663-8d3636035164', 3 | '252c1d98-a9b0-4f18-8298-e59058bdfe16', 4 | '297923c5-a83c-4052-bab0-030887154e52', 5 | 'lol bet ur still bad, 1v1 me', 6 | now() + '50 minutes ago'::interval), 7 | ('cb7612a2-cff4-4e3e-a768-055f01f25dc4', 8 | '252c1d98-a9b0-4f18-8298-e59058bdfe16', 9 | '297923c5-a83c-4052-bab0-030887154e52', 10 | 'you''re on!', 11 | now() + '45 minutes ago'::interval), 12 | ('f2164fcc-a770-4f52-8714-d9cc6a1c89cf', 13 | '844265f7-2472-4689-9a2e-b21f40dbf401', 14 | '297923c5-a83c-4052-bab0-030887154e52', 15 | 'lol you''re just mad you lost :P', 16 | now() + '15 minutes ago'::interval); 17 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Running Tests 4 | SQLx uses docker to run many compatible database systems for integration testing. You'll need to [install docker](https://docs.docker.com/engine/) to run the full suite. You can validate your docker installation with: 5 | 6 | $ docker run hello-world 7 | 8 | Start the databases with `docker-compose` before running tests: 9 | 10 | $ docker-compose up 11 | 12 | Run all tests against all supported databases using: 13 | 14 | $ ./x.py 15 | 16 | If you see test failures, or want to run a more specific set of tests against a specific database, you can specify both the features to be tests and the DATABASE_URL. e.g. 17 | 18 | $ DATABASE_URL=mysql://root:password@127.0.0.1:49183/sqlx cargo test --no-default-features --features macros,offline,any,all-types,mysql,runtime-async-std-native-tls 19 | -------------------------------------------------------------------------------- /sqlx-postgres/src/query_result.rs: -------------------------------------------------------------------------------- 1 | use std::iter::{Extend, IntoIterator}; 2 | 3 | #[derive(Debug, Default)] 4 | pub struct PgQueryResult { 5 | pub(super) rows_affected: u64, 6 | } 7 | 8 | impl PgQueryResult { 9 | pub fn rows_affected(&self) -> u64 { 10 | self.rows_affected 11 | } 12 | } 13 | 14 | impl Extend for PgQueryResult { 15 | fn extend>(&mut self, iter: T) { 16 | for elem in iter { 17 | self.rows_affected += elem.rows_affected; 18 | } 19 | } 20 | } 21 | 22 | #[cfg(feature = "any")] 23 | impl From for sqlx_core::any::AnyQueryResult { 24 | fn from(done: PgQueryResult) -> Self { 25 | sqlx_core::any::AnyQueryResult { 26 | rows_affected: done.rows_affected, 27 | last_insert_id: None, 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/sqlite/derives.rs: -------------------------------------------------------------------------------- 1 | use sqlx::Sqlite; 2 | use sqlx_test::test_type; 3 | 4 | #[derive(Debug, PartialEq, sqlx::Type)] 5 | #[repr(u32)] 6 | enum Origin { 7 | Foo = 1, 8 | Bar = 2, 9 | } 10 | 11 | test_type!(origin_enum(Sqlite, 12 | "1" == Origin::Foo, 13 | "2" == Origin::Bar, 14 | )); 15 | 16 | #[derive(PartialEq, Eq, Debug, sqlx::Type)] 17 | #[sqlx(transparent)] 18 | struct TransparentTuple(i64); 19 | 20 | #[derive(PartialEq, Eq, Debug, sqlx::Type)] 21 | #[sqlx(transparent)] 22 | struct TransparentNamed { 23 | field: i64, 24 | } 25 | 26 | test_type!(transparent_tuple(Sqlite, 27 | "0" == TransparentTuple(0), 28 | "23523" == TransparentTuple(23523) 29 | )); 30 | 31 | test_type!(transparent_named(Sqlite, 32 | "0" == TransparentNamed { field: 0 }, 33 | "23523" == TransparentNamed { field: 23523 }, 34 | )); 35 | -------------------------------------------------------------------------------- /sqlx-postgres/src/message/flush.rs: -------------------------------------------------------------------------------- 1 | use crate::message::{FrontendMessage, FrontendMessageFormat}; 2 | use sqlx_core::Error; 3 | use std::num::Saturating; 4 | 5 | /// The Flush message does not cause any specific output to be generated, 6 | /// but forces the backend to deliver any data pending in its output buffers. 7 | /// 8 | /// A Flush must be sent after any extended-query command except Sync, if the 9 | /// frontend wishes to examine the results of that command before issuing more commands. 10 | #[derive(Debug)] 11 | pub struct Flush; 12 | 13 | impl FrontendMessage for Flush { 14 | const FORMAT: FrontendMessageFormat = FrontendMessageFormat::Flush; 15 | 16 | #[inline(always)] 17 | fn body_size_hint(&self) -> Saturating { 18 | Saturating(0) 19 | } 20 | 21 | #[inline(always)] 22 | fn encode_body(&self, _buf: &mut Vec) -> Result<(), Error> { 23 | Ok(()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sqlx-sqlite/src/column.rs: -------------------------------------------------------------------------------- 1 | use crate::ext::ustr::UStr; 2 | use crate::{Sqlite, SqliteTypeInfo}; 3 | 4 | pub(crate) use sqlx_core::column::*; 5 | 6 | #[derive(Debug, Clone)] 7 | #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] 8 | pub struct SqliteColumn { 9 | pub(crate) name: UStr, 10 | pub(crate) ordinal: usize, 11 | pub(crate) type_info: SqliteTypeInfo, 12 | 13 | #[cfg_attr(feature = "offline", serde(default))] 14 | pub(crate) origin: ColumnOrigin, 15 | } 16 | 17 | impl Column for SqliteColumn { 18 | type Database = Sqlite; 19 | 20 | fn ordinal(&self) -> usize { 21 | self.ordinal 22 | } 23 | 24 | fn name(&self) -> &str { 25 | &self.name 26 | } 27 | 28 | fn type_info(&self) -> &SqliteTypeInfo { 29 | &self.type_info 30 | } 31 | 32 | fn origin(&self) -> ColumnOrigin { 33 | self.origin.clone() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/postgres/multi-tenant/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-postgres-multi-tenant" 3 | version.workspace = true 4 | license.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | keywords.workspace = true 8 | categories.workspace = true 9 | authors.workspace = true 10 | 11 | [dependencies] 12 | tokio = { version = "1", features = ["rt-multi-thread", "macros"] } 13 | 14 | color-eyre = "0.6.3" 15 | dotenvy = "0.15.7" 16 | tracing-subscriber = "0.3.19" 17 | 18 | rust_decimal = "1.36.0" 19 | 20 | rand = "0.8.5" 21 | 22 | [dependencies.sqlx] 23 | # version = "0.9.0" 24 | workspace = true 25 | features = ["runtime-tokio", "postgres", "migrate", "sqlx-toml"] 26 | 27 | [dependencies.accounts] 28 | path = "accounts" 29 | package = "sqlx-example-postgres-multi-tenant-accounts" 30 | 31 | [dependencies.payments] 32 | path = "payments" 33 | package = "sqlx-example-postgres-multi-tenant-payments" 34 | 35 | [lints] 36 | workspace = true 37 | -------------------------------------------------------------------------------- /sqlx-core/src/type_info.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Display}; 2 | 3 | /// Provides information about a SQL type for the database driver. 4 | pub trait TypeInfo: Debug + Display + Clone + PartialEq + Send + Sync { 5 | fn is_null(&self) -> bool; 6 | 7 | /// Returns the database system name of the type. Length specifiers should not be included. 8 | /// Common type names are `VARCHAR`, `TEXT`, or `INT`. Type names should be uppercase. They 9 | /// should be a rough approximation of how they are written in SQL in the given database. 10 | fn name(&self) -> &str; 11 | 12 | /// Return `true` if `self` and `other` represent mutually compatible types. 13 | /// 14 | /// Defaults to `self == other`. 15 | fn type_compatible(&self, other: &Self) -> bool 16 | where 17 | Self: Sized, 18 | { 19 | self == other 20 | } 21 | 22 | #[doc(hidden)] 23 | fn is_void(&self) -> bool { 24 | false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/postgres/multi-database/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-postgres-multi-database" 3 | version.workspace = true 4 | license.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | keywords.workspace = true 8 | categories.workspace = true 9 | authors.workspace = true 10 | 11 | [dependencies] 12 | tokio = { version = "1", features = ["rt-multi-thread", "macros"] } 13 | 14 | color-eyre = "0.6.3" 15 | dotenvy = "0.15.7" 16 | tracing-subscriber = "0.3.19" 17 | 18 | rust_decimal = "1.36.0" 19 | 20 | rand = "0.8.5" 21 | 22 | [dependencies.sqlx] 23 | # version = "0.9.0" 24 | workspace = true 25 | features = ["runtime-tokio", "postgres", "migrate", "sqlx-toml"] 26 | 27 | [dependencies.accounts] 28 | path = "accounts" 29 | package = "sqlx-example-postgres-multi-database-accounts" 30 | 31 | [dependencies.payments] 32 | path = "payments" 33 | package = "sqlx-example-postgres-multi-database-payments" 34 | 35 | [lints] 36 | workspace = true 37 | -------------------------------------------------------------------------------- /sqlx-postgres/src/message/query.rs: -------------------------------------------------------------------------------- 1 | use crate::io::BufMutExt; 2 | use crate::message::{FrontendMessage, FrontendMessageFormat}; 3 | use sqlx_core::Error; 4 | use std::num::Saturating; 5 | 6 | #[derive(Debug)] 7 | pub struct Query<'a>(pub &'a str); 8 | 9 | impl FrontendMessage for Query<'_> { 10 | const FORMAT: FrontendMessageFormat = FrontendMessageFormat::Query; 11 | 12 | fn body_size_hint(&self) -> Saturating { 13 | let mut size = Saturating(0); 14 | 15 | size += self.0.len(); 16 | size += 1; // NUL terminator 17 | 18 | size 19 | } 20 | 21 | fn encode_body(&self, buf: &mut Vec) -> Result<(), Error> { 22 | buf.put_str_nul(self.0); 23 | Ok(()) 24 | } 25 | } 26 | 27 | #[test] 28 | fn test_encode_query() { 29 | const EXPECTED: &[u8] = b"Q\0\0\0\x0DSELECT 1\0"; 30 | 31 | let mut buf = Vec::new(); 32 | let m = Query("SELECT 1"); 33 | 34 | m.encode_msg(&mut buf).unwrap(); 35 | 36 | assert_eq!(buf, EXPECTED); 37 | } 38 | -------------------------------------------------------------------------------- /sqlx-core/src/any/types/bool.rs: -------------------------------------------------------------------------------- 1 | use crate::any::{Any, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind}; 2 | use crate::database::Database; 3 | use crate::decode::Decode; 4 | use crate::encode::{Encode, IsNull}; 5 | use crate::error::BoxDynError; 6 | use crate::types::Type; 7 | 8 | impl Type for bool { 9 | fn type_info() -> AnyTypeInfo { 10 | AnyTypeInfo { 11 | kind: AnyTypeInfoKind::Bool, 12 | } 13 | } 14 | } 15 | 16 | impl Encode<'_, Any> for bool { 17 | fn encode_by_ref( 18 | &self, 19 | buf: &mut ::ArgumentBuffer, 20 | ) -> Result { 21 | buf.0.push(AnyValueKind::Bool(*self)); 22 | Ok(IsNull::No) 23 | } 24 | } 25 | 26 | impl<'r> Decode<'r, Any> for bool { 27 | fn decode(value: ::ValueRef<'r>) -> Result { 28 | match value.kind { 29 | AnyValueKind::Bool(b) => Ok(*b), 30 | other => other.unexpected(), 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sqlx-mysql/src/types/rust_decimal.rs: -------------------------------------------------------------------------------- 1 | use rust_decimal::Decimal; 2 | 3 | use crate::decode::Decode; 4 | use crate::encode::{Encode, IsNull}; 5 | use crate::error::BoxDynError; 6 | use crate::io::MySqlBufMutExt; 7 | use crate::protocol::text::ColumnType; 8 | use crate::types::Type; 9 | use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; 10 | 11 | impl Type for Decimal { 12 | fn type_info() -> MySqlTypeInfo { 13 | MySqlTypeInfo::binary(ColumnType::NewDecimal) 14 | } 15 | 16 | fn compatible(ty: &MySqlTypeInfo) -> bool { 17 | matches!(ty.r#type, ColumnType::Decimal | ColumnType::NewDecimal) 18 | } 19 | } 20 | 21 | impl Encode<'_, MySql> for Decimal { 22 | fn encode_by_ref(&self, buf: &mut Vec) -> Result { 23 | buf.put_str_lenenc(&self.to_string()); 24 | 25 | Ok(IsNull::No) 26 | } 27 | } 28 | 29 | impl Decode<'_, MySql> for Decimal { 30 | fn decode(value: MySqlValueRef<'_>) -> Result { 31 | Ok(value.as_str()?.parse()?) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sqlx-sqlite/src/types/bool.rs: -------------------------------------------------------------------------------- 1 | use crate::arguments::SqliteArgumentsBuffer; 2 | use crate::decode::Decode; 3 | use crate::encode::{Encode, IsNull}; 4 | use crate::error::BoxDynError; 5 | use crate::type_info::DataType; 6 | use crate::types::Type; 7 | use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; 8 | 9 | impl Type for bool { 10 | fn type_info() -> SqliteTypeInfo { 11 | SqliteTypeInfo(DataType::Bool) 12 | } 13 | 14 | fn compatible(ty: &SqliteTypeInfo) -> bool { 15 | matches!(ty.0, DataType::Bool | DataType::Int4 | DataType::Integer) 16 | } 17 | } 18 | 19 | impl Encode<'_, Sqlite> for bool { 20 | fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { 21 | args.push(SqliteArgumentValue::Int((*self).into())); 22 | 23 | Ok(IsNull::No) 24 | } 25 | } 26 | 27 | impl<'r> Decode<'r, Sqlite> for bool { 28 | fn decode(value: SqliteValueRef<'r>) -> Result { 29 | Ok(value.int64()? != 0) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /sqlx-mysql/src/types/bigdecimal.rs: -------------------------------------------------------------------------------- 1 | use bigdecimal::BigDecimal; 2 | 3 | use crate::decode::Decode; 4 | use crate::encode::{Encode, IsNull}; 5 | use crate::error::BoxDynError; 6 | use crate::io::MySqlBufMutExt; 7 | use crate::protocol::text::ColumnType; 8 | use crate::types::Type; 9 | use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; 10 | 11 | impl Type for BigDecimal { 12 | fn type_info() -> MySqlTypeInfo { 13 | MySqlTypeInfo::binary(ColumnType::NewDecimal) 14 | } 15 | 16 | fn compatible(ty: &MySqlTypeInfo) -> bool { 17 | matches!(ty.r#type, ColumnType::Decimal | ColumnType::NewDecimal) 18 | } 19 | } 20 | 21 | impl Encode<'_, MySql> for BigDecimal { 22 | fn encode_by_ref(&self, buf: &mut Vec) -> Result { 23 | buf.put_str_lenenc(&self.to_string()); 24 | 25 | Ok(IsNull::No) 26 | } 27 | } 28 | 29 | impl Decode<'_, MySql> for BigDecimal { 30 | fn decode(value: MySqlValueRef<'_>) -> Result { 31 | Ok(value.as_str()?.parse()?) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sqlx-macros-core/src/common.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | use std::path::{Path, PathBuf}; 3 | 4 | pub(crate) fn resolve_path(path: impl AsRef, err_span: Span) -> syn::Result { 5 | let path = path.as_ref(); 6 | 7 | if path.is_absolute() { 8 | return Err(syn::Error::new( 9 | err_span, 10 | "absolute paths will only work on the current machine", 11 | )); 12 | } 13 | 14 | // requires `proc_macro::SourceFile::path()` to be stable 15 | // https://github.com/rust-lang/rust/issues/54725 16 | if path.is_relative() 17 | && path 18 | .parent() 19 | .is_none_or(|parent| parent.as_os_str().is_empty()) 20 | { 21 | return Err(syn::Error::new( 22 | err_span, 23 | "paths relative to the current file's directory are not currently supported", 24 | )); 25 | } 26 | 27 | let mut out_path = crate::manifest_dir().map_err(|e| syn::Error::new(err_span, e))?; 28 | 29 | out_path.push(path); 30 | 31 | Ok(out_path) 32 | } 33 | -------------------------------------------------------------------------------- /sqlx-mysql/src/column.rs: -------------------------------------------------------------------------------- 1 | use crate::ext::ustr::UStr; 2 | use crate::protocol::text::ColumnFlags; 3 | use crate::{MySql, MySqlTypeInfo}; 4 | pub(crate) use sqlx_core::column::*; 5 | 6 | #[derive(Debug, Clone)] 7 | #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] 8 | pub struct MySqlColumn { 9 | pub(crate) ordinal: usize, 10 | pub(crate) name: UStr, 11 | pub(crate) type_info: MySqlTypeInfo, 12 | 13 | #[cfg_attr(feature = "offline", serde(default))] 14 | pub(crate) origin: ColumnOrigin, 15 | 16 | #[cfg_attr(feature = "offline", serde(skip))] 17 | pub(crate) flags: Option, 18 | } 19 | 20 | impl Column for MySqlColumn { 21 | type Database = MySql; 22 | 23 | fn ordinal(&self) -> usize { 24 | self.ordinal 25 | } 26 | 27 | fn name(&self) -> &str { 28 | &self.name 29 | } 30 | 31 | fn type_info(&self) -> &MySqlTypeInfo { 32 | &self.type_info 33 | } 34 | 35 | fn origin(&self) -> ColumnOrigin { 36 | self.origin.clone() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/sqlite/any.rs: -------------------------------------------------------------------------------- 1 | use sqlx::Any; 2 | use sqlx_test::new; 3 | 4 | #[sqlx_macros::test] 5 | async fn it_encodes_bool_with_any() -> anyhow::Result<()> { 6 | sqlx::any::install_default_drivers(); 7 | let mut conn = new::().await?; 8 | 9 | let res = sqlx::query("INSERT INTO accounts (name, is_active) VALUES (?, ?)") 10 | .bind("Harrison Ford") 11 | .bind(true) 12 | .execute(&mut conn) 13 | .await 14 | .expect("failed to encode bool"); 15 | assert_eq!(res.rows_affected(), 1); 16 | 17 | Ok(()) 18 | } 19 | 20 | #[sqlx_macros::test] 21 | async fn issue_3179() -> anyhow::Result<()> { 22 | sqlx::any::install_default_drivers(); 23 | 24 | let mut conn = new::().await?; 25 | 26 | // 4294967297 = 2^32 27 | let number: i64 = sqlx::query_scalar("SELECT 4294967296") 28 | .fetch_one(&mut conn) 29 | .await?; 30 | 31 | // Previously, the decoding would use `i32` as an intermediate which would overflow to 0. 32 | assert_eq!(number, 4294967296); 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /examples/postgres/axum-social-with-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-postgres-axum-social" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | # Primary crates 10 | axum = { version = "0.5.13", features = ["macros"] } 11 | sqlx = { path = "../../../", features = [ "runtime-tokio", "tls-rustls-ring", "postgres", "time", "uuid" ] } 12 | tokio = { version = "1.20.1", features = ["rt-multi-thread", "macros"] } 13 | 14 | # Important secondary crates 15 | argon2 = "0.4.1" 16 | rand = "0.8.5" 17 | regex = "1.6.0" 18 | serde = "1.0.140" 19 | serde_with = { version = "2.0.0", features = ["time_0_3"] } 20 | time = "0.3.11" 21 | uuid = { version = "1.1.2", features = ["serde"] } 22 | validator = { version = "0.16.0", features = ["derive"] } 23 | 24 | # Auxilliary crates 25 | anyhow = "1.0.58" 26 | dotenvy = "0.15.1" 27 | thiserror = "2.0.0" 28 | tracing = "0.1.35" 29 | 30 | [dev-dependencies] 31 | serde_json = "1.0.82" 32 | tower = "0.4.13" 33 | -------------------------------------------------------------------------------- /examples/postgres/multi-database/payments/migrations/01_setup.sql: -------------------------------------------------------------------------------- 1 | -- We try to ensure every table has `created_at` and `updated_at` columns, which can help immensely with debugging 2 | -- and auditing. 3 | -- 4 | -- While `created_at` can just be `default now()`, setting `updated_at` on update requires a trigger which 5 | -- is a lot of boilerplate. These two functions save us from writing that every time as instead we can just do 6 | -- 7 | -- select trigger_updated_at(''); 8 | -- 9 | -- after a `CREATE TABLE`. 10 | create or replace function set_updated_at() 11 | returns trigger as 12 | $$ 13 | begin 14 | NEW.updated_at = now(); 15 | return NEW; 16 | end; 17 | $$ language plpgsql; 18 | 19 | create or replace function trigger_updated_at(tablename regclass) 20 | returns void as 21 | $$ 22 | begin 23 | execute format('CREATE TRIGGER set_updated_at 24 | BEFORE UPDATE 25 | ON %s 26 | FOR EACH ROW 27 | WHEN (OLD is distinct from NEW) 28 | EXECUTE FUNCTION set_updated_at();', tablename); 29 | end; 30 | $$ language plpgsql; 31 | -------------------------------------------------------------------------------- /examples/postgres/multi-database/src/migrations/01_setup.sql: -------------------------------------------------------------------------------- 1 | -- We try to ensure every table has `created_at` and `updated_at` columns, which can help immensely with debugging 2 | -- and auditing. 3 | -- 4 | -- While `created_at` can just be `default now()`, setting `updated_at` on update requires a trigger which 5 | -- is a lot of boilerplate. These two functions save us from writing that every time as instead we can just do 6 | -- 7 | -- select trigger_updated_at('
'); 8 | -- 9 | -- after a `CREATE TABLE`. 10 | create or replace function set_updated_at() 11 | returns trigger as 12 | $$ 13 | begin 14 | NEW.updated_at = now(); 15 | return NEW; 16 | end; 17 | $$ language plpgsql; 18 | 19 | create or replace function trigger_updated_at(tablename regclass) 20 | returns void as 21 | $$ 22 | begin 23 | execute format('CREATE TRIGGER set_updated_at 24 | BEFORE UPDATE 25 | ON %s 26 | FOR EACH ROW 27 | WHEN (OLD is distinct from NEW) 28 | EXECUTE FUNCTION set_updated_at();', tablename); 29 | end; 30 | $$ language plpgsql; 31 | -------------------------------------------------------------------------------- /examples/postgres/multi-tenant/src/migrations/01_setup.sql: -------------------------------------------------------------------------------- 1 | -- We try to ensure every table has `created_at` and `updated_at` columns, which can help immensely with debugging 2 | -- and auditing. 3 | -- 4 | -- While `created_at` can just be `default now()`, setting `updated_at` on update requires a trigger which 5 | -- is a lot of boilerplate. These two functions save us from writing that every time as instead we can just do 6 | -- 7 | -- select trigger_updated_at('
'); 8 | -- 9 | -- after a `CREATE TABLE`. 10 | create or replace function set_updated_at() 11 | returns trigger as 12 | $$ 13 | begin 14 | NEW.updated_at = now(); 15 | return NEW; 16 | end; 17 | $$ language plpgsql; 18 | 19 | create or replace function trigger_updated_at(tablename regclass) 20 | returns void as 21 | $$ 22 | begin 23 | execute format('CREATE TRIGGER set_updated_at 24 | BEFORE UPDATE 25 | ON %s 26 | FOR EACH ROW 27 | WHEN (OLD is distinct from NEW) 28 | EXECUTE FUNCTION set_updated_at();', tablename); 29 | end; 30 | $$ language plpgsql; 31 | -------------------------------------------------------------------------------- /examples/postgres/preferred-crates/src/migrations/01_setup.sql: -------------------------------------------------------------------------------- 1 | -- We try to ensure every table has `created_at` and `updated_at` columns, which can help immensely with debugging 2 | -- and auditing. 3 | -- 4 | -- While `created_at` can just be `default now()`, setting `updated_at` on update requires a trigger which 5 | -- is a lot of boilerplate. These two functions save us from writing that every time as instead we can just do 6 | -- 7 | -- select trigger_updated_at('
'); 8 | -- 9 | -- after a `CREATE TABLE`. 10 | create or replace function set_updated_at() 11 | returns trigger as 12 | $$ 13 | begin 14 | NEW.updated_at = now(); 15 | return NEW; 16 | end; 17 | $$ language plpgsql; 18 | 19 | create or replace function trigger_updated_at(tablename regclass) 20 | returns void as 21 | $$ 22 | begin 23 | execute format('CREATE TRIGGER set_updated_at 24 | BEFORE UPDATE 25 | ON %s 26 | FOR EACH ROW 27 | WHEN (OLD is distinct from NEW) 28 | EXECUTE FUNCTION set_updated_at();', tablename); 29 | end; 30 | $$ language plpgsql; 31 | -------------------------------------------------------------------------------- /examples/postgres/multi-database/accounts/migrations/01_setup.sql: -------------------------------------------------------------------------------- 1 | -- We try to ensure every table has `created_at` and `updated_at` columns, which can help immensely with debugging 2 | -- and auditing. 3 | -- 4 | -- While `created_at` can just be `default now()`, setting `updated_at` on update requires a trigger which 5 | -- is a lot of boilerplate. These two functions save us from writing that every time as instead we can just do 6 | -- 7 | -- select trigger_updated_at('
'); 8 | -- 9 | -- after a `CREATE TABLE`. 10 | create or replace function set_updated_at() 11 | returns trigger as 12 | $$ 13 | begin 14 | NEW.updated_at = now(); 15 | return NEW; 16 | end; 17 | $$ language plpgsql; 18 | 19 | create or replace function trigger_updated_at(tablename regclass) 20 | returns void as 21 | $$ 22 | begin 23 | execute format('CREATE TRIGGER set_updated_at 24 | BEFORE UPDATE 25 | ON %s 26 | FOR EACH ROW 27 | WHEN (OLD is distinct from NEW) 28 | EXECUTE FUNCTION set_updated_at();', tablename); 29 | end; 30 | $$ language plpgsql; 31 | -------------------------------------------------------------------------------- /sqlx-core/src/io/read_buf.rs: -------------------------------------------------------------------------------- 1 | use bytes::{BufMut, BytesMut}; 2 | 3 | /// An extension for [`BufMut`] for getting a writeable buffer in safe code. 4 | pub trait ReadBuf: BufMut { 5 | /// Get the full capacity of this buffer as a safely initialized slice. 6 | fn init_mut(&mut self) -> &mut [u8]; 7 | } 8 | 9 | impl ReadBuf for &'_ mut [u8] { 10 | #[inline(always)] 11 | fn init_mut(&mut self) -> &mut [u8] { 12 | self 13 | } 14 | } 15 | 16 | impl ReadBuf for BytesMut { 17 | #[inline(always)] 18 | fn init_mut(&mut self) -> &mut [u8] { 19 | // `self.remaining_mut()` returns `usize::MAX - self.len()` 20 | let remaining = self.capacity() - self.len(); 21 | 22 | // I'm hoping for most uses that this operation is elided by the optimizer. 23 | self.put_bytes(0, remaining); 24 | 25 | self 26 | } 27 | } 28 | 29 | #[test] 30 | fn test_read_buf_bytes_mut() { 31 | let mut buf = BytesMut::with_capacity(8); 32 | buf.put_u32(0x12345678); 33 | 34 | assert_eq!(buf.init_mut(), [0x12, 0x34, 0x56, 0x78, 0, 0, 0, 0]); 35 | } 36 | -------------------------------------------------------------------------------- /examples/postgres/multi-tenant/payments/migrations/01_setup.sql: -------------------------------------------------------------------------------- 1 | -- We try to ensure every table has `created_at` and `updated_at` columns, which can help immensely with debugging 2 | -- and auditing. 3 | -- 4 | -- While `created_at` can just be `default now()`, setting `updated_at` on update requires a trigger which 5 | -- is a lot of boilerplate. These two functions save us from writing that every time as instead we can just do 6 | -- 7 | -- select payments.trigger_updated_at('
'); 8 | -- 9 | -- after a `CREATE TABLE`. 10 | create or replace function payments.set_updated_at() 11 | returns trigger as 12 | $$ 13 | begin 14 | NEW.updated_at = now(); 15 | return NEW; 16 | end; 17 | $$ language plpgsql; 18 | 19 | create or replace function payments.trigger_updated_at(tablename regclass) 20 | returns void as 21 | $$ 22 | begin 23 | execute format('CREATE TRIGGER set_updated_at 24 | BEFORE UPDATE 25 | ON %s 26 | FOR EACH ROW 27 | WHEN (OLD is distinct from NEW) 28 | EXECUTE FUNCTION payments.set_updated_at();', tablename); 29 | end; 30 | $$ language plpgsql; 31 | -------------------------------------------------------------------------------- /sqlx-mysql/src/database.rs: -------------------------------------------------------------------------------- 1 | use crate::value::{MySqlValue, MySqlValueRef}; 2 | use crate::{ 3 | MySqlArguments, MySqlColumn, MySqlConnection, MySqlQueryResult, MySqlRow, MySqlStatement, 4 | MySqlTransactionManager, MySqlTypeInfo, 5 | }; 6 | pub(crate) use sqlx_core::database::{Database, HasStatementCache}; 7 | 8 | /// MySQL database driver. 9 | #[derive(Debug)] 10 | pub struct MySql; 11 | 12 | impl Database for MySql { 13 | type Connection = MySqlConnection; 14 | 15 | type TransactionManager = MySqlTransactionManager; 16 | 17 | type Row = MySqlRow; 18 | 19 | type QueryResult = MySqlQueryResult; 20 | 21 | type Column = MySqlColumn; 22 | 23 | type TypeInfo = MySqlTypeInfo; 24 | 25 | type Value = MySqlValue; 26 | type ValueRef<'r> = MySqlValueRef<'r>; 27 | 28 | type Arguments = MySqlArguments; 29 | type ArgumentBuffer = Vec; 30 | 31 | type Statement = MySqlStatement; 32 | 33 | const NAME: &'static str = "MySQL"; 34 | 35 | const URL_SCHEMES: &'static [&'static str] = &["mysql", "mariadb"]; 36 | } 37 | 38 | impl HasStatementCache for MySql {} 39 | -------------------------------------------------------------------------------- /examples/postgres/preferred-crates/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-example-postgres-preferred-crates" 3 | version.workspace = true 4 | license.workspace = true 5 | edition.workspace = true 6 | repository.workspace = true 7 | keywords.workspace = true 8 | categories.workspace = true 9 | authors.workspace = true 10 | 11 | [dependencies] 12 | dotenvy.workspace = true 13 | 14 | anyhow = "1" 15 | chrono = "0.4" 16 | serde = { version = "1", features = ["derive"] } 17 | uuid = { version = "1", features = ["serde"] } 18 | 19 | [dependencies.tokio] 20 | workspace = true 21 | features = ["rt-multi-thread", "macros"] 22 | 23 | [dependencies.sqlx] 24 | # version = "0.9.0" 25 | workspace = true 26 | features = ["runtime-tokio", "postgres", "bigdecimal", "chrono", "derive", "macros", "migrate", "sqlx-toml"] 27 | 28 | [dependencies.uses-rust-decimal] 29 | path = "uses-rust-decimal" 30 | package = "sqlx-example-postgres-preferred-crates-uses-rust-decimal" 31 | 32 | [dependencies.uses-time] 33 | path = "uses-time" 34 | package = "sqlx-example-postgres-preferred-crates-uses-time" 35 | 36 | [lints] 37 | workspace = true 38 | -------------------------------------------------------------------------------- /sqlx-postgres/src/options/connect.rs: -------------------------------------------------------------------------------- 1 | use crate::connection::ConnectOptions; 2 | use crate::error::Error; 3 | use crate::{PgConnectOptions, PgConnection}; 4 | use log::LevelFilter; 5 | use sqlx_core::Url; 6 | use std::future::Future; 7 | use std::time::Duration; 8 | 9 | impl ConnectOptions for PgConnectOptions { 10 | type Connection = PgConnection; 11 | 12 | fn from_url(url: &Url) -> Result { 13 | Self::parse_from_url(url) 14 | } 15 | 16 | fn to_url_lossy(&self) -> Url { 17 | self.build_url() 18 | } 19 | 20 | fn connect(&self) -> impl Future> + Send + '_ 21 | where 22 | Self::Connection: Sized, 23 | { 24 | PgConnection::establish(self) 25 | } 26 | 27 | fn log_statements(mut self, level: LevelFilter) -> Self { 28 | self.log_settings.log_statements(level); 29 | self 30 | } 31 | 32 | fn log_slow_statements(mut self, level: LevelFilter, duration: Duration) -> Self { 33 | self.log_settings.log_slow_statements(level, duration); 34 | self 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/postgres/multi-tenant/accounts/migrations/01_setup.sql: -------------------------------------------------------------------------------- 1 | -- We try to ensure every table has `created_at` and `updated_at` columns, which can help immensely with debugging 2 | -- and auditing. 3 | -- 4 | -- While `created_at` can just be `default now()`, setting `updated_at` on update requires a trigger which 5 | -- is a lot of boilerplate. These two functions save us from writing that every time as instead we can just do 6 | -- 7 | -- select accounts.trigger_updated_at('
'); 8 | -- 9 | -- after a `CREATE TABLE`. 10 | create or replace function accounts.set_updated_at() 11 | returns trigger as 12 | $$ 13 | begin 14 | NEW.updated_at = now(); 15 | return NEW; 16 | end; 17 | $$ language plpgsql; 18 | 19 | create or replace function accounts.trigger_updated_at(tablename regclass) 20 | returns void as 21 | $$ 22 | begin 23 | execute format('CREATE TRIGGER set_updated_at 24 | BEFORE UPDATE 25 | ON %s 26 | FOR EACH ROW 27 | WHEN (OLD is distinct from NEW) 28 | EXECUTE FUNCTION accounts.set_updated_at();', tablename); 29 | end; 30 | $$ language plpgsql; 31 | -------------------------------------------------------------------------------- /sqlx-postgres/src/database.rs: -------------------------------------------------------------------------------- 1 | use crate::arguments::PgArgumentBuffer; 2 | use crate::value::{PgValue, PgValueRef}; 3 | use crate::{ 4 | PgArguments, PgColumn, PgConnection, PgQueryResult, PgRow, PgStatement, PgTransactionManager, 5 | PgTypeInfo, 6 | }; 7 | 8 | pub(crate) use sqlx_core::database::{Database, HasStatementCache}; 9 | 10 | /// PostgreSQL database driver. 11 | #[derive(Debug)] 12 | pub struct Postgres; 13 | 14 | impl Database for Postgres { 15 | type Connection = PgConnection; 16 | 17 | type TransactionManager = PgTransactionManager; 18 | 19 | type Row = PgRow; 20 | 21 | type QueryResult = PgQueryResult; 22 | 23 | type Column = PgColumn; 24 | 25 | type TypeInfo = PgTypeInfo; 26 | 27 | type Value = PgValue; 28 | type ValueRef<'r> = PgValueRef<'r>; 29 | 30 | type Arguments = PgArguments; 31 | type ArgumentBuffer = PgArgumentBuffer; 32 | 33 | type Statement = PgStatement; 34 | 35 | const NAME: &'static str = "PostgreSQL"; 36 | 37 | const URL_SCHEMES: &'static [&'static str] = &["postgres", "postgresql"]; 38 | } 39 | 40 | impl HasStatementCache for Postgres {} 41 | -------------------------------------------------------------------------------- /tests/sqlite/migrations_simple/20220721115524_convert_type.sql: -------------------------------------------------------------------------------- 1 | -- Perform a tricky conversion of the payload. 2 | -- 3 | -- This script will only succeed once and will fail if executed twice. 4 | 5 | -- set up temporary target column 6 | ALTER TABLE migrations_simple_test 7 | ADD some_payload_tmp TEXT; 8 | 9 | -- perform conversion 10 | -- This will fail if `some_payload` is already a string column due to the addition. 11 | -- We add a suffix after the addition to ensure that the SQL database does not silently cast the string back to an 12 | -- integer. 13 | UPDATE migrations_simple_test 14 | SET some_payload_tmp = CAST((some_payload + 10) AS TEXT) || '_suffix'; 15 | 16 | -- remove original column including the content 17 | ALTER TABLE migrations_simple_test 18 | DROP COLUMN some_payload; 19 | 20 | -- prepare new payload column (nullable, so we can copy over the data) 21 | ALTER TABLE migrations_simple_test 22 | ADD some_payload TEXT; 23 | 24 | -- copy new values 25 | UPDATE migrations_simple_test 26 | SET some_payload = some_payload_tmp; 27 | 28 | -- clean up 29 | ALTER TABLE migrations_simple_test 30 | DROP COLUMN some_payload_tmp; 31 | -------------------------------------------------------------------------------- /sqlx-postgres/src/types/uuid.rs: -------------------------------------------------------------------------------- 1 | use uuid::Uuid; 2 | 3 | use crate::decode::Decode; 4 | use crate::encode::{Encode, IsNull}; 5 | use crate::error::BoxDynError; 6 | use crate::types::Type; 7 | use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; 8 | 9 | impl Type for Uuid { 10 | fn type_info() -> PgTypeInfo { 11 | PgTypeInfo::UUID 12 | } 13 | } 14 | 15 | impl PgHasArrayType for Uuid { 16 | fn array_type_info() -> PgTypeInfo { 17 | PgTypeInfo::UUID_ARRAY 18 | } 19 | } 20 | 21 | impl Encode<'_, Postgres> for Uuid { 22 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { 23 | buf.extend_from_slice(self.as_bytes()); 24 | 25 | Ok(IsNull::No) 26 | } 27 | } 28 | 29 | impl Decode<'_, Postgres> for Uuid { 30 | fn decode(value: PgValueRef<'_>) -> Result { 31 | match value.format() { 32 | PgValueFormat::Binary => Uuid::from_slice(value.as_bytes()?), 33 | PgValueFormat::Text => value.as_str()?.parse(), 34 | } 35 | .map_err(Into::into) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sqlx-sqlite/src/options/auto_vacuum.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use std::str::FromStr; 3 | 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 5 | pub enum SqliteAutoVacuum { 6 | #[default] 7 | None, 8 | Full, 9 | Incremental, 10 | } 11 | 12 | impl SqliteAutoVacuum { 13 | pub(crate) fn as_str(&self) -> &'static str { 14 | match self { 15 | SqliteAutoVacuum::None => "NONE", 16 | SqliteAutoVacuum::Full => "FULL", 17 | SqliteAutoVacuum::Incremental => "INCREMENTAL", 18 | } 19 | } 20 | } 21 | 22 | impl FromStr for SqliteAutoVacuum { 23 | type Err = Error; 24 | 25 | fn from_str(s: &str) -> Result { 26 | Ok(match &*s.to_ascii_lowercase() { 27 | "none" => SqliteAutoVacuum::None, 28 | "full" => SqliteAutoVacuum::Full, 29 | "incremental" => SqliteAutoVacuum::Incremental, 30 | 31 | _ => { 32 | return Err(Error::Configuration( 33 | format!("unknown value {s:?} for `auto_vacuum`").into(), 34 | )); 35 | } 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/response/eof.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | 3 | use crate::error::Error; 4 | use crate::io::ProtocolDecode; 5 | use crate::protocol::response::Status; 6 | use crate::protocol::Capabilities; 7 | 8 | /// Marks the end of a result set, returning status and warnings. 9 | /// 10 | /// # Note 11 | /// 12 | /// The EOF packet is deprecated as of MySQL 5.7.5. SQLx only uses this packet for MySQL 13 | /// prior MySQL versions. 14 | #[derive(Debug)] 15 | pub struct EofPacket { 16 | #[allow(dead_code)] 17 | pub warnings: u16, 18 | pub status: Status, 19 | } 20 | 21 | impl ProtocolDecode<'_, Capabilities> for EofPacket { 22 | fn decode_with(mut buf: Bytes, _: Capabilities) -> Result { 23 | let header = buf.get_u8(); 24 | if header != 0xfe { 25 | return Err(err_protocol!( 26 | "expected 0xfe (EOF_Packet) but found 0x{:x}", 27 | header 28 | )); 29 | } 30 | 31 | let warnings = buf.get_u16_le(); 32 | let status = Status::from_bits_truncate(buf.get_u16_le()); 33 | 34 | Ok(Self { status, warnings }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 LaunchBadge, LLC 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /sqlx-sqlite/src/database.rs: -------------------------------------------------------------------------------- 1 | pub(crate) use sqlx_core::database::{Database, HasStatementCache}; 2 | 3 | use crate::arguments::SqliteArgumentsBuffer; 4 | use crate::{ 5 | SqliteArguments, SqliteColumn, SqliteConnection, SqliteQueryResult, SqliteRow, SqliteStatement, 6 | SqliteTransactionManager, SqliteTypeInfo, SqliteValue, SqliteValueRef, 7 | }; 8 | 9 | /// Sqlite database driver. 10 | #[derive(Debug)] 11 | pub struct Sqlite; 12 | 13 | impl Database for Sqlite { 14 | type Connection = SqliteConnection; 15 | 16 | type TransactionManager = SqliteTransactionManager; 17 | 18 | type Row = SqliteRow; 19 | 20 | type QueryResult = SqliteQueryResult; 21 | 22 | type Column = SqliteColumn; 23 | 24 | type TypeInfo = SqliteTypeInfo; 25 | 26 | type Value = SqliteValue; 27 | type ValueRef<'r> = SqliteValueRef<'r>; 28 | 29 | type Arguments = SqliteArguments; 30 | type ArgumentBuffer = SqliteArgumentsBuffer; 31 | 32 | type Statement = SqliteStatement; 33 | 34 | const NAME: &'static str = "SQLite"; 35 | 36 | const URL_SCHEMES: &'static [&'static str] = &["sqlite"]; 37 | } 38 | 39 | impl HasStatementCache for Sqlite {} 40 | -------------------------------------------------------------------------------- /sqlx-sqlite/src/transaction.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use sqlx_core::transaction::TransactionManager; 4 | use sqlx_core::{error::Error, sql_str::SqlStr}; 5 | 6 | use crate::{Sqlite, SqliteConnection}; 7 | 8 | /// Implementation of [`TransactionManager`] for SQLite. 9 | pub struct SqliteTransactionManager; 10 | 11 | impl TransactionManager for SqliteTransactionManager { 12 | type Database = Sqlite; 13 | 14 | async fn begin(conn: &mut SqliteConnection, statement: Option) -> Result<(), Error> { 15 | conn.worker.begin(statement).await 16 | } 17 | 18 | fn commit(conn: &mut SqliteConnection) -> impl Future> + Send + '_ { 19 | conn.worker.commit() 20 | } 21 | 22 | fn rollback( 23 | conn: &mut SqliteConnection, 24 | ) -> impl Future> + Send + '_ { 25 | conn.worker.rollback() 26 | } 27 | 28 | fn start_rollback(conn: &mut SqliteConnection) { 29 | conn.worker.start_rollback().ok(); 30 | } 31 | 32 | fn get_transaction_depth(conn: &SqliteConnection) -> usize { 33 | conn.worker.shared.get_transaction_depth() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sqlx-core/src/any/transaction.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use crate::any::{Any, AnyConnection}; 4 | use crate::database::Database; 5 | use crate::error::Error; 6 | use crate::sql_str::SqlStr; 7 | use crate::transaction::TransactionManager; 8 | 9 | pub struct AnyTransactionManager; 10 | 11 | impl TransactionManager for AnyTransactionManager { 12 | type Database = Any; 13 | 14 | fn begin( 15 | conn: &mut AnyConnection, 16 | statement: Option, 17 | ) -> impl Future> + Send + '_ { 18 | conn.backend.begin(statement) 19 | } 20 | 21 | fn commit(conn: &mut AnyConnection) -> impl Future> + Send + '_ { 22 | conn.backend.commit() 23 | } 24 | 25 | fn rollback(conn: &mut AnyConnection) -> impl Future> + Send + '_ { 26 | conn.backend.rollback() 27 | } 28 | 29 | fn start_rollback(conn: &mut AnyConnection) { 30 | conn.backend.start_rollback() 31 | } 32 | 33 | fn get_transaction_depth(conn: &::Connection) -> usize { 34 | conn.backend.get_transaction_depth() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sqlx-sqlite/src/types/text.rs: -------------------------------------------------------------------------------- 1 | use crate::arguments::SqliteArgumentsBuffer; 2 | use crate::{Sqlite, SqliteTypeInfo, SqliteValueRef}; 3 | use sqlx_core::decode::Decode; 4 | use sqlx_core::encode::{Encode, IsNull}; 5 | use sqlx_core::error::BoxDynError; 6 | use sqlx_core::types::{Text, Type}; 7 | use std::fmt::Display; 8 | use std::str::FromStr; 9 | 10 | impl Type for Text { 11 | fn type_info() -> SqliteTypeInfo { 12 | >::type_info() 13 | } 14 | 15 | fn compatible(ty: &SqliteTypeInfo) -> bool { 16 | >::compatible(ty) 17 | } 18 | } 19 | 20 | impl Encode<'_, Sqlite> for Text 21 | where 22 | T: Display, 23 | { 24 | fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { 25 | Encode::::encode(self.0.to_string(), buf) 26 | } 27 | } 28 | 29 | impl<'r, T> Decode<'r, Sqlite> for Text 30 | where 31 | T: FromStr, 32 | BoxDynError: From<::Err>, 33 | { 34 | fn decode(value: SqliteValueRef<'r>) -> Result { 35 | Ok(Self(value.with_temp_text(|text| text.parse::())??)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sqlx-mysql/src/query_result.rs: -------------------------------------------------------------------------------- 1 | use std::iter::{Extend, IntoIterator}; 2 | 3 | #[derive(Debug, Default)] 4 | pub struct MySqlQueryResult { 5 | pub(super) rows_affected: u64, 6 | pub(super) last_insert_id: u64, 7 | } 8 | 9 | impl MySqlQueryResult { 10 | pub fn last_insert_id(&self) -> u64 { 11 | self.last_insert_id 12 | } 13 | 14 | pub fn rows_affected(&self) -> u64 { 15 | self.rows_affected 16 | } 17 | } 18 | 19 | impl Extend for MySqlQueryResult { 20 | fn extend>(&mut self, iter: T) { 21 | for elem in iter { 22 | self.rows_affected += elem.rows_affected; 23 | self.last_insert_id = elem.last_insert_id; 24 | } 25 | } 26 | } 27 | #[cfg(feature = "any")] 28 | /// This conversion attempts to save last_insert_id by converting to i64. 29 | impl From for sqlx_core::any::AnyQueryResult { 30 | fn from(done: MySqlQueryResult) -> Self { 31 | sqlx_core::any::AnyQueryResult { 32 | rows_affected: done.rows_affected(), 33 | last_insert_id: done.last_insert_id().try_into().ok(), 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/mysql/setup.sql: -------------------------------------------------------------------------------- 1 | -- https://github.com/prisma/database-schema-examples/tree/master/postgres/basic-twitter#basic-twitter 2 | CREATE TABLE tweet 3 | ( 4 | id BIGINT PRIMARY KEY AUTO_INCREMENT, 5 | created_at TIMESTAMP NOT NULL DEFAULT NOW(), 6 | text TEXT NOT NULL, 7 | owner_id BIGINT 8 | ); 9 | 10 | CREATE TABLE tweet_reply 11 | ( 12 | id BIGINT PRIMARY KEY AUTO_INCREMENT, 13 | tweet_id BIGINT NOT NULL, 14 | created_at TIMESTAMP NOT NULL DEFAULT NOW(), 15 | text TEXT NOT NULL, 16 | owner_id BIGINT, 17 | CONSTRAINT tweet_id_fk FOREIGN KEY (tweet_id) REFERENCES tweet(id) 18 | ); 19 | 20 | CREATE TABLE products ( 21 | product_no INTEGER, 22 | name TEXT, 23 | price NUMERIC CHECK (price > 0) 24 | ); 25 | 26 | -- Create a user without a password to test passwordless auth. 27 | CREATE USER 'no_password'@'%'; 28 | 29 | -- The minimum privilege apparently needed to connect to a specific database. 30 | -- Granting no privileges, or just `GRANT USAGE`, gives an "access denied" error. 31 | -- https://github.com/launchbadge/sqlx/issues/3484#issuecomment-2350901546 32 | GRANT SELECT ON sqlx.* TO 'no_password'@'%'; 33 | -------------------------------------------------------------------------------- /tests/ui/mysql/gated/chrono.stderr: -------------------------------------------------------------------------------- 1 | error: optional sqlx feature `chrono` required for type DATE of column #1 ("date") 2 | --> $DIR/chrono.rs:2:13 3 | | 4 | 2 | let _ = sqlx::query!("select CONVERT(now(), DATE) date"); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: optional sqlx feature `chrono` required for type TIME of column #1 ("time") 10 | --> $DIR/chrono.rs:4:13 11 | | 12 | 4 | let _ = sqlx::query!("select CONVERT(now(), TIME) time"); 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 14 | | 15 | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) 16 | 17 | error: optional sqlx feature `chrono` required for type DATETIME of column #1 ("datetime") 18 | --> $DIR/chrono.rs:6:13 19 | | 20 | 6 | let _ = sqlx::query!("select CONVERT(now(), DATETIME) datetime"); 21 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 22 | | 23 | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) 24 | -------------------------------------------------------------------------------- /sqlx-postgres/src/types/text.rs: -------------------------------------------------------------------------------- 1 | use crate::{PgArgumentBuffer, PgTypeInfo, PgValueRef, Postgres}; 2 | use sqlx_core::decode::Decode; 3 | use sqlx_core::encode::{Encode, IsNull}; 4 | use sqlx_core::error::BoxDynError; 5 | use sqlx_core::types::{Text, Type}; 6 | use std::fmt::Display; 7 | use std::str::FromStr; 8 | 9 | use std::io::Write; 10 | 11 | impl Type for Text { 12 | fn type_info() -> PgTypeInfo { 13 | >::type_info() 14 | } 15 | 16 | fn compatible(ty: &PgTypeInfo) -> bool { 17 | >::compatible(ty) 18 | } 19 | } 20 | 21 | impl Encode<'_, Postgres> for Text 22 | where 23 | T: Display, 24 | { 25 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { 26 | write!(**buf, "{}", self.0)?; 27 | Ok(IsNull::No) 28 | } 29 | } 30 | 31 | impl<'r, T> Decode<'r, Postgres> for Text 32 | where 33 | T: FromStr, 34 | BoxDynError: From<::Err>, 35 | { 36 | fn decode(value: PgValueRef<'r>) -> Result { 37 | let s: &str = Decode::::decode(value)?; 38 | Ok(Self(s.parse()?)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/sqlite/setup.sql: -------------------------------------------------------------------------------- 1 | -- https://github.com/prisma/database-schema-examples/tree/master/postgres/basic-twitter#basic-twitter 2 | CREATE TABLE tweet ( 3 | id BIGINT NOT NULL PRIMARY KEY, 4 | text TEXT NOT NULL, 5 | is_sent BOOLEAN NOT NULL DEFAULT TRUE, 6 | owner_id BIGINT 7 | ); 8 | INSERT INTO tweet(id, text, owner_id) 9 | VALUES (1, '#sqlx is pretty cool!', 1); 10 | -- 11 | CREATE TABLE tweet_reply ( 12 | id BIGINT NOT NULL PRIMARY KEY, 13 | tweet_id BIGINT NOT NULL, 14 | text TEXT NOT NULL, 15 | owner_id BIGINT, 16 | CONSTRAINT tweet_id_fk FOREIGN KEY (tweet_id) REFERENCES tweet(id) 17 | ); 18 | INSERT INTO tweet_reply(id, tweet_id, text, owner_id) 19 | VALUES (1, 1, 'Yeah! #sqlx is indeed pretty cool!', 1); 20 | -- 21 | CREATE TABLE accounts ( 22 | id INTEGER NOT NULL PRIMARY KEY, 23 | name TEXT NOT NULL, 24 | is_active BOOLEAN 25 | ); 26 | INSERT INTO accounts(id, name, is_active) 27 | VALUES (1, 'Herp Derpinson', 1); 28 | CREATE VIEW accounts_view as 29 | SELECT * 30 | FROM accounts; 31 | -- 32 | CREATE TABLE products ( 33 | product_no INTEGER, 34 | name TEXT, 35 | price NUMERIC, 36 | CONSTRAINT price_greater_than_zero CHECK (price > 0) 37 | ); 38 | -------------------------------------------------------------------------------- /sqlx-sqlite/src/query_result.rs: -------------------------------------------------------------------------------- 1 | use std::iter::{Extend, IntoIterator}; 2 | 3 | #[derive(Debug, Default)] 4 | pub struct SqliteQueryResult { 5 | pub(super) changes: u64, 6 | pub(super) last_insert_rowid: i64, 7 | } 8 | 9 | impl SqliteQueryResult { 10 | pub fn rows_affected(&self) -> u64 { 11 | self.changes 12 | } 13 | 14 | pub fn last_insert_rowid(&self) -> i64 { 15 | self.last_insert_rowid 16 | } 17 | } 18 | 19 | impl Extend for SqliteQueryResult { 20 | fn extend>(&mut self, iter: T) { 21 | for elem in iter { 22 | self.changes += elem.changes; 23 | self.last_insert_rowid = elem.last_insert_rowid; 24 | } 25 | } 26 | } 27 | 28 | #[cfg(feature = "any")] 29 | impl From for sqlx_core::any::AnyQueryResult { 30 | fn from(done: SqliteQueryResult) -> Self { 31 | let last_insert_id = match done.last_insert_rowid() { 32 | 0 => None, 33 | n => Some(n), 34 | }; 35 | sqlx_core::any::AnyQueryResult { 36 | rows_affected: done.rows_affected(), 37 | last_insert_id, 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /sqlx-sqlite/src/types/json.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::arguments::SqliteArgumentsBuffer; 4 | use crate::decode::Decode; 5 | use crate::encode::{Encode, IsNull}; 6 | use crate::error::BoxDynError; 7 | use crate::types::{Json, Type}; 8 | use crate::{type_info::DataType, Sqlite, SqliteTypeInfo, SqliteValueRef}; 9 | 10 | impl Type for Json { 11 | fn type_info() -> SqliteTypeInfo { 12 | SqliteTypeInfo(DataType::Text) 13 | } 14 | 15 | fn compatible(ty: &SqliteTypeInfo) -> bool { 16 | <&str as Type>::compatible(ty) 17 | } 18 | } 19 | 20 | impl Encode<'_, Sqlite> for Json 21 | where 22 | T: Serialize, 23 | { 24 | fn encode_by_ref(&self, buf: &mut SqliteArgumentsBuffer) -> Result { 25 | Encode::::encode(self.encode_to_string()?, buf) 26 | } 27 | } 28 | 29 | impl<'r, T> Decode<'r, Sqlite> for Json 30 | where 31 | T: 'r + Deserialize<'r>, 32 | { 33 | fn decode(value: SqliteValueRef<'r>) -> Result { 34 | // Saves a pass over the data by making `serde_json` check UTF-8. 35 | Self::decode_from_bytes(Decode::::decode(value)?) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: I have a feature request for SQLx 2 | description: Create a feature-request issue 3 | labels: [enhancement] 4 | body: 5 | - type: textarea 6 | id: related-issues 7 | validations: 8 | required: true 9 | attributes: 10 | label: I have found these related issues/pull requests 11 | description: "Provide context for your pull request." 12 | placeholder: | 13 | Closes \#... 14 | Relates to \#... 15 | - type: textarea 16 | id: feature-description 17 | validations: 18 | required: true 19 | attributes: 20 | label: Description 21 | description: A clear and concise description of what the problem is 22 | placeholder: You should add ... 23 | - type: textarea 24 | id: solution 25 | validations: 26 | required: true 27 | attributes: 28 | label: Prefered solution 29 | description: A clear and concise description of what you want to happen. 30 | placeholder: In my use-case, ... 31 | - type: textarea 32 | id: breaking-change 33 | validations: 34 | required: true 35 | attributes: 36 | label: Is this a breaking change? Why or why not? 37 | 38 | -------------------------------------------------------------------------------- /sqlx-sqlite/src/options/locking_mode.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use std::str::FromStr; 3 | 4 | /// Refer to [SQLite documentation] for the meaning of the connection locking mode. 5 | /// 6 | /// [SQLite documentation]: https://www.sqlite.org/pragma.html#pragma_locking_mode 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 8 | pub enum SqliteLockingMode { 9 | #[default] 10 | Normal, 11 | Exclusive, 12 | } 13 | 14 | impl SqliteLockingMode { 15 | pub(crate) fn as_str(&self) -> &'static str { 16 | match self { 17 | SqliteLockingMode::Normal => "NORMAL", 18 | SqliteLockingMode::Exclusive => "EXCLUSIVE", 19 | } 20 | } 21 | } 22 | 23 | impl FromStr for SqliteLockingMode { 24 | type Err = Error; 25 | 26 | fn from_str(s: &str) -> Result { 27 | Ok(match &*s.to_ascii_lowercase() { 28 | "normal" => SqliteLockingMode::Normal, 29 | "exclusive" => SqliteLockingMode::Exclusive, 30 | 31 | _ => { 32 | return Err(Error::Configuration( 33 | format!("unknown value {s:?} for `locking_mode`").into(), 34 | )); 35 | } 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /sqlx-core/src/any/database.rs: -------------------------------------------------------------------------------- 1 | use crate::any::{ 2 | AnyArgumentBuffer, AnyArguments, AnyColumn, AnyConnection, AnyQueryResult, AnyRow, 3 | AnyStatement, AnyTransactionManager, AnyTypeInfo, AnyValue, AnyValueRef, 4 | }; 5 | use crate::database::{Database, HasStatementCache}; 6 | 7 | /// Opaque database driver. Capable of being used in place of any SQLx database driver. The actual 8 | /// driver used will be selected at runtime, from the connection url. 9 | #[derive(Debug)] 10 | pub struct Any; 11 | 12 | impl Database for Any { 13 | type Connection = AnyConnection; 14 | 15 | type TransactionManager = AnyTransactionManager; 16 | 17 | type Row = AnyRow; 18 | 19 | type QueryResult = AnyQueryResult; 20 | 21 | type Column = AnyColumn; 22 | 23 | type TypeInfo = AnyTypeInfo; 24 | 25 | type Value = AnyValue; 26 | type ValueRef<'r> = AnyValueRef<'r>; 27 | 28 | type Arguments = AnyArguments; 29 | type ArgumentBuffer = AnyArgumentBuffer; 30 | 31 | type Statement = AnyStatement; 32 | 33 | const NAME: &'static str = "Any"; 34 | 35 | const URL_SCHEMES: &'static [&'static str] = &[]; 36 | } 37 | 38 | // This _may_ be true, depending on the selected database 39 | impl HasStatementCache for Any {} 40 | -------------------------------------------------------------------------------- /tests/mysql/migrations_simple/20220721115524_convert_type.sql: -------------------------------------------------------------------------------- 1 | -- Perform a tricky conversion of the payload. 2 | -- 3 | -- This script will only succeed once and will fail if executed twice. 4 | 5 | -- set up temporary target column 6 | ALTER TABLE migrations_simple_test 7 | ADD some_payload_tmp TEXT; 8 | 9 | -- perform conversion 10 | -- This will fail if `some_payload` is already a string column due to the addition. 11 | -- We add a suffix after the addition to ensure that the SQL database does not silently cast the string back to an 12 | -- integer. 13 | UPDATE migrations_simple_test 14 | SET some_payload_tmp = CONCAT(CAST((some_payload + 10) AS CHAR(3)), '_suffix'); 15 | 16 | -- remove original column including the content 17 | ALTER TABLE migrations_simple_test 18 | DROP COLUMN some_payload; 19 | 20 | -- prepare new payload column (nullable, so we can copy over the data) 21 | ALTER TABLE migrations_simple_test 22 | ADD some_payload TEXT; 23 | 24 | -- copy new values 25 | UPDATE migrations_simple_test 26 | SET some_payload = some_payload_tmp; 27 | 28 | -- "freeze" column 29 | ALTER TABLE migrations_simple_test 30 | MODIFY some_payload TEXT NOT NULL; 31 | 32 | -- clean up 33 | ALTER TABLE migrations_simple_test 34 | DROP COLUMN some_payload_tmp; 35 | -------------------------------------------------------------------------------- /tests/migrate/migrations_simple/20220721115524_convert_type.sql: -------------------------------------------------------------------------------- 1 | -- Perform a tricky conversion of the payload. 2 | -- 3 | -- This script will only succeed once and will fail if executed twice. 4 | 5 | -- set up temporary target column 6 | ALTER TABLE migrations_simple_test 7 | ADD some_payload_tmp TEXT; 8 | 9 | -- perform conversion 10 | -- This will fail if `some_payload` is already a string column due to the addition. 11 | -- We add a suffix after the addition to ensure that the SQL database does not silently cast the string back to an 12 | -- integer. 13 | UPDATE migrations_simple_test 14 | SET some_payload_tmp = CONCAT(CAST((some_payload + 10) AS TEXT), '_suffix'); 15 | 16 | -- remove original column including the content 17 | ALTER TABLE migrations_simple_test 18 | DROP COLUMN some_payload; 19 | 20 | -- prepare new payload column (nullable, so we can copy over the data) 21 | ALTER TABLE migrations_simple_test 22 | ADD some_payload TEXT; 23 | 24 | -- copy new values 25 | UPDATE migrations_simple_test 26 | SET some_payload = some_payload_tmp; 27 | 28 | -- "freeze" column 29 | ALTER TABLE migrations_simple_test 30 | ALTER COLUMN some_payload SET NOT NULL; 31 | 32 | -- clean up 33 | ALTER TABLE migrations_simple_test 34 | DROP COLUMN some_payload_tmp; 35 | -------------------------------------------------------------------------------- /tests/postgres/migrations_simple/20220721115524_convert_type.sql: -------------------------------------------------------------------------------- 1 | -- Perform a tricky conversion of the payload. 2 | -- 3 | -- This script will only succeed once and will fail if executed twice. 4 | 5 | -- set up temporary target column 6 | ALTER TABLE migrations_simple_test 7 | ADD some_payload_tmp TEXT; 8 | 9 | -- perform conversion 10 | -- This will fail if `some_payload` is already a string column due to the addition. 11 | -- We add a suffix after the addition to ensure that the SQL database does not silently cast the string back to an 12 | -- integer. 13 | UPDATE migrations_simple_test 14 | SET some_payload_tmp = CONCAT(CAST((some_payload + 10) AS TEXT), '_suffix'); 15 | 16 | -- remove original column including the content 17 | ALTER TABLE migrations_simple_test 18 | DROP COLUMN some_payload; 19 | 20 | -- prepare new payload column (nullable, so we can copy over the data) 21 | ALTER TABLE migrations_simple_test 22 | ADD some_payload TEXT; 23 | 24 | -- copy new values 25 | UPDATE migrations_simple_test 26 | SET some_payload = some_payload_tmp; 27 | 28 | -- "freeze" column 29 | ALTER TABLE migrations_simple_test 30 | ALTER COLUMN some_payload SET NOT NULL; 31 | 32 | -- clean up 33 | ALTER TABLE migrations_simple_test 34 | DROP COLUMN some_payload_tmp; 35 | -------------------------------------------------------------------------------- /tests/mssql/types.rs: -------------------------------------------------------------------------------- 1 | use sqlx::mssql::Mssql; 2 | use sqlx_test::test_type; 3 | 4 | test_type!(null>(Mssql, 5 | "CAST(NULL as INT)" == None:: 6 | )); 7 | 8 | test_type!(u8( 9 | Mssql, 10 | "CAST(5 AS TINYINT)" == 5_u8, 11 | "CAST(0 AS TINYINT)" == 0_u8, 12 | "CAST(255 AS TINYINT)" == 255_u8, 13 | )); 14 | 15 | test_type!(i8( 16 | Mssql, 17 | "CAST(5 AS TINYINT)" == 5_i8, 18 | "CAST(0 AS TINYINT)" == 0_i8 19 | )); 20 | 21 | test_type!(i16(Mssql, "CAST(21415 AS SMALLINT)" == 21415_i16)); 22 | 23 | test_type!(i32(Mssql, "CAST(2141512 AS INT)" == 2141512_i32)); 24 | 25 | test_type!(i64(Mssql, "CAST(32324324432 AS BIGINT)" == 32324324432_i64)); 26 | 27 | test_type!(f32( 28 | Mssql, 29 | "CAST(3.1410000324249268 AS REAL)" == 3.141f32 as f64 as f32 30 | )); 31 | 32 | test_type!(f64( 33 | Mssql, 34 | "CAST(939399419.1225182 AS FLOAT)" == 939399419.1225182_f64 35 | )); 36 | 37 | test_type!(str_nvarchar(Mssql, 38 | "CAST('this is foo' as NVARCHAR)" == "this is foo", 39 | )); 40 | 41 | test_type!(str(Mssql, 42 | "'this is foo'" == "this is foo", 43 | "''" == "", 44 | )); 45 | 46 | test_type!(bool( 47 | Mssql, 48 | "CAST(1 as BIT)" == true, 49 | "CAST(0 as BIT)" == false 50 | )); 51 | -------------------------------------------------------------------------------- /sqlx-postgres/src/types/bigdecimal-range.md: -------------------------------------------------------------------------------- 1 | #### Note: `BigDecimal` Has a Larger Range than `NUMERIC` 2 | `BigDecimal` can represent values with a far, far greater range than the `NUMERIC` type in Postgres can. 3 | 4 | `NUMERIC` is limited to 131,072 digits before the decimal point, and 16,384 digits after it. 5 | See [Section 8.1, Numeric Types] of the Postgres manual for details. 6 | 7 | Meanwhile, `BigDecimal` can theoretically represent a value with an arbitrary number of decimal digits, albeit 8 | with a maximum of 263 significant figures. 9 | 10 | Because encoding in the current API design _must_ be infallible, 11 | when attempting to encode a `BigDecimal` that cannot fit in the wire representation of `NUMERIC`, 12 | SQLx may instead encode a sentinel value that falls outside the allowed range but is still representable. 13 | 14 | This will cause the query to return a `DatabaseError` with code `22P03` (`invalid_binary_representation`) 15 | and the error message `invalid scale in external "numeric" value` (though this may be subject to change). 16 | 17 | However, `BigDecimal` should be able to decode any `NUMERIC` value except `NaN`, 18 | for which it has no representation. 19 | 20 | [Section 8.1, Numeric Types]: https://www.postgresql.org/docs/current/datatype-numeric.html 21 | -------------------------------------------------------------------------------- /sqlx-postgres/src/message/notification.rs: -------------------------------------------------------------------------------- 1 | use sqlx_core::bytes::{Buf, Bytes}; 2 | 3 | use crate::error::Error; 4 | use crate::io::BufExt; 5 | use crate::message::{BackendMessage, BackendMessageFormat}; 6 | 7 | #[derive(Debug)] 8 | pub struct Notification { 9 | pub(crate) process_id: u32, 10 | pub(crate) channel: Bytes, 11 | pub(crate) payload: Bytes, 12 | } 13 | 14 | impl BackendMessage for Notification { 15 | const FORMAT: BackendMessageFormat = BackendMessageFormat::NotificationResponse; 16 | 17 | fn decode_body(mut buf: Bytes) -> Result { 18 | let process_id = buf.get_u32(); 19 | let channel = buf.get_bytes_nul()?; 20 | let payload = buf.get_bytes_nul()?; 21 | 22 | Ok(Self { 23 | process_id, 24 | channel, 25 | payload, 26 | }) 27 | } 28 | } 29 | 30 | #[test] 31 | fn test_decode_notification_response() { 32 | const NOTIFICATION_RESPONSE: &[u8] = b"\x34\x20\x10\x02TEST-CHANNEL\0THIS IS A TEST\0"; 33 | 34 | let message = Notification::decode_body(Bytes::from(NOTIFICATION_RESPONSE)).unwrap(); 35 | 36 | assert_eq!(message.process_id, 0x34201002); 37 | assert_eq!(&*message.channel, &b"TEST-CHANNEL"[..]); 38 | assert_eq!(&*message.payload, &b"THIS IS A TEST"[..]); 39 | } 40 | -------------------------------------------------------------------------------- /sqlx-postgres/src/types/bool.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::Decode; 2 | use crate::encode::{Encode, IsNull}; 3 | use crate::error::BoxDynError; 4 | use crate::types::Type; 5 | use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; 6 | 7 | impl Type for bool { 8 | fn type_info() -> PgTypeInfo { 9 | PgTypeInfo::BOOL 10 | } 11 | } 12 | 13 | impl PgHasArrayType for bool { 14 | fn array_type_info() -> PgTypeInfo { 15 | PgTypeInfo::BOOL_ARRAY 16 | } 17 | } 18 | 19 | impl Encode<'_, Postgres> for bool { 20 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { 21 | buf.push(*self as u8); 22 | 23 | Ok(IsNull::No) 24 | } 25 | } 26 | 27 | impl Decode<'_, Postgres> for bool { 28 | fn decode(value: PgValueRef<'_>) -> Result { 29 | Ok(match value.format() { 30 | PgValueFormat::Binary => value.as_bytes()?[0] != 0, 31 | 32 | PgValueFormat::Text => match value.as_str()? { 33 | "t" => true, 34 | "f" => false, 35 | 36 | s => { 37 | return Err(format!("unexpected value {s:?} for boolean").into()); 38 | } 39 | }, 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/sqlite/extension/migrations/20250203094951_addresses.sql: -------------------------------------------------------------------------------- 1 | create table addresses (address text, family integer); 2 | 3 | -- The `ipfamily` function is provided by the 4 | -- [ipaddr](https://github.com/nalgeon/sqlean/blob/main/docs/ipaddr.md) 5 | -- sqlite extension, and so this migration can not run if that 6 | -- extension is not loaded. 7 | insert into addresses (address, family) values 8 | ('fd04:3d29:9f41::1', ipfamily('fd04:3d29:9f41::1')), 9 | ('10.0.0.1', ipfamily('10.0.0.1')), 10 | ('10.0.0.2', ipfamily('10.0.0.2')), 11 | ('fd04:3d29:9f41::2', ipfamily('fd04:3d29:9f41::2')), 12 | ('fd04:3d29:9f41::3', ipfamily('fd04:3d29:9f41::3')), 13 | ('10.0.0.3', ipfamily('10.0.0.3')), 14 | ('fd04:3d29:9f41::4', ipfamily('fd04:3d29:9f41::4')), 15 | ('fd04:3d29:9f41::5', ipfamily('fd04:3d29:9f41::5')), 16 | ('fd04:3d29:9f41::6', ipfamily('fd04:3d29:9f41::6')), 17 | ('10.0.0.4', ipfamily('10.0.0.4')), 18 | ('10.0.0.5', ipfamily('10.0.0.5')), 19 | ('10.0.0.6', ipfamily('10.0.0.6')), 20 | ('10.0.0.7', ipfamily('10.0.0.7')), 21 | ('fd04:3d29:9f41::7', ipfamily('fd04:3d29:9f41::7')), 22 | ('fd04:3d29:9f41::8', ipfamily('fd04:3d29:9f41::8')), 23 | ('10.0.0.8', ipfamily('10.0.0.8')), 24 | ('fd04:3d29:9f41::9', ipfamily('fd04:3d29:9f41::9')), 25 | ('10.0.0.9', ipfamily('10.0.0.9')); 26 | -------------------------------------------------------------------------------- /examples/postgres/mockable-todos/README.md: -------------------------------------------------------------------------------- 1 | # Mockable TODOs Example 2 | 3 | ## Description 4 | 5 | This example is based on the ideas in [this blog post](https://medium.com/better-programming/structuring-rust-project-for-testability-18207b5d0243). The value here is that the business logic can be unit tested independently from the database layer. Otherwise it is identical to the todos example. 6 | 7 | ## Setup 8 | 9 | 1. Run `docker-compose up -d` to run Postgres in the background. 10 | 11 | 2. Declare the database URL, either by exporting it: 12 | 13 | ``` 14 | export DATABASE_URL="postgres://postgres:password@localhost/todos" 15 | ``` 16 | 17 | or by making a `.env` file: 18 | 19 | ``` 20 | cp .env.example .env 21 | ``` 22 | 23 | 3. Create the database. 24 | 25 | ``` 26 | $ sqlx db create 27 | ``` 28 | 29 | 4. Run sql migrations 30 | 31 | ``` 32 | $ sqlx migrate run 33 | ``` 34 | 35 | ## Usage 36 | 37 | Add a todo 38 | 39 | ``` 40 | cargo run -- add "todo description" 41 | ``` 42 | 43 | Complete a todo. 44 | 45 | ``` 46 | cargo run -- done 47 | ``` 48 | 49 | List all todos 50 | 51 | ``` 52 | cargo run 53 | ``` 54 | 55 | ## Cleanup 56 | 57 | To destroy the Postgres database, run: 58 | 59 | ``` 60 | docker-compose down --volumes 61 | ``` 62 | -------------------------------------------------------------------------------- /examples/postgres/axum-social-with-tests/src/password.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Context}; 2 | use tokio::task; 3 | 4 | use argon2::password_hash::SaltString; 5 | use argon2::{password_hash, Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; 6 | 7 | pub async fn hash(password: String) -> anyhow::Result { 8 | task::spawn_blocking(move || { 9 | let salt = SaltString::generate(rand::thread_rng()); 10 | Ok(Argon2::default() 11 | .hash_password(password.as_bytes(), &salt) 12 | .map_err(|e| anyhow!(e).context("failed to hash password"))? 13 | .to_string()) 14 | }) 15 | .await 16 | .context("panic in hash()")? 17 | } 18 | 19 | pub async fn verify(password: String, hash: String) -> anyhow::Result { 20 | task::spawn_blocking(move || { 21 | let hash = PasswordHash::new(&hash) 22 | .map_err(|e| anyhow!(e).context("BUG: password hash invalid"))?; 23 | 24 | let res = Argon2::default().verify_password(password.as_bytes(), &hash); 25 | 26 | match res { 27 | Ok(()) => Ok(true), 28 | Err(password_hash::Error::Password) => Ok(false), 29 | Err(e) => Err(anyhow!(e).context("failed to verify password")), 30 | } 31 | }) 32 | .await 33 | .context("panic in verify()")? 34 | } 35 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/connect/ssl_request.rs: -------------------------------------------------------------------------------- 1 | use crate::io::ProtocolEncode; 2 | use crate::protocol::Capabilities; 3 | 4 | // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_connection_phase_packets_protocol_handshake_response.html 5 | // https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest 6 | 7 | #[derive(Debug)] 8 | pub struct SslRequest { 9 | pub max_packet_size: u32, 10 | pub charset: u8, 11 | } 12 | 13 | impl ProtocolEncode<'_, Capabilities> for SslRequest { 14 | fn encode_with(&self, buf: &mut Vec, context: Capabilities) -> Result<(), crate::Error> { 15 | // truncation is intended 16 | #[allow(clippy::cast_possible_truncation)] 17 | buf.extend(&(context.bits() as u32).to_le_bytes()); 18 | buf.extend(&self.max_packet_size.to_le_bytes()); 19 | buf.push(self.charset); 20 | 21 | // reserved: string<19> 22 | buf.extend(&[0_u8; 19]); 23 | 24 | if context.contains(Capabilities::MYSQL) { 25 | // reserved: string<4> 26 | buf.extend(&[0_u8; 4]); 27 | } else { 28 | // extended client capabilities (MariaDB-specified): int<4> 29 | buf.extend(&((context.bits() >> 32) as u32).to_le_bytes()); 30 | } 31 | 32 | Ok(()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sqlx-postgres/src/message/close.rs: -------------------------------------------------------------------------------- 1 | use crate::io::{PgBufMutExt, PortalId, StatementId}; 2 | use crate::message::{FrontendMessage, FrontendMessageFormat}; 3 | use std::num::Saturating; 4 | 5 | const CLOSE_PORTAL: u8 = b'P'; 6 | const CLOSE_STATEMENT: u8 = b'S'; 7 | 8 | #[derive(Debug)] 9 | #[allow(dead_code)] 10 | pub enum Close { 11 | Statement(StatementId), 12 | Portal(PortalId), 13 | } 14 | 15 | impl FrontendMessage for Close { 16 | const FORMAT: FrontendMessageFormat = FrontendMessageFormat::Close; 17 | 18 | fn body_size_hint(&self) -> Saturating { 19 | // Either `CLOSE_PORTAL` or `CLOSE_STATEMENT` 20 | let mut size = Saturating(1); 21 | 22 | match self { 23 | Close::Statement(id) => size += id.name_len(), 24 | Close::Portal(id) => size += id.name_len(), 25 | } 26 | 27 | size 28 | } 29 | 30 | fn encode_body(&self, buf: &mut Vec) -> Result<(), crate::Error> { 31 | match self { 32 | Close::Statement(id) => { 33 | buf.push(CLOSE_STATEMENT); 34 | buf.put_statement_name(*id); 35 | } 36 | 37 | Close::Portal(id) => { 38 | buf.push(CLOSE_PORTAL); 39 | buf.put_portal_name(*id); 40 | } 41 | } 42 | 43 | Ok(()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/auth.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use crate::error::Error; 4 | 5 | #[derive(Debug, Copy, Clone)] 6 | // These have all the same suffix but they match the auth plugin names. 7 | #[allow(clippy::enum_variant_names)] 8 | pub enum AuthPlugin { 9 | MySqlNativePassword, 10 | CachingSha2Password, 11 | Sha256Password, 12 | MySqlClearPassword, 13 | } 14 | 15 | impl AuthPlugin { 16 | pub(crate) fn name(self) -> &'static str { 17 | match self { 18 | AuthPlugin::MySqlNativePassword => "mysql_native_password", 19 | AuthPlugin::CachingSha2Password => "caching_sha2_password", 20 | AuthPlugin::Sha256Password => "sha256_password", 21 | AuthPlugin::MySqlClearPassword => "mysql_clear_password", 22 | } 23 | } 24 | } 25 | 26 | impl FromStr for AuthPlugin { 27 | type Err = Error; 28 | 29 | fn from_str(s: &str) -> Result { 30 | match s { 31 | "mysql_native_password" => Ok(AuthPlugin::MySqlNativePassword), 32 | "caching_sha2_password" => Ok(AuthPlugin::CachingSha2Password), 33 | "sha256_password" => Ok(AuthPlugin::Sha256Password), 34 | "mysql_clear_password" => Ok(AuthPlugin::MySqlClearPassword), 35 | 36 | _ => Err(err_protocol!("unknown authentication plugin: {}", s)), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sqlx-core/src/pool/maybe.rs: -------------------------------------------------------------------------------- 1 | use crate::database::Database; 2 | use crate::pool::PoolConnection; 3 | use std::ops::{Deref, DerefMut}; 4 | 5 | pub enum MaybePoolConnection<'c, DB: Database> { 6 | #[allow(dead_code)] 7 | Connection(&'c mut DB::Connection), 8 | PoolConnection(PoolConnection), 9 | } 10 | 11 | impl Deref for MaybePoolConnection<'_, DB> { 12 | type Target = DB::Connection; 13 | 14 | #[inline] 15 | fn deref(&self) -> &Self::Target { 16 | match self { 17 | MaybePoolConnection::Connection(v) => v, 18 | MaybePoolConnection::PoolConnection(v) => v, 19 | } 20 | } 21 | } 22 | 23 | impl DerefMut for MaybePoolConnection<'_, DB> { 24 | #[inline] 25 | fn deref_mut(&mut self) -> &mut Self::Target { 26 | match self { 27 | MaybePoolConnection::Connection(v) => v, 28 | MaybePoolConnection::PoolConnection(v) => v, 29 | } 30 | } 31 | } 32 | 33 | impl From> for MaybePoolConnection<'_, DB> { 34 | fn from(v: PoolConnection) -> Self { 35 | MaybePoolConnection::PoolConnection(v) 36 | } 37 | } 38 | 39 | impl<'c, DB: Database> From<&'c mut DB::Connection> for MaybePoolConnection<'c, DB> { 40 | fn from(v: &'c mut DB::Connection) -> Self { 41 | MaybePoolConnection::Connection(v) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/statement/execute.rs: -------------------------------------------------------------------------------- 1 | use crate::io::ProtocolEncode; 2 | use crate::protocol::text::ColumnFlags; 3 | use crate::protocol::Capabilities; 4 | use crate::MySqlArguments; 5 | 6 | // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_com_stmt_execute.html 7 | 8 | #[derive(Debug)] 9 | pub struct Execute<'q> { 10 | pub statement: u32, 11 | pub arguments: &'q MySqlArguments, 12 | } 13 | 14 | impl ProtocolEncode<'_, Capabilities> for Execute<'_> { 15 | fn encode_with(&self, buf: &mut Vec, _: Capabilities) -> Result<(), crate::Error> { 16 | buf.push(0x17); // COM_STMT_EXECUTE 17 | buf.extend(&self.statement.to_le_bytes()); 18 | buf.push(0); // NO_CURSOR 19 | buf.extend(&1_u32.to_le_bytes()); // iterations (always 1): int<4> 20 | 21 | if !self.arguments.types.is_empty() { 22 | buf.extend_from_slice(&self.arguments.null_bitmap); 23 | buf.push(1); // send type to server 24 | 25 | for ty in &self.arguments.types { 26 | buf.push(ty.r#type as u8); 27 | 28 | buf.push(if ty.flags.contains(ColumnFlags::UNSIGNED) { 29 | 0x80 30 | } else { 31 | 0 32 | }); 33 | } 34 | 35 | buf.extend(&*self.arguments.values); 36 | } 37 | 38 | Ok(()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/mssql/describe.rs: -------------------------------------------------------------------------------- 1 | use sqlx::mssql::Mssql; 2 | use sqlx::{Column, Executor, TypeInfo}; 3 | use sqlx_test::new; 4 | 5 | #[sqlx_macros::test] 6 | async fn it_describes_simple() -> anyhow::Result<()> { 7 | let mut conn = new::().await?; 8 | 9 | let d = conn.describe("SELECT * FROM tweet").await?; 10 | 11 | assert_eq!(d.columns()[0].name(), "id"); 12 | assert_eq!(d.columns()[1].name(), "text"); 13 | assert_eq!(d.columns()[2].name(), "is_sent"); 14 | assert_eq!(d.columns()[3].name(), "owner_id"); 15 | 16 | assert_eq!(d.nullable(0), Some(false)); 17 | assert_eq!(d.nullable(1), Some(false)); 18 | assert_eq!(d.nullable(2), Some(false)); 19 | assert_eq!(d.nullable(3), Some(true)); 20 | 21 | assert_eq!(d.columns()[0].type_info().name(), "BIGINT"); 22 | assert_eq!(d.columns()[1].type_info().name(), "NVARCHAR"); 23 | assert_eq!(d.columns()[2].type_info().name(), "TINYINT"); 24 | assert_eq!(d.columns()[3].type_info().name(), "BIGINT"); 25 | 26 | Ok(()) 27 | } 28 | 29 | #[sqlx_macros::test] 30 | async fn it_describes_with_params() -> anyhow::Result<()> { 31 | let mut conn = new::().await?; 32 | 33 | let d = conn 34 | .describe("SELECT text FROM tweet WHERE id = @p1") 35 | .await?; 36 | 37 | assert_eq!(d.columns()[0].name(), "text"); 38 | assert_eq!(d.nullable(0), Some(false)); 39 | 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /sqlx-macros-core/src/derives/mod.rs: -------------------------------------------------------------------------------- 1 | mod attributes; 2 | mod decode; 3 | mod encode; 4 | mod row; 5 | mod r#type; 6 | 7 | pub use decode::expand_derive_decode; 8 | pub use encode::expand_derive_encode; 9 | pub use r#type::expand_derive_type; 10 | pub use row::expand_derive_from_row; 11 | 12 | use self::attributes::RenameAll; 13 | use heck::{ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; 14 | use proc_macro2::TokenStream; 15 | use syn::DeriveInput; 16 | 17 | pub fn expand_derive_type_encode_decode(input: &DeriveInput) -> syn::Result { 18 | let encode_tts = expand_derive_encode(input)?; 19 | let decode_tts = expand_derive_decode(input)?; 20 | let type_tts = expand_derive_type(input)?; 21 | 22 | let combined = TokenStream::from_iter(encode_tts.into_iter().chain(decode_tts).chain(type_tts)); 23 | 24 | Ok(combined) 25 | } 26 | 27 | pub(crate) fn rename_all(s: &str, pattern: RenameAll) -> String { 28 | match pattern { 29 | RenameAll::LowerCase => s.to_lowercase(), 30 | RenameAll::SnakeCase => s.to_snake_case(), 31 | RenameAll::UpperCase => s.to_uppercase(), 32 | RenameAll::ScreamingSnakeCase => s.to_shouty_snake_case(), 33 | RenameAll::KebabCase => s.to_kebab_case(), 34 | RenameAll::CamelCase => s.to_lower_camel_case(), 35 | RenameAll::PascalCase => s.to_upper_camel_case(), 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sqlx-postgres/src/message/ssl_request.rs: -------------------------------------------------------------------------------- 1 | use crate::io::ProtocolEncode; 2 | 3 | pub struct SslRequest; 4 | 5 | impl SslRequest { 6 | // https://www.postgresql.org/docs/current/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-SSLREQUEST 7 | pub const BYTES: &'static [u8] = b"\x00\x00\x00\x08\x04\xd2\x16\x2f"; 8 | } 9 | 10 | // Cannot impl FrontendMessage because it does not have a format code 11 | impl ProtocolEncode<'_> for SslRequest { 12 | #[inline(always)] 13 | fn encode_with(&self, buf: &mut Vec, _context: ()) -> Result<(), crate::Error> { 14 | buf.extend_from_slice(Self::BYTES); 15 | Ok(()) 16 | } 17 | } 18 | 19 | #[test] 20 | fn test_encode_ssl_request() { 21 | let mut buf = Vec::new(); 22 | 23 | // Int32(8) 24 | // Length of message contents in bytes, including self. 25 | buf.extend_from_slice(&8_u32.to_be_bytes()); 26 | 27 | // Int32(80877103) 28 | // The SSL request code. The value is chosen to contain 1234 in the most significant 16 bits, 29 | // and 5679 in the least significant 16 bits. 30 | // (To avoid confusion, this code must not be the same as any protocol version number.) 31 | buf.extend_from_slice(&(((1234 << 16) | 5679) as u32).to_be_bytes()); 32 | 33 | let mut encoded = Vec::new(); 34 | SslRequest.encode(&mut encoded).unwrap(); 35 | 36 | assert_eq!(buf, SslRequest::BYTES); 37 | assert_eq!(buf, encoded); 38 | } 39 | -------------------------------------------------------------------------------- /sqlx-sqlite/src/options/synchronous.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use std::str::FromStr; 3 | 4 | /// Refer to [SQLite documentation] for the meaning of various synchronous settings. 5 | /// 6 | /// [SQLite documentation]: https://www.sqlite.org/pragma.html#pragma_synchronous 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 8 | pub enum SqliteSynchronous { 9 | Off, 10 | Normal, 11 | #[default] 12 | Full, 13 | Extra, 14 | } 15 | 16 | impl SqliteSynchronous { 17 | pub(crate) fn as_str(&self) -> &'static str { 18 | match self { 19 | SqliteSynchronous::Off => "OFF", 20 | SqliteSynchronous::Normal => "NORMAL", 21 | SqliteSynchronous::Full => "FULL", 22 | SqliteSynchronous::Extra => "EXTRA", 23 | } 24 | } 25 | } 26 | 27 | impl FromStr for SqliteSynchronous { 28 | type Err = Error; 29 | 30 | fn from_str(s: &str) -> Result { 31 | Ok(match &*s.to_ascii_lowercase() { 32 | "off" => SqliteSynchronous::Off, 33 | "normal" => SqliteSynchronous::Normal, 34 | "full" => SqliteSynchronous::Full, 35 | "extra" => SqliteSynchronous::Extra, 36 | 37 | _ => { 38 | return Err(Error::Configuration( 39 | format!("unknown value {s:?} for `synchronous`").into(), 40 | )); 41 | } 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/migrate/macro.rs: -------------------------------------------------------------------------------- 1 | #![cfg(unix)] 2 | use sqlx::migrate::Migrator; 3 | use std::path::Path; 4 | 5 | static EMBEDDED_SIMPLE: Migrator = sqlx::migrate!("tests/migrate/migrations_simple"); 6 | static EMBEDDED_REVERSIBLE: Migrator = sqlx::migrate!("tests/migrate/migrations_reversible"); 7 | static EMBEDDED_SYMLINK: Migrator = sqlx::migrate!("tests/migrate/migrations_symlink"); 8 | 9 | #[sqlx_macros::test] 10 | async fn same_output() -> anyhow::Result<()> { 11 | let runtime_simple = Migrator::new(Path::new("tests/migrate/migrations_simple")).await?; 12 | let runtime_reversible = 13 | Migrator::new(Path::new("tests/migrate/migrations_reversible")).await?; 14 | let runtime_symlink = Migrator::new(Path::new("tests/migrate/migrations_symlink")).await?; 15 | 16 | assert_same(&EMBEDDED_SIMPLE, &runtime_simple); 17 | assert_same(&EMBEDDED_REVERSIBLE, &runtime_reversible); 18 | assert_same(&EMBEDDED_SYMLINK, &runtime_symlink); 19 | 20 | Ok(()) 21 | } 22 | 23 | fn assert_same(embedded: &Migrator, runtime: &Migrator) { 24 | assert_eq!(runtime.migrations.len(), embedded.migrations.len()); 25 | 26 | for (e, r) in embedded.iter().zip(runtime.iter()) { 27 | assert_eq!(e.version, r.version); 28 | assert_eq!(e.description, r.description); 29 | assert_eq!(e.migration_type, r.migration_type); 30 | assert_eq!(e.sql, r.sql); 31 | assert_eq!(e.checksum, r.checksum); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sqlx-core/src/io/write_and_flush.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use sqlx_rt::AsyncWrite; 3 | use std::future::Future; 4 | use std::io::{BufRead, Cursor}; 5 | use std::pin::Pin; 6 | use std::task::{ready, Context, Poll}; 7 | 8 | // Atomic operation that writes the full buffer to the stream, flushes the stream, and then 9 | // clears the buffer (even if either of the two previous operations failed). 10 | pub struct WriteAndFlush<'a, S> { 11 | pub(super) stream: &'a mut S, 12 | pub(super) buf: Cursor<&'a mut Vec>, 13 | } 14 | 15 | impl Future for WriteAndFlush<'_, S> { 16 | type Output = Result<(), Error>; 17 | 18 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 19 | let Self { 20 | ref mut stream, 21 | ref mut buf, 22 | } = *self; 23 | 24 | loop { 25 | let read = buf.fill_buf()?; 26 | 27 | if !read.is_empty() { 28 | let written = ready!(Pin::new(&mut *stream).poll_write(cx, read)?); 29 | buf.consume(written); 30 | } else { 31 | break; 32 | } 33 | } 34 | 35 | Pin::new(stream).poll_flush(cx).map_err(Error::Io) 36 | } 37 | } 38 | 39 | impl<'a, S> Drop for WriteAndFlush<'a, S> { 40 | fn drop(&mut self) { 41 | // clear the buffer regardless of whether the flush succeeded or not 42 | self.buf.get_mut().clear(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sqlx-mysql/src/types/bool.rs: -------------------------------------------------------------------------------- 1 | use crate::collation::Collation; 2 | use crate::decode::Decode; 3 | use crate::encode::{Encode, IsNull}; 4 | use crate::error::BoxDynError; 5 | use crate::types::Type; 6 | use crate::{ 7 | protocol::text::{ColumnFlags, ColumnType}, 8 | MySql, MySqlTypeInfo, MySqlValueRef, 9 | }; 10 | 11 | impl Type for bool { 12 | fn type_info() -> MySqlTypeInfo { 13 | // MySQL has no actual `BOOLEAN` type, the type is an alias of `[UNSIGNED] TINYINT(1)` 14 | MySqlTypeInfo { 15 | flags: ColumnFlags::BINARY | ColumnFlags::UNSIGNED, 16 | collation: Collation::BINARY, 17 | max_size: Some(1), 18 | r#type: ColumnType::Tiny, 19 | } 20 | } 21 | 22 | fn compatible(ty: &MySqlTypeInfo) -> bool { 23 | matches!( 24 | ty.r#type, 25 | ColumnType::Tiny 26 | | ColumnType::Short 27 | | ColumnType::Long 28 | | ColumnType::Int24 29 | | ColumnType::LongLong 30 | | ColumnType::Bit 31 | ) 32 | } 33 | } 34 | 35 | impl Encode<'_, MySql> for bool { 36 | fn encode_by_ref(&self, buf: &mut Vec) -> Result { 37 | >::encode(*self as i8, buf) 38 | } 39 | } 40 | 41 | impl Decode<'_, MySql> for bool { 42 | fn decode(value: MySqlValueRef<'_>) -> Result { 43 | Ok(>::decode(value)? != 0) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/ui/postgres/gated/ipnetwork.stderr: -------------------------------------------------------------------------------- 1 | error: optional sqlx feature `ipnetwork` required for type INET of column #1 ("inet") 2 | --> $DIR/ipnetwork.rs:2:13 3 | | 4 | 2 | let _ = sqlx::query!("select '127.0.0.1'::inet"); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error: optional sqlx feature `ipnetwork` required for type CIDR of column #1 ("cidr") 10 | --> $DIR/ipnetwork.rs:4:13 11 | | 12 | 4 | let _ = sqlx::query!("select '2001:4f8:3:ba::/64'::cidr"); 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 14 | | 15 | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) 16 | 17 | error: optional sqlx feature `ipnetwork` required for type INET of param #1 18 | --> $DIR/ipnetwork.rs:6:13 19 | | 20 | 6 | let _ = sqlx::query!("select $1::inet", ()); 21 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 22 | | 23 | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) 24 | 25 | error: optional sqlx feature `ipnetwork` required for type CIDR of param #1 26 | --> $DIR/ipnetwork.rs:8:13 27 | | 28 | 8 | let _ = sqlx::query!("select $1::cidr", ()); 29 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 30 | | 31 | = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) 32 | -------------------------------------------------------------------------------- /sqlx-core/src/any/type_info.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display, Formatter}; 2 | 3 | use crate::type_info::TypeInfo; 4 | 5 | use AnyTypeInfoKind::*; 6 | 7 | #[derive(Debug, Clone, PartialEq)] 8 | pub struct AnyTypeInfo { 9 | #[doc(hidden)] 10 | pub kind: AnyTypeInfoKind, 11 | } 12 | 13 | impl AnyTypeInfo { 14 | pub fn kind(&self) -> AnyTypeInfoKind { 15 | self.kind 16 | } 17 | } 18 | 19 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 20 | pub enum AnyTypeInfoKind { 21 | Null, 22 | Bool, 23 | SmallInt, 24 | Integer, 25 | BigInt, 26 | Real, 27 | Double, 28 | Text, 29 | Blob, 30 | } 31 | 32 | impl TypeInfo for AnyTypeInfo { 33 | fn is_null(&self) -> bool { 34 | self.kind == Null 35 | } 36 | 37 | fn name(&self) -> &str { 38 | use AnyTypeInfoKind::*; 39 | 40 | match self.kind { 41 | Bool => "BOOLEAN", 42 | SmallInt => "SMALLINT", 43 | Integer => "INTEGER", 44 | BigInt => "BIGINT", 45 | Real => "REAL", 46 | Double => "DOUBLE", 47 | Text => "TEXT", 48 | Blob => "BLOB", 49 | Null => "NULL", 50 | } 51 | } 52 | } 53 | 54 | impl Display for AnyTypeInfo { 55 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 56 | f.write_str(self.name()) 57 | } 58 | } 59 | 60 | impl AnyTypeInfoKind { 61 | pub fn is_integer(&self) -> bool { 62 | matches!(self, SmallInt | Integer | BigInt) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/statement/prepare_ok.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | 3 | use crate::error::Error; 4 | use crate::io::ProtocolDecode; 5 | use crate::protocol::Capabilities; 6 | 7 | // https://dev.mysql.com/doc/internals/en/com-stmt-prepare-response.html#packet-COM_STMT_PREPARE_OK 8 | 9 | #[derive(Debug)] 10 | pub(crate) struct PrepareOk { 11 | pub(crate) statement_id: u32, 12 | pub(crate) columns: u16, 13 | pub(crate) params: u16, 14 | #[allow(unused)] 15 | pub(crate) warnings: u16, 16 | } 17 | 18 | impl ProtocolDecode<'_, Capabilities> for PrepareOk { 19 | fn decode_with(buf: Bytes, _: Capabilities) -> Result { 20 | const SIZE: usize = 12; 21 | 22 | let mut slice = buf.get(..SIZE).ok_or_else(|| { 23 | err_protocol!("PrepareOk expected 12 bytes but got {} bytes", buf.len()) 24 | })?; 25 | 26 | let status = slice.get_u8(); 27 | if status != 0x00 { 28 | return Err(err_protocol!( 29 | "expected 0x00 (COM_STMT_PREPARE_OK) but found 0x{:02x}", 30 | status 31 | )); 32 | } 33 | 34 | let statement_id = slice.get_u32_le(); 35 | let columns = slice.get_u16_le(); 36 | let params = slice.get_u16_le(); 37 | 38 | slice.advance(1); // reserved: string<1> 39 | 40 | let warnings = slice.get_u16_le(); 41 | 42 | Ok(Self { 43 | statement_id, 44 | columns, 45 | params, 46 | warnings, 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/postgres/files/src/main.rs: -------------------------------------------------------------------------------- 1 | use sqlx::{query_file, query_file_as, FromRow, PgPool}; 2 | use std::fmt::{Display, Formatter}; 3 | 4 | #[derive(FromRow)] 5 | struct PostWithAuthorQuery { 6 | pub post_id: i64, 7 | pub title: String, 8 | pub body: String, 9 | pub author_id: i64, 10 | pub author_username: String, 11 | } 12 | 13 | impl Display for PostWithAuthorQuery { 14 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 15 | write!( 16 | f, 17 | r#" 18 | post_id: {}, 19 | title: {}, 20 | body: {}, 21 | author_id: {}, 22 | author_username: {} 23 | "#, 24 | self.post_id, self.title, self.body, self.author_id, self.author_username 25 | ) 26 | } 27 | } 28 | 29 | #[tokio::main(flavor = "current_thread")] 30 | async fn main() -> anyhow::Result<()> { 31 | let pool = PgPool::connect(&dotenvy::var("DATABASE_URL")?).await?; 32 | 33 | // we can use a traditional wrapper around the `query!()` macro using files 34 | query_file!("queries/insert_seed_data.sql") 35 | .execute(&pool) 36 | .await?; 37 | 38 | // we can also use `query_file_as!()` similarly to `query_as!()` to map our database models 39 | let posts_with_authors = query_file_as!(PostWithAuthorQuery, "queries/list_all_posts.sql") 40 | .fetch_all(&pool) 41 | .await?; 42 | 43 | for post_with_author in posts_with_authors { 44 | println!("{post_with_author}"); 45 | } 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /sqlx-sqlite/src/types/float.rs: -------------------------------------------------------------------------------- 1 | use crate::arguments::SqliteArgumentsBuffer; 2 | use crate::decode::Decode; 3 | use crate::encode::{Encode, IsNull}; 4 | use crate::error::BoxDynError; 5 | use crate::type_info::DataType; 6 | use crate::types::Type; 7 | use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; 8 | 9 | impl Type for f32 { 10 | fn type_info() -> SqliteTypeInfo { 11 | SqliteTypeInfo(DataType::Float) 12 | } 13 | } 14 | 15 | impl Encode<'_, Sqlite> for f32 { 16 | fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { 17 | args.push(SqliteArgumentValue::Double((*self).into())); 18 | 19 | Ok(IsNull::No) 20 | } 21 | } 22 | 23 | impl<'r> Decode<'r, Sqlite> for f32 { 24 | fn decode(value: SqliteValueRef<'r>) -> Result { 25 | // Truncation is intentional 26 | #[allow(clippy::cast_possible_truncation)] 27 | Ok(value.double()? as f32) 28 | } 29 | } 30 | 31 | impl Type for f64 { 32 | fn type_info() -> SqliteTypeInfo { 33 | SqliteTypeInfo(DataType::Float) 34 | } 35 | } 36 | 37 | impl Encode<'_, Sqlite> for f64 { 38 | fn encode_by_ref(&self, args: &mut SqliteArgumentsBuffer) -> Result { 39 | args.push(SqliteArgumentValue::Double(*self)); 40 | 41 | Ok(IsNull::No) 42 | } 43 | } 44 | 45 | impl<'r> Decode<'r, Sqlite> for f64 { 46 | fn decode(value: SqliteValueRef<'r>) -> Result { 47 | Ok(value.double()?) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /sqlx-postgres/src/message/backend_key_data.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ByteOrder}; 2 | use sqlx_core::bytes::Bytes; 3 | 4 | use crate::error::Error; 5 | use crate::message::{BackendMessage, BackendMessageFormat}; 6 | 7 | /// Contains cancellation key data. The frontend must save these values if it 8 | /// wishes to be able to issue `CancelRequest` messages later. 9 | #[derive(Debug)] 10 | pub struct BackendKeyData { 11 | /// The process ID of this database. 12 | pub process_id: u32, 13 | 14 | /// The secret key of this database. 15 | pub secret_key: u32, 16 | } 17 | 18 | impl BackendMessage for BackendKeyData { 19 | const FORMAT: BackendMessageFormat = BackendMessageFormat::BackendKeyData; 20 | 21 | fn decode_body(buf: Bytes) -> Result { 22 | let process_id = BigEndian::read_u32(&buf); 23 | let secret_key = BigEndian::read_u32(&buf[4..]); 24 | 25 | Ok(Self { 26 | process_id, 27 | secret_key, 28 | }) 29 | } 30 | } 31 | 32 | #[test] 33 | fn test_decode_backend_key_data() { 34 | const DATA: &[u8] = b"\0\0'\xc6\x89R\xc5+"; 35 | 36 | let m = BackendKeyData::decode_body(DATA.into()).unwrap(); 37 | 38 | assert_eq!(m.process_id, 10182); 39 | assert_eq!(m.secret_key, 2303903019); 40 | } 41 | 42 | #[cfg(all(test, not(debug_assertions)))] 43 | #[bench] 44 | fn bench_decode_backend_key_data(b: &mut test::Bencher) { 45 | const DATA: &[u8] = b"\0\0'\xc6\x89R\xc5+"; 46 | 47 | b.iter(|| { 48 | BackendKeyData::decode_body(test::black_box(Bytes::from_static(DATA))).unwrap(); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /sqlx-postgres/src/types/mac_address.rs: -------------------------------------------------------------------------------- 1 | use mac_address::MacAddress; 2 | 3 | use crate::decode::Decode; 4 | use crate::encode::{Encode, IsNull}; 5 | use crate::error::BoxDynError; 6 | use crate::types::Type; 7 | use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; 8 | 9 | impl Type for MacAddress { 10 | fn type_info() -> PgTypeInfo { 11 | PgTypeInfo::MACADDR 12 | } 13 | 14 | fn compatible(ty: &PgTypeInfo) -> bool { 15 | *ty == PgTypeInfo::MACADDR 16 | } 17 | } 18 | 19 | impl PgHasArrayType for MacAddress { 20 | fn array_type_info() -> PgTypeInfo { 21 | PgTypeInfo::MACADDR_ARRAY 22 | } 23 | } 24 | 25 | impl Encode<'_, Postgres> for MacAddress { 26 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { 27 | buf.extend_from_slice(&self.bytes()); // write just the address 28 | Ok(IsNull::No) 29 | } 30 | 31 | fn size_hint(&self) -> usize { 32 | 6 33 | } 34 | } 35 | 36 | impl Decode<'_, Postgres> for MacAddress { 37 | fn decode(value: PgValueRef<'_>) -> Result { 38 | let bytes = match value.format() { 39 | PgValueFormat::Binary => value.as_bytes()?, 40 | PgValueFormat::Text => { 41 | return Ok(value.as_str()?.parse()?); 42 | } 43 | }; 44 | 45 | if bytes.len() == 6 { 46 | return Ok(MacAddress::new(bytes.try_into().unwrap())); 47 | } 48 | 49 | Err("invalid data received when expecting an MACADDR".into()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /sqlx-mysql/src/statement.rs: -------------------------------------------------------------------------------- 1 | use super::MySqlColumn; 2 | use crate::column::ColumnIndex; 3 | use crate::error::Error; 4 | use crate::ext::ustr::UStr; 5 | use crate::HashMap; 6 | use crate::{MySql, MySqlArguments, MySqlTypeInfo}; 7 | use either::Either; 8 | use sqlx_core::sql_str::SqlStr; 9 | use std::sync::Arc; 10 | 11 | pub(crate) use sqlx_core::statement::*; 12 | 13 | #[derive(Debug, Clone)] 14 | pub struct MySqlStatement { 15 | pub(crate) sql: SqlStr, 16 | pub(crate) metadata: MySqlStatementMetadata, 17 | } 18 | 19 | #[derive(Debug, Default, Clone)] 20 | pub(crate) struct MySqlStatementMetadata { 21 | pub(crate) columns: Arc>, 22 | pub(crate) column_names: Arc>, 23 | pub(crate) parameters: usize, 24 | } 25 | 26 | impl Statement for MySqlStatement { 27 | type Database = MySql; 28 | 29 | fn into_sql(self) -> SqlStr { 30 | self.sql 31 | } 32 | 33 | fn sql(&self) -> &SqlStr { 34 | &self.sql 35 | } 36 | 37 | fn parameters(&self) -> Option> { 38 | Some(Either::Right(self.metadata.parameters)) 39 | } 40 | 41 | fn columns(&self) -> &[MySqlColumn] { 42 | &self.metadata.columns 43 | } 44 | 45 | impl_statement_query!(MySqlArguments); 46 | } 47 | 48 | impl ColumnIndex for &'_ str { 49 | fn index(&self, statement: &MySqlStatement) -> Result { 50 | statement 51 | .metadata 52 | .column_names 53 | .get(*self) 54 | .ok_or_else(|| Error::ColumnNotFound((*self).into())) 55 | .copied() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /sqlx-postgres/src/message/ready_for_query.rs: -------------------------------------------------------------------------------- 1 | use sqlx_core::bytes::Bytes; 2 | 3 | use crate::error::Error; 4 | use crate::message::{BackendMessage, BackendMessageFormat}; 5 | 6 | #[derive(Debug)] 7 | #[repr(u8)] 8 | pub enum TransactionStatus { 9 | /// Not in a transaction block. 10 | Idle = b'I', 11 | 12 | /// In a transaction block. 13 | Transaction = b'T', 14 | 15 | /// In a _failed_ transaction block. Queries will be rejected until block is ended. 16 | Error = b'E', 17 | } 18 | 19 | #[derive(Debug)] 20 | pub struct ReadyForQuery { 21 | pub transaction_status: TransactionStatus, 22 | } 23 | 24 | impl BackendMessage for ReadyForQuery { 25 | const FORMAT: BackendMessageFormat = BackendMessageFormat::ReadyForQuery; 26 | 27 | fn decode_body(buf: Bytes) -> Result { 28 | let status = match buf[0] { 29 | b'I' => TransactionStatus::Idle, 30 | b'T' => TransactionStatus::Transaction, 31 | b'E' => TransactionStatus::Error, 32 | 33 | status => { 34 | return Err(err_protocol!( 35 | "unknown transaction status: {:?}", 36 | status as char 37 | )); 38 | } 39 | }; 40 | 41 | Ok(Self { 42 | transaction_status: status, 43 | }) 44 | } 45 | } 46 | 47 | #[test] 48 | fn test_decode_ready_for_query() -> Result<(), Error> { 49 | const DATA: &[u8] = b"E"; 50 | 51 | let m = ReadyForQuery::decode_body(Bytes::from_static(DATA))?; 52 | 53 | assert!(matches!(m.transaction_status, TransactionStatus::Error)); 54 | 55 | Ok(()) 56 | } 57 | -------------------------------------------------------------------------------- /tests/ui-tests.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | #[test] 4 | #[ignore] 5 | fn ui_tests() { 6 | let t = trybuild::TestCases::new(); 7 | 8 | if cfg!(feature = "postgres") { 9 | t.compile_fail("tests/ui/postgres/*.rs"); 10 | 11 | // UI tests for column types that require gated features 12 | if cfg!(not(feature = "chrono")) && cfg!(not(feature = "time")) { 13 | t.compile_fail("tests/ui/postgres/gated/chrono.rs"); 14 | } 15 | 16 | if cfg!(not(feature = "uuid")) { 17 | t.compile_fail("tests/ui/postgres/gated/uuid.rs"); 18 | } 19 | 20 | if cfg!(not(feature = "ipnet")) && cfg!(not(feature = "ipnetwork")) { 21 | t.compile_fail("tests/ui/postgres/gated/ipnetwork.rs"); 22 | } 23 | } 24 | 25 | if cfg!(feature = "mysql") { 26 | t.compile_fail("tests/ui/mysql/*.rs"); 27 | 28 | // UI tests for column types that require gated features 29 | if cfg!(not(feature = "chrono")) && cfg!(not(feature = "time")) { 30 | t.compile_fail("tests/ui/mysql/gated/chrono.rs"); 31 | } 32 | } 33 | 34 | if cfg!(feature = "_sqlite") { 35 | if dotenvy::var("DATABASE_URL").map_or(true, |v| { 36 | Path::is_relative(v.trim_start_matches("sqlite://").as_ref()) 37 | }) { 38 | // this isn't `Trybuild`'s fault: https://github.com/dtolnay/trybuild/issues/69#issuecomment-620329526 39 | panic!("DATABASE_URL must contain an absolute path for SQLite UI tests") 40 | } 41 | 42 | t.compile_fail("tests/ui/sqlite/*.rs"); 43 | } 44 | 45 | t.compile_fail("tests/ui/*.rs"); 46 | } 47 | -------------------------------------------------------------------------------- /sqlx-core/src/types/bstr.rs: -------------------------------------------------------------------------------- 1 | /// Conversions between `bstr` types and SQL types. 2 | use crate::database::Database; 3 | use crate::decode::Decode; 4 | use crate::encode::{Encode, IsNull}; 5 | use crate::error::BoxDynError; 6 | use crate::types::Type; 7 | 8 | #[doc(no_inline)] 9 | pub use bstr::{BStr, BString, ByteSlice}; 10 | 11 | impl Type for BString 12 | where 13 | DB: Database, 14 | [u8]: Type, 15 | { 16 | fn type_info() -> DB::TypeInfo { 17 | <&[u8] as Type>::type_info() 18 | } 19 | 20 | fn compatible(ty: &DB::TypeInfo) -> bool { 21 | <&[u8] as Type>::compatible(ty) 22 | } 23 | } 24 | 25 | impl<'r, DB> Decode<'r, DB> for BString 26 | where 27 | DB: Database, 28 | Vec: Decode<'r, DB>, 29 | { 30 | fn decode(value: ::ValueRef<'r>) -> Result { 31 | as Decode>::decode(value).map(BString::from) 32 | } 33 | } 34 | 35 | impl<'q, DB: Database> Encode<'q, DB> for &'q BStr 36 | where 37 | DB: Database, 38 | &'q [u8]: Encode<'q, DB>, 39 | { 40 | fn encode_by_ref( 41 | &self, 42 | buf: &mut ::ArgumentBuffer, 43 | ) -> Result { 44 | <&[u8] as Encode>::encode(self.as_bytes(), buf) 45 | } 46 | } 47 | 48 | impl<'q, DB: Database> Encode<'q, DB> for BString 49 | where 50 | DB: Database, 51 | Vec: Encode<'q, DB>, 52 | { 53 | fn encode_by_ref( 54 | &self, 55 | buf: &mut ::ArgumentBuffer, 56 | ) -> Result { 57 | as Encode>::encode(self.as_bytes().to_vec(), buf) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /examples/postgres/preferred-crates/uses-rust-decimal/src/lib.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use sqlx::PgExecutor; 3 | 4 | #[derive(sqlx::FromRow, Debug)] 5 | pub struct Purchase { 6 | pub id: Uuid, 7 | pub user_id: Uuid, 8 | pub amount: Decimal, 9 | pub description: String, 10 | pub created_at: DateTime, 11 | } 12 | 13 | pub use rust_decimal::Decimal; 14 | use uuid::Uuid; 15 | 16 | pub async fn create_table(e: impl PgExecutor<'_>) -> sqlx::Result<()> { 17 | sqlx::raw_sql( 18 | // language=PostgreSQL 19 | "create table if not exists purchases( \ 20 | id uuid primary key default gen_random_uuid(), \ 21 | user_id uuid not null, \ 22 | amount numeric not null check(amount > 0), \ 23 | description text not null, \ 24 | created_at timestamptz not null default now() \ 25 | ); 26 | ", 27 | ) 28 | .execute(e) 29 | .await?; 30 | 31 | Ok(()) 32 | } 33 | 34 | pub async fn create_purchase( 35 | e: impl PgExecutor<'_>, 36 | user_id: Uuid, 37 | amount: Decimal, 38 | description: &str, 39 | ) -> sqlx::Result { 40 | sqlx::query_scalar( 41 | "insert into purchases(user_id, amount, description) values ($1, $2, $3) returning id", 42 | ) 43 | .bind(user_id) 44 | .bind(amount) 45 | .bind(description) 46 | .fetch_one(e) 47 | .await 48 | } 49 | 50 | pub async fn get_purchase(e: impl PgExecutor<'_>, id: Uuid) -> sqlx::Result> { 51 | sqlx::query_as("select * from purchases where id = $1") 52 | .bind(id) 53 | .fetch_optional(e) 54 | .await 55 | } 56 | -------------------------------------------------------------------------------- /sqlx-mysql/src/row.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | pub(crate) use sqlx_core::row::*; 4 | 5 | use crate::column::ColumnIndex; 6 | use crate::error::Error; 7 | use crate::ext::ustr::UStr; 8 | use crate::HashMap; 9 | use crate::{protocol, MySql, MySqlColumn, MySqlValueFormat, MySqlValueRef}; 10 | 11 | /// Implementation of [`Row`] for MySQL. 12 | pub struct MySqlRow { 13 | pub(crate) row: protocol::Row, 14 | pub(crate) format: MySqlValueFormat, 15 | pub(crate) columns: Arc>, 16 | pub(crate) column_names: Arc>, 17 | } 18 | 19 | impl Row for MySqlRow { 20 | type Database = MySql; 21 | 22 | fn columns(&self) -> &[MySqlColumn] { 23 | &self.columns 24 | } 25 | 26 | fn try_get_raw(&self, index: I) -> Result, Error> 27 | where 28 | I: ColumnIndex, 29 | { 30 | let index = index.index(self)?; 31 | let column = &self.columns[index]; 32 | let value = self.row.get(index); 33 | 34 | Ok(MySqlValueRef { 35 | format: self.format, 36 | row: Some(&self.row.storage), 37 | type_info: column.type_info.clone(), 38 | value, 39 | }) 40 | } 41 | } 42 | 43 | impl ColumnIndex for &'_ str { 44 | fn index(&self, row: &MySqlRow) -> Result { 45 | row.column_names 46 | .get(*self) 47 | .ok_or_else(|| Error::ColumnNotFound((*self).into())) 48 | .copied() 49 | } 50 | } 51 | 52 | impl std::fmt::Debug for MySqlRow { 53 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 54 | debug_row(self, f) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /sqlx-mysql/src/io/buf.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | 3 | use crate::error::Error; 4 | use crate::io::BufExt; 5 | 6 | pub trait MySqlBufExt: Buf { 7 | // Read a length-encoded integer. 8 | // NOTE: 0xfb or NULL is only returned for binary value encoding to indicate NULL. 9 | // NOTE: 0xff is only returned during a result set to indicate ERR. 10 | // 11 | fn get_uint_lenenc(&mut self) -> u64; 12 | 13 | // Read a length-encoded string. 14 | #[allow(dead_code)] 15 | fn get_str_lenenc(&mut self) -> Result; 16 | 17 | // Read a length-encoded byte sequence. 18 | fn get_bytes_lenenc(&mut self) -> Result; 19 | } 20 | 21 | impl MySqlBufExt for Bytes { 22 | fn get_uint_lenenc(&mut self) -> u64 { 23 | match self.get_u8() { 24 | 0xfc => u64::from(self.get_u16_le()), 25 | 0xfd => self.get_uint_le(3), 26 | 0xfe => self.get_u64_le(), 27 | 28 | v => u64::from(v), 29 | } 30 | } 31 | 32 | fn get_str_lenenc(&mut self) -> Result { 33 | let size = self.get_uint_lenenc(); 34 | let size = usize::try_from(size) 35 | .map_err(|_| err_protocol!("string length overflows usize: {size}"))?; 36 | 37 | self.get_str(size) 38 | } 39 | 40 | fn get_bytes_lenenc(&mut self) -> Result { 41 | let size = self.get_uint_lenenc(); 42 | let size = usize::try_from(size) 43 | .map_err(|_| err_protocol!("string length overflows usize: {size}"))?; 44 | 45 | Ok(self.split_to(size)) 46 | } 47 | } 48 | --------------------------------------------------------------------------------