├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── pull_request_template.md └── workflows │ ├── examples.yml │ ├── sqlx-cli.yml │ └── sqlx.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── FAQ.md ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches └── sqlite │ └── describe.rs ├── clippy.toml ├── contrib └── ide │ └── vscode │ └── settings.json ├── examples ├── .gitignore ├── mysql │ └── todos │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── migrations │ │ └── 20200718111257_todos.sql │ │ └── src │ │ └── main.rs ├── postgres │ ├── axum-social-with-tests │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── migrations │ │ │ ├── 1_user.sql │ │ │ ├── 2_post.sql │ │ │ └── 3_comment.sql │ │ ├── src │ │ │ ├── http │ │ │ │ ├── error.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── post │ │ │ │ │ ├── comment.rs │ │ │ │ │ └── mod.rs │ │ │ │ └── user.rs │ │ │ ├── lib.rs │ │ │ ├── main.rs │ │ │ └── password.rs │ │ └── tests │ │ │ ├── comment.rs │ │ │ ├── common.rs │ │ │ ├── fixtures │ │ │ ├── comments.sql │ │ │ ├── posts.sql │ │ │ └── users.sql │ │ │ ├── post.rs │ │ │ └── user.rs │ ├── chat │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src │ │ │ └── main.rs │ ├── files │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── migrations │ │ │ └── 20220712221654_files.sql │ │ ├── queries │ │ │ ├── insert_seed_data.sql │ │ │ └── list_all_posts.sql │ │ └── src │ │ │ └── main.rs │ ├── json │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── migrations │ │ │ └── 20200824190010_json.sql │ │ └── src │ │ │ └── main.rs │ ├── listen │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src │ │ │ └── main.rs │ ├── mockable-todos │ │ ├── .env.example │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── docker-compose.yml │ │ ├── migrations │ │ │ └── 20200718111257_todos.sql │ │ └── src │ │ │ └── main.rs │ ├── todos │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── migrations │ │ │ └── 20200718111257_todos.sql │ │ └── src │ │ │ └── main.rs │ └── transaction │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── migrations │ │ └── 20200718111257_todos.sql │ │ └── src │ │ └── main.rs ├── sqlite │ └── todos │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── migrations │ │ └── 20200718111257_todos.sql │ │ └── src │ │ └── main.rs └── x.py ├── gen-changelog.sh ├── rust-toolchain.toml ├── sqlx-cli ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── src │ ├── bin │ │ ├── cargo-sqlx.rs │ │ └── sqlx.rs │ ├── completions.rs │ ├── database.rs │ ├── lib.rs │ ├── metadata.rs │ ├── migrate.rs │ ├── migration.rs │ ├── opt.rs │ └── prepare.rs └── tests │ ├── add.rs │ ├── assets │ └── sample_metadata.json │ ├── common │ └── mod.rs │ ├── migrate.rs │ └── migrations_reversible │ ├── 20230101000000_test1.down.sql │ ├── 20230101000000_test1.up.sql │ ├── 20230201000000_test2.down.sql │ ├── 20230201000000_test2.up.sql │ ├── 20230301000000_test3.down.sql │ ├── 20230301000000_test3.up.sql │ ├── 20230401000000_test4.down.sql │ ├── 20230401000000_test4.up.sql │ ├── 20230501000000_test5.down.sql │ └── 20230501000000_test5.up.sql ├── sqlx-core ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT └── src │ ├── acquire.rs │ ├── any │ ├── arguments.rs │ ├── column.rs │ ├── connection │ │ ├── backend.rs │ │ ├── executor.rs │ │ └── mod.rs │ ├── database.rs │ ├── driver.rs │ ├── error.rs │ ├── kind.rs │ ├── migrate.rs │ ├── mod.rs │ ├── options.rs │ ├── query_result.rs │ ├── row.rs │ ├── statement.rs │ ├── transaction.rs │ ├── type_info.rs │ ├── types │ │ ├── blob.rs │ │ ├── bool.rs │ │ ├── float.rs │ │ ├── int.rs │ │ ├── mod.rs │ │ └── str.rs │ └── value.rs │ ├── arguments.rs │ ├── column.rs │ ├── common │ ├── mod.rs │ └── statement_cache.rs │ ├── connection.rs │ ├── database.rs │ ├── decode.rs │ ├── describe.rs │ ├── encode.rs │ ├── error.rs │ ├── executor.rs │ ├── ext │ ├── async_stream.rs │ ├── mod.rs │ └── ustr.rs │ ├── from_row.rs │ ├── fs.rs │ ├── io │ ├── buf.rs │ ├── buf_mut.rs │ ├── buf_stream.rs │ ├── decode.rs │ ├── encode.rs │ ├── mod.rs │ ├── read_buf.rs │ └── write_and_flush.rs │ ├── lib.rs │ ├── logger.rs │ ├── migrate │ ├── error.rs │ ├── migrate.rs │ ├── migration.rs │ ├── migration_type.rs │ ├── migrator.rs │ ├── mod.rs │ └── source.rs │ ├── net │ ├── mod.rs │ ├── socket │ │ ├── buffered.rs │ │ └── mod.rs │ └── tls │ │ ├── mod.rs │ │ ├── tls_native_tls.rs │ │ ├── tls_rustls.rs │ │ └── util.rs │ ├── pool │ ├── connection.rs │ ├── executor.rs │ ├── inner.rs │ ├── maybe.rs │ ├── mod.rs │ └── options.rs │ ├── query.rs │ ├── query_as.rs │ ├── query_builder.rs │ ├── query_scalar.rs │ ├── raw_sql.rs │ ├── row.rs │ ├── rt │ ├── mod.rs │ ├── rt_async_std │ │ ├── mod.rs │ │ └── socket.rs │ └── rt_tokio │ │ ├── mod.rs │ │ └── socket.rs │ ├── statement.rs │ ├── sync.rs │ ├── testing │ ├── fixtures.rs │ └── mod.rs │ ├── transaction.rs │ ├── type_checking.rs │ ├── type_info.rs │ ├── types │ ├── bstr.rs │ ├── json.rs │ ├── mod.rs │ ├── non_zero.rs │ └── text.rs │ └── value.rs ├── sqlx-macros-core ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT └── src │ ├── common.rs │ ├── database │ ├── impls.rs │ └── mod.rs │ ├── derives │ ├── attributes.rs │ ├── decode.rs │ ├── encode.rs │ ├── mod.rs │ ├── row.rs │ └── type.rs │ ├── lib.rs │ ├── migrate.rs │ ├── query │ ├── args.rs │ ├── data.rs │ ├── input.rs │ ├── mod.rs │ └── output.rs │ └── test_attr.rs ├── sqlx-macros ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT └── src │ └── lib.rs ├── sqlx-mysql ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT └── src │ ├── any.rs │ ├── arguments.rs │ ├── collation.rs │ ├── column.rs │ ├── connection │ ├── auth.rs │ ├── establish.rs │ ├── executor.rs │ ├── mod.rs │ ├── stream.rs │ └── tls.rs │ ├── database.rs │ ├── error.rs │ ├── io │ ├── buf.rs │ ├── buf_mut.rs │ └── mod.rs │ ├── lib.rs │ ├── migrate.rs │ ├── options │ ├── connect.rs │ ├── mod.rs │ ├── parse.rs │ └── ssl_mode.rs │ ├── protocol │ ├── auth.rs │ ├── capabilities.rs │ ├── connect │ │ ├── auth_switch.rs │ │ ├── handshake.rs │ │ ├── handshake_response.rs │ │ ├── mod.rs │ │ └── ssl_request.rs │ ├── mod.rs │ ├── packet.rs │ ├── response │ │ ├── eof.rs │ │ ├── err.rs │ │ ├── mod.rs │ │ ├── ok.rs │ │ └── status.rs │ ├── row.rs │ ├── statement │ │ ├── execute.rs │ │ ├── mod.rs │ │ ├── prepare.rs │ │ ├── prepare_ok.rs │ │ ├── row.rs │ │ └── stmt_close.rs │ └── text │ │ ├── column.rs │ │ ├── mod.rs │ │ ├── ping.rs │ │ ├── query.rs │ │ ├── quit.rs │ │ └── row.rs │ ├── query_result.rs │ ├── row.rs │ ├── statement.rs │ ├── testing │ └── mod.rs │ ├── transaction.rs │ ├── type_checking.rs │ ├── type_info.rs │ ├── types │ ├── bigdecimal.rs │ ├── bool.rs │ ├── bytes.rs │ ├── chrono.rs │ ├── float.rs │ ├── inet.rs │ ├── int.rs │ ├── json.rs │ ├── mod.rs │ ├── mysql_time.rs │ ├── rust_decimal.rs │ ├── str.rs │ ├── text.rs │ ├── time.rs │ ├── uint.rs │ └── uuid.rs │ └── value.rs ├── sqlx-postgres ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT └── src │ ├── advisory_lock.rs │ ├── any.rs │ ├── arguments.rs │ ├── column.rs │ ├── connection │ ├── describe.rs │ ├── establish.rs │ ├── executor.rs │ ├── mod.rs │ ├── sasl.rs │ ├── stream.rs │ └── tls.rs │ ├── copy.rs │ ├── database.rs │ ├── error.rs │ ├── io │ ├── buf_mut.rs │ └── mod.rs │ ├── lib.rs │ ├── listener.rs │ ├── message │ ├── authentication.rs │ ├── backend_key_data.rs │ ├── bind.rs │ ├── close.rs │ ├── command_complete.rs │ ├── copy.rs │ ├── data_row.rs │ ├── describe.rs │ ├── execute.rs │ ├── flush.rs │ ├── mod.rs │ ├── notification.rs │ ├── parameter_description.rs │ ├── parameter_status.rs │ ├── parse.rs │ ├── parse_complete.rs │ ├── password.rs │ ├── query.rs │ ├── ready_for_query.rs │ ├── response.rs │ ├── row_description.rs │ ├── sasl.rs │ ├── ssl_request.rs │ ├── startup.rs │ ├── sync.rs │ └── terminate.rs │ ├── migrate.rs │ ├── options │ ├── connect.rs │ ├── doc.md │ ├── mod.rs │ ├── parse.rs │ ├── pgpass.rs │ └── ssl_mode.rs │ ├── query_result.rs │ ├── row.rs │ ├── statement.rs │ ├── testing │ └── mod.rs │ ├── transaction.rs │ ├── type_checking.rs │ ├── type_info.rs │ ├── types │ ├── array.rs │ ├── bigdecimal-range.md │ ├── bigdecimal.rs │ ├── bit_vec.rs │ ├── bool.rs │ ├── bytes.rs │ ├── chrono │ │ ├── date.rs │ │ ├── datetime.rs │ │ ├── mod.rs │ │ └── time.rs │ ├── citext.rs │ ├── cube.rs │ ├── float.rs │ ├── geometry │ │ ├── box.rs │ │ ├── circle.rs │ │ ├── line.rs │ │ ├── line_segment.rs │ │ ├── mod.rs │ │ ├── path.rs │ │ ├── point.rs │ │ └── polygon.rs │ ├── hstore.rs │ ├── int.rs │ ├── interval.rs │ ├── ipnet │ │ ├── ipaddr.rs │ │ ├── ipnet.rs │ │ └── mod.rs │ ├── ipnetwork │ │ ├── ipaddr.rs │ │ ├── ipnetwork.rs │ │ └── mod.rs │ ├── json.rs │ ├── lquery.rs │ ├── ltree.rs │ ├── mac_address.rs │ ├── mod.rs │ ├── money.rs │ ├── numeric.rs │ ├── oid.rs │ ├── range.rs │ ├── record.rs │ ├── rust_decimal-range.md │ ├── rust_decimal.rs │ ├── str.rs │ ├── text.rs │ ├── time │ │ ├── date.rs │ │ ├── datetime.rs │ │ ├── mod.rs │ │ └── time.rs │ ├── time_tz.rs │ ├── tuple.rs │ ├── uuid.rs │ └── void.rs │ └── value.rs ├── sqlx-sqlite ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT └── src │ ├── any.rs │ ├── arguments.rs │ ├── column.rs │ ├── connection │ ├── collation.rs │ ├── describe.rs │ ├── establish.rs │ ├── execute.rs │ ├── executor.rs │ ├── explain.rs │ ├── handle.rs │ ├── intmap.rs │ ├── mod.rs │ ├── preupdate_hook.rs │ ├── serialize.rs │ └── worker.rs │ ├── database.rs │ ├── error.rs │ ├── lib.rs │ ├── logger.rs │ ├── migrate.rs │ ├── options │ ├── auto_vacuum.rs │ ├── connect.rs │ ├── journal_mode.rs │ ├── locking_mode.rs │ ├── mod.rs │ ├── parse.rs │ └── synchronous.rs │ ├── query_result.rs │ ├── regexp.rs │ ├── row.rs │ ├── statement │ ├── handle.rs │ ├── mod.rs │ ├── unlock_notify.rs │ └── virtual.rs │ ├── testing │ └── mod.rs │ ├── transaction.rs │ ├── type_checking.rs │ ├── type_info.rs │ ├── types │ ├── bool.rs │ ├── bytes.rs │ ├── chrono.rs │ ├── float.rs │ ├── int.rs │ ├── json.rs │ ├── mod.rs │ ├── str.rs │ ├── text.rs │ ├── time.rs │ ├── uint.rs │ └── uuid.rs │ └── value.rs ├── sqlx-test ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT └── src │ └── lib.rs ├── src ├── any │ ├── install_drivers_note.md │ └── mod.rs ├── lib.md ├── lib.rs ├── macros │ ├── mod.rs │ └── test.md ├── spec_error.rs └── ty_match.rs └── tests ├── .dockerignore ├── .env ├── .gitignore ├── README.md ├── any ├── any.rs └── pool.rs ├── certs ├── ca.crt ├── ca.srl ├── client.crt ├── client.csr └── server.crt ├── docker-compose.yml ├── docker.py ├── fixtures ├── mysql │ ├── posts.sql │ └── users.sql └── postgres │ ├── posts.sql │ └── users.sql ├── keys ├── ca.key ├── client.key └── server.key ├── migrate ├── macro.rs ├── migrations_reversible │ ├── 20220721124650_add_table.down.sql │ ├── 20220721124650_add_table.up.sql │ ├── 20220721125033_modify_column.down.sql │ └── 20220721125033_modify_column.up.sql ├── migrations_simple │ ├── 20220721115250_add_test_table.sql │ └── 20220721115524_convert_type.sql └── migrations_symlink ├── mssql ├── Dockerfile ├── configure-db.sh ├── describe.rs ├── entrypoint.sh ├── macros.rs ├── mssql-2017.dockerfile ├── mssql.rs ├── setup.sql └── types.rs ├── mysql ├── Dockerfile ├── derives.rs ├── describe.rs ├── error.rs ├── fixtures │ ├── comments.sql │ ├── posts.sql │ └── users.sql ├── macros.rs ├── migrate.rs ├── migrations │ ├── 1_user.sql │ ├── 2_post.sql │ └── 3_comment.sql ├── migrations_reversible │ ├── 20220721124650_add_table.down.sql │ ├── 20220721124650_add_table.up.sql │ ├── 20220721125033_modify_column.down.sql │ └── 20220721125033_modify_column.up.sql ├── migrations_simple │ ├── 20220721115250_add_test_table.sql │ └── 20220721115524_convert_type.sql ├── my.cnf ├── mysql.rs ├── rustsec.rs ├── setup.sql ├── test-attr.rs └── types.rs ├── postgres ├── Dockerfile ├── derives.rs ├── describe.rs ├── error.rs ├── fixtures │ ├── comments.sql │ ├── posts.sql │ ├── rustsec │ │ └── 2024_0363.sql │ └── users.sql ├── macros.rs ├── migrate.rs ├── migrations │ ├── 0_setup.sql │ ├── 1_user.sql │ ├── 2_post.sql │ └── 3_comment.sql ├── migrations_no_tx │ └── 0_create_db.sql ├── migrations_reversible │ ├── 20220721124650_add_table.down.sql │ ├── 20220721124650_add_table.up.sql │ ├── 20220721125033_modify_column.down.sql │ └── 20220721125033_modify_column.up.sql ├── migrations_simple │ ├── 20220721115250_add_test_table.sql │ └── 20220721115524_convert_type.sql ├── pg_hba.conf ├── postgres.rs ├── query_builder.rs ├── rustsec.rs ├── setup.sql ├── test-attr.rs ├── test-query.sql └── types.rs ├── sqlite ├── .gitignore ├── any.rs ├── derives.rs ├── describe.rs ├── error.rs ├── fixtures │ ├── comments.sql │ ├── posts.sql │ └── users.sql ├── macros.rs ├── migrate.rs ├── migrations │ ├── 1_user.sql │ ├── 2_post.sql │ └── 3_comment.sql ├── migrations_reversible │ ├── 20220721124650_add_table.down.sql │ ├── 20220721124650_add_table.up.sql │ ├── 20220721125033_modify_column.down.sql │ └── 20220721125033_modify_column.up.sql ├── migrations_simple │ ├── 20220721115250_add_test_table.sql │ └── 20220721115524_convert_type.sql ├── rustsec.rs ├── setup.sql ├── sqlcipher.rs ├── sqlite.db ├── sqlite.rs ├── test-attr.rs └── types.rs ├── ui-tests.rs ├── ui ├── mysql │ └── gated │ │ ├── chrono.rs │ │ └── chrono.stderr ├── postgres │ ├── deprecated_rename.rs │ ├── deprecated_rename.stderr │ ├── gated │ │ ├── chrono.rs │ │ ├── chrono.stderr │ │ ├── ipnetwork.rs │ │ ├── ipnetwork.stderr │ │ ├── uuid.rs │ │ └── uuid.stderr │ ├── issue_30.rs │ ├── issue_30.stderr │ ├── unsupported-type.rs │ ├── unsupported-type.stderr │ ├── wrong_param_type.rs │ └── wrong_param_type.stderr └── sqlite │ ├── expression-column-type.rs │ └── expression-column-type.stderr └── x.py /.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 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | So, you've decided to contribute, that's great! 4 | 5 | You can use this document to figure out how and where to start. 6 | 7 | ## Getting started 8 | 9 | - Make sure you have a [GitHub account](https://github.com/join). 10 | - Take a look at [existing issues](https://github.com/launchbadge/sqlx/issues). 11 | - If you need to create an issue: 12 | - Make sure to clearly describe it. 13 | - Including steps to reproduce when it is a bug. 14 | - Include the version of SQLx used. 15 | - Include the database driver and version. 16 | - Include the database version. 17 | 18 | ## Making changes 19 | 20 | - Fork the repository on GitHub. 21 | - Create a branch on your fork. 22 | - You can usually base it on the `main` branch. 23 | - Make sure not to commit directly to `main`. 24 | - Make commits of logical and atomic units. 25 | - Make sure you have added the necessary tests for your changes. 26 | - Push your changes to a topic branch in your fork of the repository. 27 | - Submit a pull request to the original repository. 28 | 29 | ## What to work on 30 | 31 | We try to mark issues with a suggested level of experience (in Rust/SQL/SQLx). 32 | Where possible we try to spell out how to go about implementing the feature. 33 | 34 | To start with, check out: 35 | - Issues labeled as ["good first issue"](https://github.com/launchbadge/sqlx/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). 36 | - Issues labeled as ["Easy"](https://github.com/launchbadge/sqlx/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy). 37 | 38 | Additionally, it's always good to work on improving/adding examples and documentation. 39 | 40 | ## Communication 41 | 42 | If you're unsure about your contribution or simply want to ask a question about anything, you can: 43 | - Visit the [SQLx Discord server](https://discord.gg/uuruzJ7) 44 | - Discuss something directly in the [Github issue](https://github.com/launchbadge/sqlx/issues). 45 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /contrib/ide/vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.assist.importMergeBehaviour": "last" 3 | } 4 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | sqlite/todos/todos.db 2 | -------------------------------------------------------------------------------- /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 | futures = "0.3" 10 | sqlx = { path = "../../../", features = [ "mysql", "runtime-tokio", "tls-native-tls" ] } 11 | clap = { version = "4", features = ["derive"] } 12 | tokio = { version = "1.20.0", features = ["rt", "macros"]} 13 | -------------------------------------------------------------------------------- /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/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/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 | once_cell = "1.13.0" 28 | thiserror = "2.0.0" 29 | tracing = "0.1.35" 30 | 31 | [dev-dependencies] 32 | serde_json = "1.0.82" 33 | tower = "0.4.13" 34 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /examples/postgres/axum-social-with-tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod http; 2 | 3 | mod password; 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | futures = "0.3.1" 10 | tokio = { version = "1.20.0", features = [ "rt-multi-thread", "macros" ] } 11 | ratatui = "0.27.0" 12 | crossterm = "0.27.0" 13 | unicode-width = "0.1" 14 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | ``` -------------------------------------------------------------------------------- /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/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/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/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 tranditional 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 | -------------------------------------------------------------------------------- /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 | futures = "0.3" 11 | serde = { version = "1", features = ["derive"] } 12 | serde_json = "1" 13 | sqlx = { path = "../../../", features = [ "runtime-tokio", "postgres", "json" ] } 14 | clap = { version = "4", features = ["derive"] } 15 | tokio = { version = "1.20.0", features = ["rt", "macros"]} 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 = "0.3.1" 10 | tokio = { version = "1.20.0", features = ["rt-multi-thread", "macros", "time"]} 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /examples/postgres/mockable-todos/.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL="postgres://postgres:password@localhost/todos" 2 | -------------------------------------------------------------------------------- /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 | futures = "0.3" 10 | sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] } 11 | clap = { version = "4", features = ["derive"] } 12 | tokio = { version = "1.20.0", features = ["rt", "macros"]} 13 | dotenvy = "0.15.0" 14 | async-trait = "0.1.41" 15 | mockall = "0.11" 16 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | futures = "0.3" 10 | sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] } 11 | clap = { version = "4", features = ["derive"] } 12 | tokio = { version = "1.20.0", features = ["rt", "macros"]} 13 | dotenvy = "0.15.0" 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | futures = "0.3.1" 10 | tokio = { version = "1.20.0", features = ["rt-multi-thread", "macros"]} 11 | -------------------------------------------------------------------------------- /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/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/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 | futures = "0.3" 10 | sqlx = { path = "../../../", features = [ "sqlite", "runtime-tokio", "tls-native-tls" ] } 11 | clap = { version = "4", features = ["derive"] } 12 | tokio = { version = "1.20.0", features = ["rt", "macros"]} 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-cli/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /sqlx-cli/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /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 | let Cli::Sqlx(opt) = Cli::parse(); 19 | 20 | if let Err(error) = sqlx_cli::run(opt).await { 21 | println!("{} {}", style("error:").bold().red(), error); 22 | process::exit(1); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /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 | let opt = Opt::parse(); 11 | 12 | // no special handling here 13 | if let Err(error) = sqlx_cli::run(opt).await { 14 | println!("{} {}", style("error:").bold().red(), error); 15 | std::process::exit(1); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /sqlx-cli/tests/migrations_reversible/20230101000000_test1.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE test1; 2 | -------------------------------------------------------------------------------- /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.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE test2; 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.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE test3; 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.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE test4; 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.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE test5; 2 | -------------------------------------------------------------------------------- /sqlx-cli/tests/migrations_reversible/20230501000000_test5.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE test5(x INTEGER PRIMARY KEY); 2 | -------------------------------------------------------------------------------- /sqlx-core/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /sqlx-core/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /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-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<'q> = AnyArguments<'q>; 29 | type ArgumentBuffer<'q> = AnyArgumentBuffer<'q>; 30 | 31 | type Statement<'q> = AnyStatement<'q>; 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /sqlx-core/src/any/transaction.rs: -------------------------------------------------------------------------------- 1 | use futures_util::future::BoxFuture; 2 | use std::borrow::Cow; 3 | 4 | use crate::any::{Any, AnyConnection}; 5 | use crate::database::Database; 6 | use crate::error::Error; 7 | use crate::transaction::TransactionManager; 8 | 9 | pub struct AnyTransactionManager; 10 | 11 | impl TransactionManager for AnyTransactionManager { 12 | type Database = Any; 13 | 14 | fn begin<'conn>( 15 | conn: &'conn mut AnyConnection, 16 | statement: Option>, 17 | ) -> BoxFuture<'conn, Result<(), Error>> { 18 | conn.backend.begin(statement) 19 | } 20 | 21 | fn commit(conn: &mut AnyConnection) -> BoxFuture<'_, Result<(), Error>> { 22 | conn.backend.commit() 23 | } 24 | 25 | fn rollback(conn: &mut AnyConnection) -> BoxFuture<'_, Result<(), Error>> { 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-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-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<'q> Encode<'q, Any> for bool { 17 | fn encode_by_ref( 18 | &self, 19 | buf: &mut ::ArgumentBuffer<'q>, 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-core/src/any/types/float.rs: -------------------------------------------------------------------------------- 1 | use crate::any::{Any, AnyArgumentBuffer, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind, AnyValueRef}; 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 f32 { 9 | fn type_info() -> AnyTypeInfo { 10 | AnyTypeInfo { 11 | kind: AnyTypeInfoKind::Real, 12 | } 13 | } 14 | } 15 | 16 | impl<'q> Encode<'q, Any> for f32 { 17 | fn encode_by_ref(&self, buf: &mut AnyArgumentBuffer<'q>) -> Result { 18 | buf.0.push(AnyValueKind::Real(*self)); 19 | Ok(IsNull::No) 20 | } 21 | } 22 | 23 | impl<'r> Decode<'r, Any> for f32 { 24 | fn decode(value: AnyValueRef<'r>) -> Result { 25 | match value.kind { 26 | AnyValueKind::Real(r) => Ok(r), 27 | other => other.unexpected(), 28 | } 29 | } 30 | } 31 | 32 | impl Type for f64 { 33 | fn type_info() -> AnyTypeInfo { 34 | AnyTypeInfo { 35 | kind: AnyTypeInfoKind::Double, 36 | } 37 | } 38 | } 39 | 40 | impl<'q> Encode<'q, Any> for f64 { 41 | fn encode_by_ref( 42 | &self, 43 | buf: &mut ::ArgumentBuffer<'q>, 44 | ) -> Result { 45 | buf.0.push(AnyValueKind::Double(*self)); 46 | Ok(IsNull::No) 47 | } 48 | } 49 | 50 | impl<'r> Decode<'r, Any> for f64 { 51 | fn decode(value: ::ValueRef<'r>) -> Result { 52 | match value.kind { 53 | // Widening is safe 54 | AnyValueKind::Real(r) => Ok(r as f64), 55 | AnyValueKind::Double(d) => Ok(d), 56 | other => other.unexpected(), 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /sqlx-core/src/ext/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ustr; 2 | 3 | #[macro_use] 4 | pub mod async_stream; 5 | -------------------------------------------------------------------------------- /sqlx-core/src/io/buf.rs: -------------------------------------------------------------------------------- 1 | use std::str::from_utf8; 2 | 3 | use bytes::{Buf, Bytes}; 4 | use memchr::memchr; 5 | 6 | use crate::error::Error; 7 | 8 | pub trait BufExt: Buf { 9 | // Read a nul-terminated byte sequence 10 | fn get_bytes_nul(&mut self) -> Result; 11 | 12 | // Read a byte sequence of the exact length 13 | fn get_bytes(&mut self, len: usize) -> Bytes; 14 | 15 | // Read a nul-terminated string 16 | fn get_str_nul(&mut self) -> Result; 17 | 18 | // Read a string of the exact length 19 | fn get_str(&mut self, len: usize) -> Result; 20 | } 21 | 22 | impl BufExt for Bytes { 23 | fn get_bytes_nul(&mut self) -> Result { 24 | let nul = 25 | memchr(b'\0', self).ok_or_else(|| err_protocol!("expected NUL in byte sequence"))?; 26 | 27 | let v = self.slice(0..nul); 28 | 29 | self.advance(nul + 1); 30 | 31 | Ok(v) 32 | } 33 | 34 | fn get_bytes(&mut self, len: usize) -> Bytes { 35 | let v = self.slice(..len); 36 | self.advance(len); 37 | 38 | v 39 | } 40 | 41 | fn get_str_nul(&mut self) -> Result { 42 | self.get_bytes_nul().and_then(|bytes| { 43 | from_utf8(&bytes) 44 | .map(ToOwned::to_owned) 45 | .map_err(|err| err_protocol!("{}", err)) 46 | }) 47 | } 48 | 49 | fn get_str(&mut self, len: usize) -> Result { 50 | let v = from_utf8(&self[..len]) 51 | .map_err(|err| err_protocol!("{}", err)) 52 | .map(ToOwned::to_owned)?; 53 | 54 | self.advance(len); 55 | 56 | Ok(v) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-core/src/migrate/error.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{BoxDynError, Error}; 2 | 3 | #[derive(Debug, thiserror::Error)] 4 | #[non_exhaustive] 5 | pub enum MigrateError { 6 | #[error("while executing migrations: {0}")] 7 | Execute(#[from] Error), 8 | 9 | #[error("while executing migration {1}: {0}")] 10 | ExecuteMigration(#[source] Error, i64), 11 | 12 | #[error("while resolving migrations: {0}")] 13 | Source(#[source] BoxDynError), 14 | 15 | #[error("migration {0} was previously applied but is missing in the resolved migrations")] 16 | VersionMissing(i64), 17 | 18 | #[error("migration {0} was previously applied but has been modified")] 19 | VersionMismatch(i64), 20 | 21 | #[error("migration {0} is not present in the migration source")] 22 | VersionNotPresent(i64), 23 | 24 | #[error("migration {0} is older than the latest applied migration {1}")] 25 | VersionTooOld(i64, i64), 26 | 27 | #[error("migration {0} is newer than the latest applied migration {1}")] 28 | VersionTooNew(i64, i64), 29 | 30 | #[error("database driver does not support force-dropping a database (Only PostgreSQL)")] 31 | ForceNotSupported, 32 | 33 | #[deprecated = "migration types are now inferred"] 34 | #[error("cannot mix reversible migrations with simple migrations. All migrations should be reversible or simple migrations")] 35 | InvalidMixReversibleAndSimple, 36 | 37 | // NOTE: this will only happen with a database that does not have transactional DDL (.e.g, MySQL or Oracle) 38 | #[error( 39 | "migration {0} is partially applied; fix and remove row from `_sqlx_migrations` table" 40 | )] 41 | Dirty(i64), 42 | } 43 | -------------------------------------------------------------------------------- /sqlx-core/src/migrate/migration.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use sha2::{Digest, Sha384}; 4 | 5 | use super::MigrationType; 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct Migration { 9 | pub version: i64, 10 | pub description: Cow<'static, str>, 11 | pub migration_type: MigrationType, 12 | pub sql: Cow<'static, str>, 13 | pub checksum: Cow<'static, [u8]>, 14 | pub no_tx: bool, 15 | } 16 | 17 | impl Migration { 18 | pub fn new( 19 | version: i64, 20 | description: Cow<'static, str>, 21 | migration_type: MigrationType, 22 | sql: Cow<'static, str>, 23 | no_tx: bool, 24 | ) -> Self { 25 | let checksum = Cow::Owned(Vec::from(Sha384::digest(sql.as_bytes()).as_slice())); 26 | 27 | Migration { 28 | version, 29 | description, 30 | migration_type, 31 | sql, 32 | checksum, 33 | no_tx, 34 | } 35 | } 36 | } 37 | 38 | #[derive(Debug, Clone)] 39 | pub struct AppliedMigration { 40 | pub version: i64, 41 | pub checksum: Cow<'static, [u8]>, 42 | } 43 | -------------------------------------------------------------------------------- /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; 15 | 16 | #[doc(hidden)] 17 | pub use source::resolve_blocking; 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /sqlx-core/src/net/tls/util.rs: -------------------------------------------------------------------------------- 1 | use crate::net::Socket; 2 | 3 | use std::io::{self, Read, Write}; 4 | use std::task::{ready, Context, Poll}; 5 | 6 | use futures_util::future; 7 | 8 | pub struct StdSocket { 9 | pub socket: S, 10 | wants_read: bool, 11 | wants_write: bool, 12 | } 13 | 14 | impl StdSocket { 15 | pub fn new(socket: S) -> Self { 16 | Self { 17 | socket, 18 | wants_read: false, 19 | wants_write: false, 20 | } 21 | } 22 | 23 | pub fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 24 | if self.wants_write { 25 | ready!(self.socket.poll_write_ready(cx))?; 26 | self.wants_write = false; 27 | } 28 | 29 | if self.wants_read { 30 | ready!(self.socket.poll_read_ready(cx))?; 31 | self.wants_read = false; 32 | } 33 | 34 | Poll::Ready(Ok(())) 35 | } 36 | 37 | pub async fn ready(&mut self) -> io::Result<()> { 38 | future::poll_fn(|cx| self.poll_ready(cx)).await 39 | } 40 | } 41 | 42 | impl Read for StdSocket { 43 | fn read(&mut self, mut buf: &mut [u8]) -> io::Result { 44 | self.wants_read = true; 45 | let read = self.socket.try_read(&mut buf)?; 46 | self.wants_read = false; 47 | 48 | Ok(read) 49 | } 50 | } 51 | 52 | impl Write for StdSocket { 53 | fn write(&mut self, buf: &[u8]) -> io::Result { 54 | self.wants_write = true; 55 | let written = self.socket.try_write(buf)?; 56 | self.wants_write = false; 57 | Ok(written) 58 | } 59 | 60 | fn flush(&mut self) -> io::Result<()> { 61 | // NOTE: TCP sockets and unix sockets are both no-ops for flushes 62 | Ok(()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /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-core/src/rt/rt_async_std/mod.rs: -------------------------------------------------------------------------------- 1 | mod socket; 2 | -------------------------------------------------------------------------------- /sqlx-core/src/rt/rt_async_std/socket.rs: -------------------------------------------------------------------------------- 1 | use crate::net::Socket; 2 | 3 | use std::io; 4 | use std::io::{Read, Write}; 5 | use std::net::{Shutdown, TcpStream}; 6 | 7 | use std::task::{Context, Poll}; 8 | 9 | use crate::io::ReadBuf; 10 | use async_io::Async; 11 | 12 | impl Socket for Async { 13 | fn try_read(&mut self, buf: &mut dyn ReadBuf) -> io::Result { 14 | self.get_mut().read(buf.init_mut()) 15 | } 16 | 17 | fn try_write(&mut self, buf: &[u8]) -> io::Result { 18 | self.get_mut().write(buf) 19 | } 20 | 21 | fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 22 | self.poll_readable(cx) 23 | } 24 | 25 | fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 26 | self.poll_writable(cx) 27 | } 28 | 29 | fn poll_shutdown(&mut self, _cx: &mut Context<'_>) -> Poll> { 30 | Poll::Ready(self.get_mut().shutdown(Shutdown::Both)) 31 | } 32 | } 33 | 34 | #[cfg(unix)] 35 | impl Socket for Async { 36 | fn try_read(&mut self, buf: &mut dyn ReadBuf) -> io::Result { 37 | self.get_mut().read(buf.init_mut()) 38 | } 39 | 40 | fn try_write(&mut self, buf: &[u8]) -> io::Result { 41 | self.get_mut().write(buf) 42 | } 43 | 44 | fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 45 | self.poll_readable(cx) 46 | } 47 | 48 | fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 49 | self.poll_writable(cx) 50 | } 51 | 52 | fn poll_shutdown(&mut self, _cx: &mut Context<'_>) -> Poll> { 53 | Poll::Ready(self.get_mut().shutdown(Shutdown::Both)) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /sqlx-core/src/rt/rt_tokio/socket.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::pin::Pin; 3 | use std::task::{Context, Poll}; 4 | 5 | use tokio::io::AsyncWrite; 6 | use tokio::net::TcpStream; 7 | 8 | use crate::io::ReadBuf; 9 | use crate::net::Socket; 10 | 11 | impl Socket for TcpStream { 12 | fn try_read(&mut self, mut buf: &mut dyn ReadBuf) -> io::Result { 13 | // Requires `&mut impl BufMut` 14 | self.try_read_buf(&mut buf) 15 | } 16 | 17 | fn try_write(&mut self, buf: &[u8]) -> io::Result { 18 | (*self).try_write(buf) 19 | } 20 | 21 | fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 22 | (*self).poll_read_ready(cx) 23 | } 24 | 25 | fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 26 | (*self).poll_write_ready(cx) 27 | } 28 | 29 | fn poll_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> { 30 | Pin::new(self).poll_shutdown(cx) 31 | } 32 | } 33 | 34 | #[cfg(unix)] 35 | impl Socket for tokio::net::UnixStream { 36 | fn try_read(&mut self, mut buf: &mut dyn ReadBuf) -> io::Result { 37 | self.try_read_buf(&mut buf) 38 | } 39 | 40 | fn try_write(&mut self, buf: &[u8]) -> io::Result { 41 | (*self).try_write(buf) 42 | } 43 | 44 | fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 45 | (*self).poll_read_ready(cx) 46 | } 47 | 48 | fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 49 | (*self).poll_write_ready(cx) 50 | } 51 | 52 | fn poll_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> { 53 | Pin::new(self).poll_shutdown(cx) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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<'q>, 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<'q>, 56 | ) -> Result { 57 | as Encode>::encode(self.as_bytes().to_vec(), buf) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /sqlx-macros-core/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /sqlx-macros-core/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /sqlx-macros-core/src/common.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | use std::env; 3 | use std::path::{Path, PathBuf}; 4 | 5 | pub(crate) fn resolve_path(path: impl AsRef, err_span: Span) -> syn::Result { 6 | let path = path.as_ref(); 7 | 8 | if path.is_absolute() { 9 | return Err(syn::Error::new( 10 | err_span, 11 | "absolute paths will only work on the current machine", 12 | )); 13 | } 14 | 15 | // requires `proc_macro::SourceFile::path()` to be stable 16 | // https://github.com/rust-lang/rust/issues/54725 17 | if path.is_relative() 18 | && path 19 | .parent() 20 | .is_none_or(|parent| parent.as_os_str().is_empty()) 21 | { 22 | return Err(syn::Error::new( 23 | err_span, 24 | "paths relative to the current file's directory are not currently supported", 25 | )); 26 | } 27 | 28 | let base_dir = env::var("CARGO_MANIFEST_DIR").map_err(|_| { 29 | syn::Error::new( 30 | err_span, 31 | "CARGO_MANIFEST_DIR is not set; please use Cargo to build", 32 | ) 33 | })?; 34 | let base_dir_path = Path::new(&base_dir); 35 | 36 | Ok(base_dir_path.join(path)) 37 | } 38 | -------------------------------------------------------------------------------- /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-macros/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /sqlx-macros/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /sqlx-mysql/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /sqlx-mysql/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /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(skip))] 14 | pub(crate) flags: Option, 15 | } 16 | 17 | impl Column for MySqlColumn { 18 | type Database = MySql; 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) -> &MySqlTypeInfo { 29 | &self.type_info 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /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<'q> = MySqlArguments; 29 | type ArgumentBuffer<'q> = Vec; 30 | 31 | type Statement<'q> = MySqlStatement<'q>; 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-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/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 collation: 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.collation); 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-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-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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/response/ok.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | 3 | use crate::error::Error; 4 | use crate::io::MySqlBufExt; 5 | use crate::io::ProtocolDecode; 6 | use crate::protocol::response::Status; 7 | 8 | /// Indicates successful completion of a previous command sent by the client. 9 | #[derive(Debug)] 10 | pub struct OkPacket { 11 | pub affected_rows: u64, 12 | pub last_insert_id: u64, 13 | pub status: Status, 14 | pub warnings: u16, 15 | } 16 | 17 | impl ProtocolDecode<'_> for OkPacket { 18 | fn decode_with(mut buf: Bytes, _: ()) -> Result { 19 | let header = buf.get_u8(); 20 | if header != 0 && header != 0xfe { 21 | return Err(err_protocol!( 22 | "expected 0x00 or 0xfe (OK_Packet) but found 0x{:02x}", 23 | header 24 | )); 25 | } 26 | 27 | let affected_rows = buf.get_uint_lenenc(); 28 | let last_insert_id = buf.get_uint_lenenc(); 29 | let status = Status::from_bits_truncate(buf.get_u16_le()); 30 | let warnings = buf.get_u16_le(); 31 | 32 | Ok(Self { 33 | affected_rows, 34 | last_insert_id, 35 | status, 36 | warnings, 37 | }) 38 | } 39 | } 40 | 41 | #[test] 42 | fn test_decode_ok_packet() { 43 | const DATA: &[u8] = b"\x00\x00\x00\x02@\x00\x00"; 44 | 45 | let p = OkPacket::decode(DATA.into()).unwrap(); 46 | 47 | assert_eq!(p.affected_rows, 0); 48 | assert_eq!(p.last_insert_id, 0); 49 | assert_eq!(p.warnings, 0); 50 | assert!(p.status.contains(Status::SERVER_STATUS_AUTOCOMMIT)); 51 | assert!(p.status.contains(Status::SERVER_SESSION_STATE_CHANGED)); 52 | } 53 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /sqlx-mysql/src/protocol/text/row.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | 3 | use crate::column::MySqlColumn; 4 | use crate::error::Error; 5 | use crate::io::MySqlBufExt; 6 | use crate::io::ProtocolDecode; 7 | use crate::protocol::Row; 8 | 9 | #[derive(Debug)] 10 | pub(crate) struct TextRow(pub(crate) Row); 11 | 12 | impl<'de> ProtocolDecode<'de, &'de [MySqlColumn]> for TextRow { 13 | fn decode_with(mut buf: Bytes, columns: &'de [MySqlColumn]) -> Result { 14 | let storage = buf.clone(); 15 | let offset = buf.len(); 16 | 17 | let mut values = Vec::with_capacity(columns.len()); 18 | 19 | for c in columns { 20 | if buf[0] == 0xfb { 21 | // NULL is sent as 0xfb 22 | values.push(None); 23 | buf.advance(1); 24 | } else { 25 | let size = buf.get_uint_lenenc(); 26 | if (buf.remaining() as u64) < size { 27 | return Err(err_protocol!( 28 | "buffer exhausted when reading data for column {:?}; decoded length is {}, but only {} bytes remain in buffer. Malformed packet or protocol error?", 29 | c, 30 | size, 31 | buf.remaining())); 32 | } 33 | let size = usize::try_from(size) 34 | .map_err(|_| err_protocol!("TextRow length out of range: {size}"))?; 35 | 36 | let offset = offset - buf.len(); 37 | 38 | values.push(Some(offset..(offset + size))); 39 | 40 | buf.advance(size); 41 | } 42 | } 43 | 44 | Ok(TextRow(Row { values, storage })) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | #[derive(Debug)] 13 | pub struct MySqlRow { 14 | pub(crate) row: protocol::Row, 15 | pub(crate) format: MySqlValueFormat, 16 | pub(crate) columns: Arc>, 17 | pub(crate) column_names: Arc>, 18 | } 19 | 20 | impl Row for MySqlRow { 21 | type Database = MySql; 22 | 23 | fn columns(&self) -> &[MySqlColumn] { 24 | &self.columns 25 | } 26 | 27 | fn try_get_raw(&self, index: I) -> Result, Error> 28 | where 29 | I: ColumnIndex, 30 | { 31 | let index = index.index(self)?; 32 | let column = &self.columns[index]; 33 | let value = self.row.get(index); 34 | 35 | Ok(MySqlValueRef { 36 | format: self.format, 37 | row: Some(&self.row.storage), 38 | type_info: column.type_info.clone(), 39 | value, 40 | }) 41 | } 42 | } 43 | 44 | impl ColumnIndex for &'_ str { 45 | fn index(&self, row: &MySqlRow) -> Result { 46 | row.column_names 47 | .get(*self) 48 | .ok_or_else(|| Error::ColumnNotFound((*self).into())) 49 | .copied() 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 std::borrow::Cow; 9 | use std::sync::Arc; 10 | 11 | pub(crate) use sqlx_core::statement::*; 12 | 13 | #[derive(Debug, Clone)] 14 | pub struct MySqlStatement<'q> { 15 | pub(crate) sql: Cow<'q, str>, 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<'q> Statement<'q> for MySqlStatement<'q> { 27 | type Database = MySql; 28 | 29 | fn to_owned(&self) -> MySqlStatement<'static> { 30 | MySqlStatement::<'static> { 31 | sql: Cow::Owned(self.sql.clone().into_owned()), 32 | metadata: self.metadata.clone(), 33 | } 34 | } 35 | 36 | fn sql(&self) -> &str { 37 | &self.sql 38 | } 39 | 40 | fn parameters(&self) -> Option> { 41 | Some(Either::Right(self.metadata.parameters)) 42 | } 43 | 44 | fn columns(&self) -> &[MySqlColumn] { 45 | &self.metadata.columns 46 | } 47 | 48 | impl_statement_query!(MySqlArguments); 49 | } 50 | 51 | impl ColumnIndex> for &'_ str { 52 | fn index(&self, statement: &MySqlStatement<'_>) -> Result { 53 | statement 54 | .metadata 55 | .column_names 56 | .get(*self) 57 | .ok_or_else(|| Error::ColumnNotFound((*self).into())) 58 | .copied() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /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-mysql/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::{ 6 | protocol::text::{ColumnFlags, ColumnType}, 7 | MySql, MySqlTypeInfo, MySqlValueRef, 8 | }; 9 | 10 | impl Type for bool { 11 | fn type_info() -> MySqlTypeInfo { 12 | // MySQL has no actual `BOOLEAN` type, the type is an alias of `TINYINT(1)` 13 | MySqlTypeInfo { 14 | flags: ColumnFlags::BINARY | ColumnFlags::UNSIGNED, 15 | max_size: Some(1), 16 | r#type: ColumnType::Tiny, 17 | } 18 | } 19 | 20 | fn compatible(ty: &MySqlTypeInfo) -> bool { 21 | matches!( 22 | ty.r#type, 23 | ColumnType::Tiny 24 | | ColumnType::Short 25 | | ColumnType::Long 26 | | ColumnType::Int24 27 | | ColumnType::LongLong 28 | | ColumnType::Bit 29 | ) 30 | } 31 | } 32 | 33 | impl Encode<'_, MySql> for bool { 34 | fn encode_by_ref(&self, buf: &mut Vec) -> Result { 35 | >::encode(*self as i8, buf) 36 | } 37 | } 38 | 39 | impl Decode<'_, MySql> for bool { 40 | fn decode(value: MySqlValueRef<'_>) -> Result { 41 | Ok(>::decode(value)? != 0) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /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-postgres/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /sqlx-postgres/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /sqlx-postgres/src/column.rs: -------------------------------------------------------------------------------- 1 | use crate::ext::ustr::UStr; 2 | use crate::{PgTypeInfo, Postgres}; 3 | 4 | pub(crate) use sqlx_core::column::{Column, ColumnIndex}; 5 | 6 | #[derive(Debug, Clone)] 7 | #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] 8 | pub struct PgColumn { 9 | pub(crate) ordinal: usize, 10 | pub(crate) name: UStr, 11 | pub(crate) type_info: PgTypeInfo, 12 | #[cfg_attr(feature = "offline", serde(skip))] 13 | pub(crate) relation_id: Option, 14 | #[cfg_attr(feature = "offline", serde(skip))] 15 | pub(crate) relation_attribute_no: Option, 16 | } 17 | 18 | impl PgColumn { 19 | /// Returns the OID of the table this column is from, if applicable. 20 | /// 21 | /// This will be `None` if the column is the result of an expression. 22 | /// 23 | /// Corresponds to column `attrelid` of the `pg_catalog.pg_attribute` table: 24 | /// 25 | pub fn relation_id(&self) -> Option { 26 | self.relation_id 27 | } 28 | 29 | /// Returns the 1-based index of this column in its parent table, if applicable. 30 | /// 31 | /// This will be `None` if the column is the result of an expression. 32 | /// 33 | /// Corresponds to column `attnum` of the `pg_catalog.pg_attribute` table: 34 | /// 35 | pub fn relation_attribute_no(&self) -> Option { 36 | self.relation_attribute_no 37 | } 38 | } 39 | 40 | impl Column for PgColumn { 41 | type Database = Postgres; 42 | 43 | fn ordinal(&self) -> usize { 44 | self.ordinal 45 | } 46 | 47 | fn name(&self) -> &str { 48 | &self.name 49 | } 50 | 51 | fn type_info(&self) -> &PgTypeInfo { 52 | &self.type_info 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /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<'q> = PgArguments; 31 | type ArgumentBuffer<'q> = PgArgumentBuffer; 32 | 33 | type Statement<'q> = PgStatement<'q>; 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 | -------------------------------------------------------------------------------- /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/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-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-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/message/parameter_description.rs: -------------------------------------------------------------------------------- 1 | use smallvec::SmallVec; 2 | use sqlx_core::bytes::{Buf, Bytes}; 3 | 4 | use crate::error::Error; 5 | use crate::message::{BackendMessage, BackendMessageFormat}; 6 | use crate::types::Oid; 7 | 8 | #[derive(Debug)] 9 | pub struct ParameterDescription { 10 | pub types: SmallVec<[Oid; 6]>, 11 | } 12 | 13 | impl BackendMessage for ParameterDescription { 14 | const FORMAT: BackendMessageFormat = BackendMessageFormat::ParameterDescription; 15 | 16 | fn decode_body(mut buf: Bytes) -> Result { 17 | // Note: this is correct, max parameters is 65535, not 32767 18 | // https://github.com/launchbadge/sqlx/issues/3464 19 | let cnt = buf.get_u16(); 20 | let mut types = SmallVec::with_capacity(cnt as usize); 21 | 22 | for _ in 0..cnt { 23 | types.push(Oid(buf.get_u32())); 24 | } 25 | 26 | Ok(Self { types }) 27 | } 28 | } 29 | 30 | #[test] 31 | fn test_decode_parameter_description() { 32 | const DATA: &[u8] = b"\x00\x02\x00\x00\x00\x00\x00\x00\x05\x00"; 33 | 34 | let m = ParameterDescription::decode_body(DATA.into()).unwrap(); 35 | 36 | assert_eq!(m.types.len(), 2); 37 | assert_eq!(m.types[0], Oid(0x0000_0000)); 38 | assert_eq!(m.types[1], Oid(0x0000_0500)); 39 | } 40 | 41 | #[test] 42 | fn test_decode_empty_parameter_description() { 43 | const DATA: &[u8] = b"\x00\x00"; 44 | 45 | let m = ParameterDescription::decode_body(DATA.into()).unwrap(); 46 | 47 | assert!(m.types.is_empty()); 48 | } 49 | 50 | #[cfg(all(test, not(debug_assertions)))] 51 | #[bench] 52 | fn bench_decode_parameter_description(b: &mut test::Bencher) { 53 | const DATA: &[u8] = b"\x00\x02\x00\x00\x00\x00\x00\x00\x05\x00"; 54 | 55 | b.iter(|| { 56 | ParameterDescription::decode_body(test::black_box(Bytes::from_static(DATA))).unwrap(); 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /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-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-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 | -------------------------------------------------------------------------------- /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-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 | -------------------------------------------------------------------------------- /sqlx-postgres/src/options/connect.rs: -------------------------------------------------------------------------------- 1 | use crate::connection::ConnectOptions; 2 | use crate::error::Error; 3 | use crate::{PgConnectOptions, PgConnection}; 4 | use futures_core::future::BoxFuture; 5 | use log::LevelFilter; 6 | use sqlx_core::Url; 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) -> BoxFuture<'_, Result> 21 | where 22 | Self::Connection: Sized, 23 | { 24 | Box::pin(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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /sqlx-postgres/src/types/chrono/mod.rs: -------------------------------------------------------------------------------- 1 | mod date; 2 | mod datetime; 3 | mod time; 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 | -------------------------------------------------------------------------------- /sqlx-postgres/src/types/ipnet/ipaddr.rs: -------------------------------------------------------------------------------- 1 | use std::net::IpAddr; 2 | 3 | use ipnet::IpNet; 4 | 5 | use crate::decode::Decode; 6 | use crate::encode::{Encode, IsNull}; 7 | use crate::error::BoxDynError; 8 | use crate::types::Type; 9 | use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef, Postgres}; 10 | 11 | impl Type for IpAddr 12 | where 13 | IpNet: Type, 14 | { 15 | fn type_info() -> PgTypeInfo { 16 | IpNet::type_info() 17 | } 18 | 19 | fn compatible(ty: &PgTypeInfo) -> bool { 20 | IpNet::compatible(ty) 21 | } 22 | } 23 | 24 | impl PgHasArrayType for IpAddr { 25 | fn array_type_info() -> PgTypeInfo { 26 | ::array_type_info() 27 | } 28 | 29 | fn array_compatible(ty: &PgTypeInfo) -> bool { 30 | ::array_compatible(ty) 31 | } 32 | } 33 | 34 | impl<'db> Encode<'db, Postgres> for IpAddr 35 | where 36 | IpNet: Encode<'db, Postgres>, 37 | { 38 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { 39 | IpNet::from(*self).encode_by_ref(buf) 40 | } 41 | 42 | fn size_hint(&self) -> usize { 43 | IpNet::from(*self).size_hint() 44 | } 45 | } 46 | 47 | impl<'db> Decode<'db, Postgres> for IpAddr 48 | where 49 | IpNet: Decode<'db, Postgres>, 50 | { 51 | fn decode(value: PgValueRef<'db>) -> Result { 52 | let ipnet = IpNet::decode(value)?; 53 | 54 | if matches!(ipnet, IpNet::V4(net) if net.prefix_len() != 32) 55 | || matches!(ipnet, IpNet::V6(net) if net.prefix_len() != 128) 56 | { 57 | Err("lossy decode from inet/cidr")? 58 | } 59 | 60 | Ok(ipnet.addr()) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /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-postgres/src/types/ipnetwork/ipaddr.rs: -------------------------------------------------------------------------------- 1 | use std::net::IpAddr; 2 | 3 | use ipnetwork::IpNetwork; 4 | 5 | use crate::decode::Decode; 6 | use crate::encode::{Encode, IsNull}; 7 | use crate::error::BoxDynError; 8 | use crate::types::Type; 9 | use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef, Postgres}; 10 | 11 | impl Type for IpAddr 12 | where 13 | IpNetwork: Type, 14 | { 15 | fn type_info() -> PgTypeInfo { 16 | IpNetwork::type_info() 17 | } 18 | 19 | fn compatible(ty: &PgTypeInfo) -> bool { 20 | IpNetwork::compatible(ty) 21 | } 22 | } 23 | 24 | impl PgHasArrayType for IpAddr { 25 | fn array_type_info() -> PgTypeInfo { 26 | ::array_type_info() 27 | } 28 | 29 | fn array_compatible(ty: &PgTypeInfo) -> bool { 30 | ::array_compatible(ty) 31 | } 32 | } 33 | 34 | impl<'db> Encode<'db, Postgres> for IpAddr 35 | where 36 | IpNetwork: Encode<'db, Postgres>, 37 | { 38 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { 39 | IpNetwork::from(*self).encode_by_ref(buf) 40 | } 41 | 42 | fn size_hint(&self) -> usize { 43 | IpNetwork::from(*self).size_hint() 44 | } 45 | } 46 | 47 | impl<'db> Decode<'db, Postgres> for IpAddr 48 | where 49 | IpNetwork: Decode<'db, Postgres>, 50 | { 51 | fn decode(value: PgValueRef<'db>) -> Result { 52 | let ipnetwork = IpNetwork::decode(value)?; 53 | 54 | if ipnetwork.is_ipv4() && ipnetwork.prefix() != 32 55 | || ipnetwork.is_ipv6() && ipnetwork.prefix() != 128 56 | { 57 | Err("lossy decode from inet/cidr")? 58 | } 59 | 60 | Ok(ipnetwork.ip()) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /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-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-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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /sqlx-postgres/src/types/time/date.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::Decode; 2 | use crate::encode::{Encode, IsNull}; 3 | use crate::error::BoxDynError; 4 | use crate::types::time::PG_EPOCH; 5 | use crate::types::Type; 6 | use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; 7 | use std::mem; 8 | use time::macros::format_description; 9 | use time::{Date, Duration}; 10 | 11 | impl Type for Date { 12 | fn type_info() -> PgTypeInfo { 13 | PgTypeInfo::DATE 14 | } 15 | } 16 | 17 | impl PgHasArrayType for Date { 18 | fn array_type_info() -> PgTypeInfo { 19 | PgTypeInfo::DATE_ARRAY 20 | } 21 | } 22 | 23 | impl Encode<'_, Postgres> for Date { 24 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { 25 | // DATE is encoded as number of days since epoch (2000-01-01) 26 | let days: i32 = (*self - PG_EPOCH).whole_days().try_into().map_err(|_| { 27 | format!("value {self:?} would overflow binary encoding for Postgres DATE") 28 | })?; 29 | Encode::::encode(days, buf) 30 | } 31 | 32 | fn size_hint(&self) -> usize { 33 | mem::size_of::() 34 | } 35 | } 36 | 37 | impl<'r> Decode<'r, Postgres> for Date { 38 | fn decode(value: PgValueRef<'r>) -> Result { 39 | Ok(match value.format() { 40 | PgValueFormat::Binary => { 41 | // DATE is encoded as the days since epoch 42 | let days: i32 = Decode::::decode(value)?; 43 | PG_EPOCH + Duration::days(days.into()) 44 | } 45 | 46 | PgValueFormat::Text => Date::parse( 47 | value.as_str()?, 48 | &format_description!("[year]-[month]-[day]"), 49 | )?, 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /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-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-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 | -------------------------------------------------------------------------------- /sqlx-sqlite/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /sqlx-sqlite/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /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 | 14 | impl Column for SqliteColumn { 15 | type Database = Sqlite; 16 | 17 | fn ordinal(&self) -> usize { 18 | self.ordinal 19 | } 20 | 21 | fn name(&self) -> &str { 22 | &self.name 23 | } 24 | 25 | fn type_info(&self) -> &SqliteTypeInfo { 26 | &self.type_info 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sqlx-sqlite/src/database.rs: -------------------------------------------------------------------------------- 1 | pub(crate) use sqlx_core::database::{Database, HasStatementCache}; 2 | 3 | use crate::{ 4 | SqliteArgumentValue, SqliteArguments, SqliteColumn, SqliteConnection, SqliteQueryResult, 5 | SqliteRow, SqliteStatement, SqliteTransactionManager, SqliteTypeInfo, SqliteValue, 6 | 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<'q> = SqliteArguments<'q>; 30 | type ArgumentBuffer<'q> = Vec>; 31 | 32 | type Statement<'q> = SqliteStatement<'q>; 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/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-sqlite/src/options/journal_mode.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use std::str::FromStr; 3 | 4 | /// Refer to [SQLite documentation] for the meaning of the database journaling mode. 5 | /// 6 | /// [SQLite documentation]: https://www.sqlite.org/pragma.html#pragma_journal_mode 7 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 8 | pub enum SqliteJournalMode { 9 | Delete, 10 | Truncate, 11 | Persist, 12 | Memory, 13 | #[default] 14 | Wal, 15 | Off, 16 | } 17 | 18 | impl SqliteJournalMode { 19 | pub(crate) fn as_str(&self) -> &'static str { 20 | match self { 21 | SqliteJournalMode::Delete => "DELETE", 22 | SqliteJournalMode::Truncate => "TRUNCATE", 23 | SqliteJournalMode::Persist => "PERSIST", 24 | SqliteJournalMode::Memory => "MEMORY", 25 | SqliteJournalMode::Wal => "WAL", 26 | SqliteJournalMode::Off => "OFF", 27 | } 28 | } 29 | } 30 | 31 | impl FromStr for SqliteJournalMode { 32 | type Err = Error; 33 | 34 | fn from_str(s: &str) -> Result { 35 | Ok(match &*s.to_ascii_lowercase() { 36 | "delete" => SqliteJournalMode::Delete, 37 | "truncate" => SqliteJournalMode::Truncate, 38 | "persist" => SqliteJournalMode::Persist, 39 | "memory" => SqliteJournalMode::Memory, 40 | "wal" => SqliteJournalMode::Wal, 41 | "off" => SqliteJournalMode::Off, 42 | 43 | _ => { 44 | return Err(Error::Configuration( 45 | format!("unknown value {s:?} for `journal_mode`").into(), 46 | )); 47 | } 48 | }) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /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-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 | -------------------------------------------------------------------------------- /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/statement/unlock_notify.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_void; 2 | use std::os::raw::c_int; 3 | use std::slice; 4 | use std::sync::{Condvar, Mutex}; 5 | 6 | use libsqlite3_sys::{sqlite3, sqlite3_unlock_notify, SQLITE_OK}; 7 | 8 | use crate::SqliteError; 9 | 10 | // Wait for unlock notification (https://www.sqlite.org/unlock_notify.html) 11 | pub unsafe fn wait(conn: *mut sqlite3) -> Result<(), SqliteError> { 12 | let notify = Notify::new(); 13 | 14 | if sqlite3_unlock_notify( 15 | conn, 16 | Some(unlock_notify_cb), 17 | ¬ify as *const Notify as *mut Notify as *mut _, 18 | ) != SQLITE_OK 19 | { 20 | return Err(SqliteError::new(conn)); 21 | } 22 | 23 | notify.wait(); 24 | 25 | Ok(()) 26 | } 27 | 28 | unsafe extern "C" fn unlock_notify_cb(ptr: *mut *mut c_void, len: c_int) { 29 | let ptr = ptr as *mut &Notify; 30 | // We don't have a choice; we can't panic and unwind into FFI here. 31 | let slice = slice::from_raw_parts(ptr, usize::try_from(len).unwrap_or(0)); 32 | 33 | for notify in slice { 34 | notify.fire(); 35 | } 36 | } 37 | 38 | struct Notify { 39 | mutex: Mutex, 40 | condvar: Condvar, 41 | } 42 | 43 | impl Notify { 44 | fn new() -> Self { 45 | Self { 46 | mutex: Mutex::new(false), 47 | condvar: Condvar::new(), 48 | } 49 | } 50 | 51 | fn wait(&self) { 52 | // We only want to wait until the lock is available again. 53 | #[allow(let_underscore_lock)] 54 | let _ = self 55 | .condvar 56 | .wait_while(self.mutex.lock().unwrap(), |fired| !*fired) 57 | .unwrap(); 58 | } 59 | 60 | fn fire(&self) { 61 | let mut lock = self.mutex.lock().unwrap(); 62 | *lock = true; 63 | self.condvar.notify_one(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /sqlx-sqlite/src/transaction.rs: -------------------------------------------------------------------------------- 1 | use futures_core::future::BoxFuture; 2 | use std::borrow::Cow; 3 | 4 | use sqlx_core::error::Error; 5 | use sqlx_core::transaction::TransactionManager; 6 | 7 | use crate::{Sqlite, SqliteConnection}; 8 | 9 | /// Implementation of [`TransactionManager`] for SQLite. 10 | pub struct SqliteTransactionManager; 11 | 12 | impl TransactionManager for SqliteTransactionManager { 13 | type Database = Sqlite; 14 | 15 | fn begin<'conn>( 16 | conn: &'conn mut SqliteConnection, 17 | statement: Option>, 18 | ) -> BoxFuture<'conn, Result<(), Error>> { 19 | Box::pin(async { 20 | let is_custom_statement = statement.is_some(); 21 | conn.worker.begin(statement).await?; 22 | if is_custom_statement { 23 | // Check that custom statement actually put the connection into a transaction. 24 | let mut handle = conn.lock_handle().await?; 25 | if !handle.in_transaction() { 26 | return Err(Error::BeginFailed); 27 | } 28 | } 29 | Ok(()) 30 | }) 31 | } 32 | 33 | fn commit(conn: &mut SqliteConnection) -> BoxFuture<'_, Result<(), Error>> { 34 | Box::pin(conn.worker.commit()) 35 | } 36 | 37 | fn rollback(conn: &mut SqliteConnection) -> BoxFuture<'_, Result<(), Error>> { 38 | Box::pin(conn.worker.rollback()) 39 | } 40 | 41 | fn start_rollback(conn: &mut SqliteConnection) { 42 | conn.worker.start_rollback().ok(); 43 | } 44 | 45 | fn get_transaction_depth(conn: &SqliteConnection) -> usize { 46 | conn.worker.shared.get_transaction_depth() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /sqlx-sqlite/src/types/bool.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::Decode; 2 | use crate::encode::{Encode, IsNull}; 3 | use crate::error::BoxDynError; 4 | use crate::type_info::DataType; 5 | use crate::types::Type; 6 | use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; 7 | 8 | impl Type for bool { 9 | fn type_info() -> SqliteTypeInfo { 10 | SqliteTypeInfo(DataType::Bool) 11 | } 12 | 13 | fn compatible(ty: &SqliteTypeInfo) -> bool { 14 | matches!(ty.0, DataType::Bool | DataType::Int4 | DataType::Integer) 15 | } 16 | } 17 | 18 | impl<'q> Encode<'q, Sqlite> for bool { 19 | fn encode_by_ref( 20 | &self, 21 | args: &mut Vec>, 22 | ) -> Result { 23 | args.push(SqliteArgumentValue::Int((*self).into())); 24 | 25 | Ok(IsNull::No) 26 | } 27 | } 28 | 29 | impl<'r> Decode<'r, Sqlite> for bool { 30 | fn decode(value: SqliteValueRef<'r>) -> Result { 31 | Ok(value.int64() != 0) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sqlx-sqlite/src/types/float.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::Decode; 2 | use crate::encode::{Encode, IsNull}; 3 | use crate::error::BoxDynError; 4 | use crate::type_info::DataType; 5 | use crate::types::Type; 6 | use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; 7 | 8 | impl Type for f32 { 9 | fn type_info() -> SqliteTypeInfo { 10 | SqliteTypeInfo(DataType::Float) 11 | } 12 | } 13 | 14 | impl<'q> Encode<'q, Sqlite> for f32 { 15 | fn encode_by_ref( 16 | &self, 17 | args: &mut Vec>, 18 | ) -> Result { 19 | args.push(SqliteArgumentValue::Double((*self).into())); 20 | 21 | Ok(IsNull::No) 22 | } 23 | } 24 | 25 | impl<'r> Decode<'r, Sqlite> for f32 { 26 | fn decode(value: SqliteValueRef<'r>) -> Result { 27 | // Truncation is intentional 28 | #[allow(clippy::cast_possible_truncation)] 29 | Ok(value.double() as f32) 30 | } 31 | } 32 | 33 | impl Type for f64 { 34 | fn type_info() -> SqliteTypeInfo { 35 | SqliteTypeInfo(DataType::Float) 36 | } 37 | } 38 | 39 | impl<'q> Encode<'q, Sqlite> for f64 { 40 | fn encode_by_ref( 41 | &self, 42 | args: &mut Vec>, 43 | ) -> Result { 44 | args.push(SqliteArgumentValue::Double(*self)); 45 | 46 | Ok(IsNull::No) 47 | } 48 | } 49 | 50 | impl<'r> Decode<'r, Sqlite> for f64 { 51 | fn decode(value: SqliteValueRef<'r>) -> Result { 52 | Ok(value.double()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /sqlx-sqlite/src/types/json.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::decode::Decode; 4 | use crate::encode::{Encode, IsNull}; 5 | use crate::error::BoxDynError; 6 | use crate::types::{Json, Type}; 7 | use crate::{type_info::DataType, Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; 8 | 9 | impl Type for Json { 10 | fn type_info() -> SqliteTypeInfo { 11 | SqliteTypeInfo(DataType::Text) 12 | } 13 | 14 | fn compatible(ty: &SqliteTypeInfo) -> bool { 15 | <&str as Type>::compatible(ty) 16 | } 17 | } 18 | 19 | impl Encode<'_, Sqlite> for Json 20 | where 21 | T: Serialize, 22 | { 23 | fn encode_by_ref(&self, buf: &mut Vec>) -> Result { 24 | Encode::::encode(self.encode_to_string()?, buf) 25 | } 26 | } 27 | 28 | impl<'r, T> Decode<'r, Sqlite> for Json 29 | where 30 | T: 'r + Deserialize<'r>, 31 | { 32 | fn decode(value: SqliteValueRef<'r>) -> Result { 33 | Self::decode_from_string(Decode::::decode(value)?) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sqlx-sqlite/src/types/text.rs: -------------------------------------------------------------------------------- 1 | use crate::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; 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 | impl Type for Text { 10 | fn type_info() -> SqliteTypeInfo { 11 | >::type_info() 12 | } 13 | 14 | fn compatible(ty: &SqliteTypeInfo) -> bool { 15 | >::compatible(ty) 16 | } 17 | } 18 | 19 | impl<'q, T> Encode<'q, Sqlite> for Text 20 | where 21 | T: Display, 22 | { 23 | fn encode_by_ref(&self, buf: &mut Vec>) -> Result { 24 | Encode::::encode(self.0.to_string(), buf) 25 | } 26 | } 27 | 28 | impl<'r, T> Decode<'r, Sqlite> for Text 29 | where 30 | T: FromStr, 31 | BoxDynError: From<::Err>, 32 | { 33 | fn decode(value: SqliteValueRef<'r>) -> Result { 34 | let s: &str = Decode::::decode(value)?; 35 | Ok(Self(s.parse()?)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /sqlx-test/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | ../LICENSE-APACHE -------------------------------------------------------------------------------- /sqlx-test/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | ../LICENSE-MIT -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/any/mod.rs: -------------------------------------------------------------------------------- 1 | //! **SEE DOCUMENTATION BEFORE USE**. Runtime-generic database driver. 2 | #![doc = include_str!("install_drivers_note.md")] 3 | 4 | use std::sync::Once; 5 | 6 | pub use sqlx_core::any::driver::install_drivers; 7 | 8 | pub use sqlx_core::any::{ 9 | Any, AnyArguments, AnyConnectOptions, AnyExecutor, AnyPoolOptions, AnyQueryResult, AnyRow, 10 | AnyStatement, AnyTransactionManager, AnyTypeInfo, AnyTypeInfoKind, AnyValue, AnyValueRef, 11 | }; 12 | 13 | #[allow(deprecated)] 14 | pub use sqlx_core::any::AnyKind; 15 | 16 | pub(crate) mod reexports { 17 | /// **SEE DOCUMENTATION BEFORE USE**. Type alias for `Pool`. 18 | #[doc = include_str!("install_drivers_note.md")] 19 | pub use sqlx_core::any::AnyPool; 20 | 21 | /// **SEE DOCUMENTATION BEFORE USE**. Runtime-generic database connection. 22 | #[doc = include_str!("install_drivers_note.md")] 23 | pub use sqlx_core::any::AnyConnection; 24 | } 25 | 26 | /// Install all currently compiled-in drivers for [`AnyConnection`] to use. 27 | /// 28 | /// May be called multiple times; only the first call will install drivers, subsequent calls 29 | /// will have no effect. 30 | /// 31 | /// ### Panics 32 | /// If [`install_drivers`] has already been called *not* through this function. 33 | /// 34 | /// [`AnyConnection`]: sqlx_core::any::AnyConnection 35 | pub fn install_default_drivers() { 36 | static ONCE: Once = Once::new(); 37 | 38 | ONCE.call_once(|| { 39 | install_drivers(&[ 40 | #[cfg(feature = "mysql")] 41 | sqlx_mysql::any::DRIVER, 42 | #[cfg(feature = "postgres")] 43 | sqlx_postgres::any::DRIVER, 44 | #[cfg(feature = "_sqlite")] 45 | sqlx_sqlite::any::DRIVER, 46 | ]) 47 | .expect("non-default drivers already installed") 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /tests/.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !certs/* 3 | !keys/* 4 | !mysql/my.cnf 5 | !mssql/*.sh 6 | !postgres/pg_hba.conf 7 | !*/*.sql 8 | -------------------------------------------------------------------------------- /tests/.env: -------------------------------------------------------------------------------- 1 | # environment values for docker-compose 2 | COMPOSE_PROJECT_NAME=sqlx 3 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | !.env 2 | __pycache__ 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/certs/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEzTCCAzWgAwIBAgIQFN6Kw8UmgcAAAGlENSADBzANBgkqhkiG9w0BAQsFADB/ 3 | MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExKjAoBgNVBAsMIW1laGNv 4 | ZGVAR29sZW0ubG9jYWwgKFJ5YW4gTGVja2V5KTExMC8GA1UEAwwobWtjZXJ0IG1l 5 | aGNvZGVAR29sZW0ubG9jYWwgKFJ5YW4gTGVja2V5KTAeFw0yMDA3MTgxMjE1NDla 6 | Fw0zMDA3MTgxMjE1NDlaMH8xHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBD 7 | QTEqMCgGA1UECwwhbWVoY29kZUBHb2xlbS5sb2NhbCAoUnlhbiBMZWNrZXkpMTEw 8 | LwYDVQQDDChta2NlcnQgbWVoY29kZUBHb2xlbS5sb2NhbCAoUnlhbiBMZWNrZXkp 9 | MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAnElV+r9IY3H3jBM72Og3 10 | MINManqh2VpBvbn6ZlQiYKsyeRiK0hyx+7PugJiw8NW60rHI9z4P4ie5pyCsXQ/F 11 | 3dkAmEuliFGOR3NclzUtBu/K3eYrwafO57cLc/k/1skcqV4p8Q4XILsRP25TiSJp 12 | O3Q3Oq70t/unZRONKHUsQfag1z79xW36w09mnFMYTBUKQB1QcaZnGZ0xFyp6jk+w 13 | Z0molgMSDiJS3gWP0zdSGRHVL+eR/072EqYBZ6ycSlfh1XlaP4SM9DMR5K/X9Iwi 14 | AQCSWFPAksUoSLrG/+pyHYWtcelj7u6NNRxIcW6DiyF+HgeSx9YJTnYq0eP9zqbb 15 | gBEhaPz6bnTTzJo6+peyHLHaClxx3K9l6oDK2Z2ZMVLXw4oDcwep6JHdBh2w4MCz 16 | r7DiopNHrnZ3flSz0msy2bf/aZ5k1TzjtmQDzJ8Ln+UlKdqyyA6UOZGA1yyYPS4j 17 | B5EwY/G1bh/1onxHD5h2yBEDtF9mEhXEigioRB/C1W9JAgMBAAGjRTBDMA4GA1Ud 18 | DwEB/wQEAwICBDASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBT0YKcYJw+Y 19 | op0QUrxexXHJ+ond9DANBgkqhkiG9w0BAQsFAAOCAYEAIoDIJo9eQ4Ifs0oxvwVR 20 | CRlZ/VZLm23dMSpMr5BLP/MvUP8W4wQY5Q2U3UO2RSABiJqLiZhS8XSPMzy7o9Ha 21 | wqbnXRbrX6TkYLrQT5pvZwImOis6+zYl2uMo3BS1wMTllNhQBAC+SqCrM+t3E5xD 22 | oasKmaqVo0flWSrHpU/hTVJLrU1rE00ghxVGjiAXoi+JvN718zI7hiIeHh+ikJlW 23 | qEVqtcaPdBKUoaf0c/xDGo/+aEr8BhPcEx5XLjvDiLdc0lHfmt3rRKCrJh8aDoMm 24 | 8PpYZwx7zc+CG4BDY+Bz6kZ5VCSd9RQ3XNjJEd25tgd3IDuiXQGp5CQoVOCQQyKv 25 | QKKhy87ZsThgcHZUR36KHXXLdRy2uAT36G2+HKdzjVTgS5nlYIdBJFi5Fvc+3Syb 26 | vM3rhXbJtg0HPZCHmzIRwLqdhyKEKxEKUy9DfkoO1RtXWLS0+1uDDdmZYLQ2qTC2 27 | /CtEMstXpen5DIy5P1lX8ycUF/UDHOAd6ba6KNul0tCo 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /tests/certs/ca.srl: -------------------------------------------------------------------------------- 1 | B6C71F4B11C0A189 2 | -------------------------------------------------------------------------------- /tests/certs/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIESDCCArCgAwIBAgIQODSVfo7ZFlBmT/IUqU7BmzANBgkqhkiG9w0BAQsFADB/ 3 | MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExKjAoBgNVBAsMIW1laGNv 4 | ZGVAR29sZW0ubG9jYWwgKFJ5YW4gTGVja2V5KTExMC8GA1UEAwwobWtjZXJ0IG1l 5 | aGNvZGVAR29sZW0ubG9jYWwgKFJ5YW4gTGVja2V5KTAeFw0xOTA2MDEwMDAwMDBa 6 | Fw0zMDA3MTgxMjE5MTNaMFUxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBj 7 | ZXJ0aWZpY2F0ZTEqMCgGA1UECwwhbWVoY29kZUBHb2xlbS5sb2NhbCAoUnlhbiBM 8 | ZWNrZXkpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt2IFkflE4SVs 9 | /WOwWjs4MHY+xIQt6LPqVpBLOeem7cm9ZdyHblojesZijV/4vbc4bwlksOBvJEkV 10 | OSanMUimT9AMwjOzJBv0Yyj8ElCI+v/2y2QJ7JHVfn5pBTbk84+lugtgP8hW5ULj 11 | tPDyE14E+8sNCXSa62C1a+lgssNLc+/EAGYQF4moQxIsZFuiI3EViLx4I6ayD/TY 12 | r4U1HBFS8sY/rWDVSh82Bx85OZCK+06xbiMpzbi5b69WwsaOh16e/yJmEa2mQKbl 13 | WZo3qT9LfMn5u1AX7KL7WKBeuohZkeI6sBMFcrO3yXGSmoSL07I2Ya2pSmJyMp8x 14 | idGp06DwnQIDAQABo2owaDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYB 15 | BQUHAwEwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBT0YKcYJw+Yop0QUrxexXHJ 16 | +ond9DASBgNVHREECzAJggdzcWx4LnJzMA0GCSqGSIb3DQEBCwUAA4IBgQAoGsn1 17 | m5xYYo2I06NFhvoRqXfNhBH3W44hHjp6PjHqONW1U8KSM7lXbElNQ+tmCN8uThU6 18 | RM+xY1bHB7rQxwGb0RxKqQphWJcBz9Or9vmqdam2rnBqza8B560MQSBv8kXCSKKY 19 | SSpa/pRaWCbGLDgXs2RL6seBaT2S2qvRPRwxiyDPTPU3fkjyeQDw4nPCpQ/7+dtu 20 | Cc7qg/NeUllW1wKYotTSxfo3FUR08Z73j1BFOoPvUgG1m8YWiDe90pQJDIAU059z 21 | 3IT4e2Jxm8yxudrlDdXmxBBLy4tA3drXYV654PoIszs1D+U6b84w3wE8/30Y8DyX 22 | +InJUQ3kCFeZfS7a7eAtBcqCJVJBpzfieu7ekZpiG3dFo0cn5QFojtwy20VmfKJU 23 | A7D0Ibb6J/UoRVBPoY8KTlYnd+7L2dEaVaE7hSE0emrC9NXa5t+it6xeAFDX0G/1 24 | N31vTfkZABuWrPU3XGCkb87TQxk1hRE6Yw/Hu0TM3DSreLd7dkRHnwt6KEs= 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /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/fixtures/mysql/users.sql: -------------------------------------------------------------------------------- 1 | insert into user(user_id, username) 2 | values (1, 'alice'), (2, 'bob'); 3 | -------------------------------------------------------------------------------- /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/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/keys/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC/TxjK2P+jk6qa 3 | O5A1x/+CZdHQ6GWdnGzLcE4xfn5Szi2FeoPuuOvxujcONGY9ttvLRW9kD1xNulMl 4 | yf/goTmbgsnACOgXawFqmUcF2MUvg/Mz9627891fapVP2Y4dvP+EeHfrmEA2LZqj 5 | Kaa6WJDBkohfB8OopgbwyviBQBNlHQhsl5/UtI33dzL2LNSbB7OGOmJ/2j086ZZx 6 | zGIurG0AyqxsobRoKGcYvksx5/HDHaStBVBZRDAJsZHhhl3sdQapcENrgVz/mP0i 7 | XDoOCC7js8TgZd3N5/JpCAobkMQGwQbude7TPKuinFEAHFb+JJI27uHzbwwUeTIH 8 | +RIrJnnBAgMBAAECggEAKNCZO328XIu+lBUtGSxIKOvMLcPHGi8rTuPw6sJP9R6j 9 | u5x91UqCnBnccR1gyr3eeqmfsDtOuA6Oertz6dq7zZ/Dp0K/MW/U54c4DdlHiHGg 10 | S3AGEtleW2MD4/tIRLPz17FT9GGRIX3tRe428f6/M20txwiDB9IUHP9QsVKYULPX 11 | pzX+BMMINj70U1CcwcsIkPH9znDhwdMfjphC/eJUgITDle9EynYRhBHz55ajTTeE 12 | hPsttRPYvbXdxd1WdSnt/Xv4+N10RKcEnrPE17WrbUs9RvqOz7hW4e4QifsBf5dR 13 | 0Sw1AemmdOK5xTrA0K9D7gRv6qC8QHuTDjIntVd8gQKBgQD+PyQUNJpvUre1zK1A 14 | HBTVbX7uIqYrX6FWXFFE55HtcnrhEWIY5QCBPfOFsVdcvBJrqclkInpELjdnVbeP 15 | 25ETIKhhiP3FnJjJlNZiFXD85NmHbRJzABvNxspb+9UOuIJfB2ixSGmnEKEQIeJf 16 | QmUzz/PJ9+2ct8/rXobZ90Is6QKBgQDAoNe/bUGc/ZmKq132hCcBeHFcjdtW6fkE 17 | 6d8giLx90b1kQzYJaM+4jhYF4s1job32ZPlykAlGUCtWBGirhonioJVgiGy4fOc0 18 | SlIcKg2Gh68FDdKGHcBN5duk0nCc+uLT1fNPqo98jy1DI6VCVej0xWBrFkMFXZ3S 19 | qJ5PWtT/GQKBgFLBkqjRBoO91PZkDPCVM2LVJT+2H4h2tDk8C2f2SFWVsdGYqumX 20 | gLaQx7d4pgsVXJmWxmrFni6bLIWCLSGyQmKLesNkp9Wuxzy2KaH7gK+Qfg3KvvqX 21 | ynUMg8m1CwCjpivwaW9rNpienQ53OQvwvKhExAG1pa4hVpgySIqiJPQhAoGAeFUB 22 | 8cdisZuKiyG6NQEhDL4csuC7IHRQ50zh4gUJGuAnG7cQzpf3CydXgp3ICHFFpeI2 23 | IebwpEf4imd+q4gEItqF9iPDJwx/sh6rZISwplWkc9fKp5V2SDNLHo+HYckoYYTJ 24 | 1f6KXBllAQgHeIUKXb3fGYZyn6t3p91F5/SqEiECgYBzjAYDWIRi7IpMkckts2ZQ 25 | p7YXZCbUP4MALTLuWulrI5IFv7gOjW20US/CArNMc4wPv5WtY1uE59pd8Td2CW9X 26 | BX1nQXqaVlF6xLOzgqsWPxloRk7y692J9nYMKcB6VxlkFVUQfbRZksFCsn2I4Y5Z 27 | ZtG/bPbIR6NgZ6ntNa+KIg== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /tests/keys/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC3YgWR+UThJWz9 3 | Y7BaOzgwdj7EhC3os+pWkEs556btyb1l3IduWiN6xmKNX/i9tzhvCWSw4G8kSRU5 4 | JqcxSKZP0AzCM7MkG/RjKPwSUIj6//bLZAnskdV+fmkFNuTzj6W6C2A/yFblQuO0 5 | 8PITXgT7yw0JdJrrYLVr6WCyw0tz78QAZhAXiahDEixkW6IjcRWIvHgjprIP9Niv 6 | hTUcEVLyxj+tYNVKHzYHHzk5kIr7TrFuIynNuLlvr1bCxo6HXp7/ImYRraZApuVZ 7 | mjepP0t8yfm7UBfsovtYoF66iFmR4jqwEwVys7fJcZKahIvTsjZhralKYnIynzGJ 8 | 0anToPCdAgMBAAECggEABVjnXq1NE9+agP0CLG9joQ4hoGtWR13PrHyCpQqbNH3Y 9 | dvrqPA6G0FKulv7Aaw/Hpn04oWu58e3rn4IACBDdQKCJbrRBOgFSq/2K9CHDDMaf 10 | 9KhTHcHW3txixZMnM+7xXy5rvRBjcEX2C9WmyWfJb2opVChBSDHGuIHSnwPQ1G2R 11 | t8PrSTD3uN7VbpdDj0pznZN7UzTxXGNyy98ruZIY6haK6urd9BRGQD8RKe6hQL6k 12 | 5m+AWW7vhIEgBmNeYg5RHoxaeECI89L7cq1Z+0kMMqyvaXdBUTpsjXGW56kYRt+A 13 | qTwxlBbCQRTZuHz+vnuWEiJ2/YbY5b5xpraHqnc8ZQKBgQDoa2ilYqA+Fb/elgyX 14 | kaXuf/d8EGLLQ2KHk+nkJeXLnbMGq7v1ISSVLsWDkMGJyENQOHh5hJVub7ew/AK2 15 | SGQ0FA1F6AcLJ+0aiWY+pvhesKLtW+cXVooiL4M6w8YgDZyH7V988sM0l7FejauK 16 | ZyDHDsaWOR2IT0eKg0bVh+N6XwKBgQDJ/P4Ua7wlvphTPc4mfjvTkppDpv0YDW8x 17 | t89Y5mLscsDV8okHav3MlKhKOPqEbmcw1sS0txT3NxLFUpFfONsuYsfT24z96XI4 18 | cHKUDP1zEFq+7D+A20F3ydks5+OW5FofcDNbc2+Yg/0upXe38wVfBJj1VEWGz70C 19 | GSN+j6vugwKBgDEdKXLxgX09KVuHB8grvg3FOu4bpFThu3t89UsB+ypo+8DoH4Lw 20 | awOfa5uexlcwW5EjLco4Cz/YGdAroQMWDx62MgvYuUxRNpiJ+nI45HlWCEfySMY0 21 | wmHw+mE7p610UuSic7A6uKdvesrJUzufCV0nMS3jiesZHbwWe6x518cvAoGBAI9P 22 | 7GpKwlS5dVRiXrkbCZGky8VCXwLIzWMmOnymAfwns0BZc/YKaIbV1s3KvZxmxNp3 23 | F1vtJnf84FmWqsQ4D/NKbOOZO+EP2FXJGtKGoPEZ4njiIHBpoHrAgVGGOglefb8e 24 | maHCNqSsyV9mUZn3WJFBLtGp+CadkEpD0dZDU8bHAoGADRorYjov1pEEWbHhQaQP 25 | X1pQsc8Oc/tQAvh9GOJPpvJ0r2hdMMMJC/lC6+63MyKKrXScagVcajgRk1ES0V2/ 26 | 9YizDOY8S4fEtHTSbPJHSS2orXhJ8KSAxl1JbjNDlDPj9EcOvocaehssZ9Tf2bPg 27 | 7ywoQK2oCbW2Hq9WcgYIubY= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/migrate/migrations_reversible/20220721124650_add_table.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE migrations_reversible_test; 2 | -------------------------------------------------------------------------------- /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/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/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/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/migrate/migrations_symlink: -------------------------------------------------------------------------------- 1 | migrations_simple -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 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/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 | -------------------------------------------------------------------------------- /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/mysql/fixtures/users.sql: -------------------------------------------------------------------------------- 1 | insert into user(user_id, username) 2 | values (1, 'alice'), (2, 'bob'); 3 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/mysql/migrations_reversible/20220721124650_add_table.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE migrations_reversible_test; 2 | -------------------------------------------------------------------------------- /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/mysql/migrations_reversible/20220721125033_modify_column.down.sql: -------------------------------------------------------------------------------- 1 | UPDATE migrations_reversible_test 2 | SET some_payload = some_payload - 1; 3 | -------------------------------------------------------------------------------- /tests/mysql/migrations_reversible/20220721125033_modify_column.up.sql: -------------------------------------------------------------------------------- 1 | UPDATE migrations_reversible_test 2 | SET some_payload = some_payload + 1; 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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 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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/postgres/migrations_no_tx/0_create_db.sql: -------------------------------------------------------------------------------- 1 | -- no-transaction 2 | 3 | CREATE DATABASE test_db; 4 | -------------------------------------------------------------------------------- /tests/postgres/migrations_reversible/20220721124650_add_table.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE migrations_reversible_test; 2 | -------------------------------------------------------------------------------- /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/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/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/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/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/postgres/setup.sql: -------------------------------------------------------------------------------- 1 | -- https://www.postgresql.org/docs/current/ltree.html 2 | CREATE EXTENSION IF NOT EXISTS ltree; 3 | 4 | -- https://www.postgresql.org/docs/current/cube.html 5 | CREATE EXTENSION IF NOT EXISTS cube; 6 | 7 | -- https://www.postgresql.org/docs/current/citext.html 8 | CREATE EXTENSION IF NOT EXISTS citext; 9 | 10 | -- https://www.postgresql.org/docs/current/hstore.html 11 | CREATE EXTENSION IF NOT EXISTS hstore; 12 | 13 | -- https://www.postgresql.org/docs/current/sql-createtype.html 14 | CREATE TYPE status AS ENUM ('new', 'open', 'closed'); 15 | 16 | -- https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-DECLARING 17 | CREATE TYPE inventory_item AS 18 | ( 19 | name TEXT, 20 | supplier_id INT, 21 | price BIGINT 22 | ); 23 | 24 | -- https://github.com/prisma/database-schema-examples/tree/master/postgres/basic-twitter#basic-twitter 25 | CREATE TABLE tweet 26 | ( 27 | id BIGSERIAL PRIMARY KEY, 28 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 29 | text TEXT NOT NULL, 30 | owner_id BIGINT 31 | ); 32 | 33 | CREATE TABLE tweet_reply 34 | ( 35 | id BIGSERIAL PRIMARY KEY, 36 | tweet_id BIGINT NOT NULL REFERENCES tweet(id), 37 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 38 | text TEXT NOT NULL, 39 | owner_id BIGINT 40 | ); 41 | 42 | CREATE TYPE float_range AS RANGE 43 | ( 44 | subtype = float8, 45 | subtype_diff = float8mi 46 | ); 47 | 48 | CREATE TABLE products ( 49 | product_no INTEGER, 50 | name TEXT, 51 | price NUMERIC CHECK (price > 0) 52 | ); 53 | 54 | CREATE OR REPLACE PROCEDURE forty_two(INOUT forty_two INT = NULL) 55 | LANGUAGE plpgsql AS 'begin forty_two := 42; end;'; 56 | 57 | CREATE TABLE test_citext ( 58 | foo CITEXT NOT NULL 59 | ); 60 | 61 | CREATE SCHEMA IF NOT EXISTS foo; 62 | 63 | CREATE TYPE foo."Foo" as ENUM ('Bar', 'Baz'); 64 | 65 | CREATE TABLE mytable(f HSTORE); 66 | -------------------------------------------------------------------------------- /tests/postgres/test-query.sql: -------------------------------------------------------------------------------- 1 | SELECT id "id!", name from (VALUES (1, null)) accounts(id, name) 2 | -------------------------------------------------------------------------------- /tests/sqlite/.gitignore: -------------------------------------------------------------------------------- 1 | sqlite.test.db 2 | 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/sqlite/fixtures/users.sql: -------------------------------------------------------------------------------- 1 | insert into "user"(user_id, username) 2 | values (1, 'alice'), (2, 'bob'); 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/sqlite/migrations_reversible/20220721124650_add_table.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE migrations_reversible_test; 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/sqlite/sqlite.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/launchbadge/sqlx/90797200eef8d7aa41ce0cd582fdd8560bfc0f24/tests/sqlite/sqlite.db -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/ui/postgres/issue_30.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let query = sqlx::query!("select 1 as \"'1\""); 3 | } 4 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/ui/postgres/wrong_param_type.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> $DIR/wrong_param_type.rs:2:50 3 | | 4 | 2 | let _query = sqlx::query!("select $1::text", 0i32); 5 | | ^^^^ expected `&str`, found `i32` 6 | 7 | error[E0308]: mismatched types 8 | --> $DIR/wrong_param_type.rs:4:50 9 | | 10 | 4 | let _query = sqlx::query!("select $1::text", &0i32); 11 | | ^ expected `str`, found `i32` 12 | | 13 | = note: expected reference `&str` 14 | found reference `&i32` 15 | 16 | error[E0308]: mismatched types 17 | --> $DIR/wrong_param_type.rs:6:50 18 | | 19 | 6 | let _query = sqlx::query!("select $1::text", Some(0i32)); 20 | | ^^^^ expected `&str`, found `i32` 21 | | 22 | = note: expected enum `Option<&str>` 23 | found enum `Option` 24 | 25 | error[E0308]: mismatched types 26 | --> $DIR/wrong_param_type.rs:9:50 27 | | 28 | 9 | let _query = sqlx::query!("select $1::text", arg); 29 | | ^^^ expected `&str`, found `i32` 30 | 31 | error[E0308]: mismatched types 32 | --> $DIR/wrong_param_type.rs:12:50 33 | | 34 | 12 | let _query = sqlx::query!("select $1::text", arg); 35 | | ^^^ expected `&str`, found `i32` 36 | | 37 | = note: expected enum `Option<&str>` 38 | found enum `Option` 39 | 40 | error[E0308]: mismatched types 41 | --> $DIR/wrong_param_type.rs:13:50 42 | | 43 | 13 | let _query = sqlx::query!("select $1::text", arg.as_ref()); 44 | | ^^^ expected `str`, found `i32` 45 | | 46 | = note: expected enum `Option<&str>` 47 | found enum `Option<&i32>` 48 | -------------------------------------------------------------------------------- /tests/ui/sqlite/expression-column-type.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _ = sqlx::query!("select 1 as id"); 3 | } 4 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------