├── .cursorignore ├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md └── workflows │ ├── publish.yml │ └── sqlx.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── FAQ.md ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── 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 │ ├── 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 ├── prep-release.sh ├── sqlx-bench ├── Cargo.toml ├── README.md ├── benches │ ├── pg_pool.rs │ └── sqlite_fetch_all.rs ├── results │ └── 2020-07-01-bench_pgpool_acquire │ │ ├── 1000_fair_pdf_small.svg │ │ ├── 1000_unfair_pdf_small.svg │ │ ├── 100_fair_pdf_small.svg │ │ ├── 100_unfair_pdf_small.svg │ │ ├── 10_fair_pdf_small.svg │ │ ├── 10_unfair_pdf_small.svg │ │ ├── 5000_fair_pdf_small.svg │ │ ├── 5000_unfair_pdf_small.svg │ │ ├── 500_fair_pdf_small.svg │ │ ├── 500_unfair_pdf_small.svg │ │ ├── 50_fair_pdf_small.svg │ │ ├── 50_unfair_pdf_small.svg │ │ ├── 5_fair_pdf_small.svg │ │ ├── 5_unfair_pdf_small.svg │ │ └── REPORT.md └── test.db ├── sqlx-cli ├── Cargo.toml ├── README.md ├── src │ ├── bin │ │ ├── cargo-sqlx.rs │ │ └── sqlx.rs │ ├── database.rs │ ├── lib.rs │ ├── metadata.rs │ ├── migrate.rs │ ├── migration.rs │ ├── opt.rs │ └── prepare.rs └── tests │ └── assets │ └── sample_metadata.json ├── sqlx-core ├── Cargo.toml └── src │ ├── acquire.rs │ ├── any │ ├── arguments.rs │ ├── column.rs │ ├── connection │ │ ├── establish.rs │ │ ├── executor.rs │ │ └── mod.rs │ ├── database.rs │ ├── decode.rs │ ├── encode.rs │ ├── error.rs │ ├── kind.rs │ ├── migrate.rs │ ├── mod.rs │ ├── options.rs │ ├── query_result.rs │ ├── row.rs │ ├── statement.rs │ ├── transaction.rs │ ├── type.rs │ ├── type_info.rs │ ├── types.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 │ ├── io │ ├── buf.rs │ ├── buf_mut.rs │ ├── buf_stream.rs │ ├── decode.rs │ ├── encode.rs │ ├── mod.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 │ ├── mssql │ ├── arguments.rs │ ├── column.rs │ ├── connection │ │ ├── establish.rs │ │ ├── executor.rs │ │ ├── mod.rs │ │ ├── prepare.rs │ │ ├── stream.rs │ │ └── tls_prelogin_stream_wrapper.rs │ ├── database.rs │ ├── error.rs │ ├── io │ │ ├── buf.rs │ │ ├── buf_mut.rs │ │ └── mod.rs │ ├── mod.rs │ ├── options │ │ ├── connect.rs │ │ ├── mod.rs │ │ └── parse.rs │ ├── protocol │ │ ├── col_meta_data.rs │ │ ├── done.rs │ │ ├── env_change.rs │ │ ├── error.rs │ │ ├── header.rs │ │ ├── info.rs │ │ ├── login.rs │ │ ├── login_ack.rs │ │ ├── message.rs │ │ ├── mod.rs │ │ ├── order.rs │ │ ├── packet.rs │ │ ├── pre_login.rs │ │ ├── return_status.rs │ │ ├── return_value.rs │ │ ├── row.rs │ │ ├── rpc.rs │ │ ├── sql_batch.rs │ │ └── type_info.rs │ ├── query_result.rs │ ├── row.rs │ ├── statement.rs │ ├── transaction.rs │ ├── type_info.rs │ ├── types │ │ ├── bigdecimal.rs │ │ ├── bool.rs │ │ ├── bytes.rs │ │ ├── chrono.rs │ │ ├── decimal.rs │ │ ├── decimal_tools.rs │ │ ├── float.rs │ │ ├── int.rs │ │ ├── json.rs │ │ ├── mod.rs │ │ ├── str.rs │ │ └── uint.rs │ └── value.rs │ ├── mysql │ ├── 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 │ ├── migrate.rs │ ├── mod.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_info.rs │ ├── types │ │ ├── bigdecimal.rs │ │ ├── bool.rs │ │ ├── bytes.rs │ │ ├── chrono.rs │ │ ├── decimal.rs │ │ ├── float.rs │ │ ├── int.rs │ │ ├── json.rs │ │ ├── mod.rs │ │ ├── str.rs │ │ ├── time.rs │ │ ├── uint.rs │ │ └── uuid.rs │ └── value.rs │ ├── net │ ├── mod.rs │ ├── socket.rs │ └── tls │ │ ├── mod.rs │ │ └── rustls.rs │ ├── pool │ ├── connection.rs │ ├── executor.rs │ ├── inner.rs │ ├── maybe.rs │ ├── mod.rs │ └── options.rs │ ├── postgres │ ├── advisory_lock.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 │ ├── 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 │ │ ├── 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 │ ├── mod.rs │ ├── options │ │ ├── connect.rs │ │ ├── mod.rs │ │ ├── parse.rs │ │ ├── pgpass.rs │ │ └── ssl_mode.rs │ ├── query_result.rs │ ├── row.rs │ ├── statement.rs │ ├── testing │ │ └── mod.rs │ ├── transaction.rs │ ├── type_info.rs │ ├── types │ │ ├── array.rs │ │ ├── bigdecimal.rs │ │ ├── bit_vec.rs │ │ ├── bool.rs │ │ ├── bytes.rs │ │ ├── chrono │ │ │ ├── date.rs │ │ │ ├── datetime.rs │ │ │ ├── mod.rs │ │ │ └── time.rs │ │ ├── decimal.rs │ │ ├── float.rs │ │ ├── int.rs │ │ ├── interval.rs │ │ ├── ipaddr.rs │ │ ├── ipnetwork.rs │ │ ├── json.rs │ │ ├── lquery.rs │ │ ├── ltree.rs │ │ ├── mac_address.rs │ │ ├── mod.rs │ │ ├── money.rs │ │ ├── numeric.rs │ │ ├── oid.rs │ │ ├── range.rs │ │ ├── record.rs │ │ ├── str.rs │ │ ├── time │ │ │ ├── date.rs │ │ │ ├── datetime.rs │ │ │ ├── mod.rs │ │ │ └── time.rs │ │ ├── time_tz.rs │ │ ├── tuple.rs │ │ ├── uuid.rs │ │ └── void.rs │ └── value.rs │ ├── query.rs │ ├── query_as.rs │ ├── query_builder.rs │ ├── query_scalar.rs │ ├── row.rs │ ├── sqlite │ ├── arguments.rs │ ├── column.rs │ ├── connection │ │ ├── collation.rs │ │ ├── describe.rs │ │ ├── establish.rs │ │ ├── execute.rs │ │ ├── executor.rs │ │ ├── explain.rs │ │ ├── function.rs │ │ ├── handle.rs │ │ ├── mod.rs │ │ └── worker.rs │ ├── database.rs │ ├── error.rs │ ├── migrate.rs │ ├── mod.rs │ ├── options │ │ ├── auto_vacuum.rs │ │ ├── connect.rs │ │ ├── journal_mode.rs │ │ ├── locking_mode.rs │ │ ├── mod.rs │ │ ├── parse.rs │ │ └── synchronous.rs │ ├── query_result.rs │ ├── row.rs │ ├── statement │ │ ├── handle.rs │ │ ├── mod.rs │ │ ├── unlock_notify.rs │ │ └── virtual.rs │ ├── testing │ │ └── mod.rs │ ├── transaction.rs │ ├── type_info.rs │ ├── types │ │ ├── bigdecimal.rs │ │ ├── bool.rs │ │ ├── bytes.rs │ │ ├── chrono.rs │ │ ├── decimal.rs │ │ ├── float.rs │ │ ├── int.rs │ │ ├── json.rs │ │ ├── mod.rs │ │ ├── str.rs │ │ ├── time.rs │ │ ├── uint.rs │ │ └── uuid.rs │ └── value.rs │ ├── statement.rs │ ├── testing │ ├── fixtures.rs │ └── mod.rs │ ├── transaction.rs │ ├── type_info.rs │ ├── types │ ├── bstr.rs │ ├── git2.rs │ ├── json.rs │ └── mod.rs │ └── value.rs ├── sqlx-macros ├── Cargo.toml └── src │ ├── common.rs │ ├── database │ ├── mod.rs │ ├── mssql.rs │ ├── mysql.rs │ ├── postgres.rs │ └── sqlite.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-rt ├── Cargo.toml └── src │ ├── lib.rs │ ├── rt_async_std.rs │ └── rt_tokio.rs ├── sqlx-test ├── Cargo.toml └── src │ └── lib.rs ├── src ├── lib.rs ├── macros │ ├── mod.rs │ └── test.md └── ty_match.rs ├── test.sh └── tests ├── .dockerignore ├── .env ├── .gitignore ├── README.md ├── any ├── any.rs └── pool.rs ├── certs ├── ca.crt ├── ca.srl ├── client.crt └── server.crt ├── docker-compose.yml ├── docker.py ├── 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 ├── mssql ├── Dockerfile ├── configure-db.sh ├── describe.rs ├── entrypoint.sh ├── macros.rs ├── mssql.conf ├── mssql.rs ├── setup.sql └── types.rs ├── mysql ├── describe.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 ├── mysql.rs ├── setup.sql ├── test-attr.rs └── types.rs ├── postgres ├── Dockerfile ├── derives.rs ├── describe.rs ├── fixtures │ ├── comments.sql │ ├── posts.sql │ └── users.sql ├── macros.rs ├── migrate.rs ├── migrations │ ├── 0_setup.sql │ ├── 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 ├── pg_hba.conf ├── postgres.rs ├── postgresql.conf ├── setup.sql ├── test-attr.rs ├── test-query.sql └── types.rs ├── sqlite ├── .gitignore ├── derives.rs ├── describe.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 ├── 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 /.cursorignore: -------------------------------------------------------------------------------- 1 | # Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) 2 | !tests/**/*.crt 3 | !tests/**/*.key 4 | -------------------------------------------------------------------------------- /.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/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: I think I found a bug in SQLx 3 | about: Create a bug-report issue. 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | --- 8 | ### Bug Description 9 | A clear and concise description of what the bug is. 10 | 11 | ### Minimal Reproduction 12 | A small code snippet or a link to a Github repo or Gist, with instructions on reproducing the bug. 13 | 14 | ### Info 15 | * SQLx version: [REQUIRED] 16 | * SQLx features enabled: [REQUIRED] 17 | * Database server and version: [REQUIRED] (MySQL / Postgres / SQLite ) 18 | * Operating system: [REQUIRED] 19 | * `rustc --version`: [REQUIRED] 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: I have a question or problem 4 | url: https://github.com/launchbadge/sqlx/tree/main/FAQ.md 5 | about: See our FAQ. 6 | - name: I have a question or problem not covered in the FAQ 7 | url: https://github.com/launchbadge/sqlx/discussions/new?category=q-a 8 | about: Open a Q&A discussion. 9 | - name: Join SQLx's Discord 10 | url: https://discord.gg/hPm3WqA 11 | about: Join our Discord server for help, discussions and release announcements. 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: I have a feature request for SQLx 3 | about: Create a feature-request issue. 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: {} 3 | push: 4 | # Sequence of patterns matched against refs/tags 5 | tags: 6 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 7 | 8 | name: Publish 9 | 10 | jobs: 11 | publish: 12 | name: Publish 13 | runs-on: ubuntu-22.04 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: Swatinem/rust-cache@v2 17 | - run: | 18 | cargo publish ${ARGS} --package sqlx-rt-oldapi 19 | cargo publish ${ARGS} --package sqlx-core-oldapi 20 | cargo publish ${ARGS} --package sqlx-macros-oldapi 21 | cargo publish ${ARGS} --package sqlx-oldapi 22 | env: 23 | ARGS: 24 | --token ${{ secrets.CRATES_TOKEN }} 25 | --no-default-features 26 | --features runtime-actix-rustls 27 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | # It is *much* too easy to misread `x.min(y)` as "x should be *at least* y" when in fact it 3 | # means the *exact* opposite, and same with `x.max(y)`; use `cmp::{min, max}` instead. 4 | "core::cmp::Ord::min", "core::cmp::Ord::max" 5 | ] 6 | -------------------------------------------------------------------------------- /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 = { package = "sqlx-oldapi", path = "../../../", features = [ "mysql", "runtime-tokio-native-tls" ] } 11 | structopt = "0.3" 12 | tokio = { version = "1.20.0", features = ["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 = { package = "sqlx-oldapi", version = "0.6.45", path = "../../../", features = ["runtime-tokio-rustls", "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" 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 = "1.0.31" 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/files/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "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 = { package = "sqlx-oldapi", path = "../../../", features = ["postgres", "offline", "runtime-tokio-native-tls"] } 11 | tokio = { version = "1.20.0", features = ["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, query_file_unchecked, 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] 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 = "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 = { package = "sqlx-oldapi", path = "../../../", features = ["postgres", "json"] } 14 | structopt = "0.3" 15 | tokio = { version = "1.20.0", features = ["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 = { package = "sqlx-oldapi", path = "../../../", features = [ "postgres", "tls" ] } 9 | futures = "0.3.1" 10 | tokio = { version = "1.20.0", features = ["macros"]} 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 = { package = "sqlx-oldapi", path = "../../../", features = ["postgres", "offline", "runtime-tokio-native-tls"] } 11 | structopt = "0.3" 12 | tokio = { version = "1.20.0", features = ["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 = { package = "sqlx-oldapi", path = "../../../", features = ["postgres", "offline", "runtime-tokio-native-tls"] } 11 | structopt = "0.3" 12 | tokio = { version = "1.20.0", features = ["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 = { package = "sqlx-oldapi", path = "../../../", features = [ "postgres", "tls", "runtime-tokio-native-tls" ] } 9 | futures = "0.3.1" 10 | tokio = { version = "1.20.0", features = ["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 = { package = "sqlx-oldapi", path = "../../../", features = ["sqlite", "runtime-tokio-native-tls"] } 11 | structopt = "0.3" 12 | tokio = { version = "1.20.0", features = ["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 | -------------------------------------------------------------------------------- /prep-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | set -ex 3 | 4 | VERSION=$1 5 | 6 | if [ -z "$VERSION" ] 7 | then 8 | echo "USAGE: ./prep-release.sh " 9 | exit 1 10 | fi 11 | 12 | cargo set-version -p sqlx-rt "$VERSION" 13 | cargo set-version -p sqlx-core "$VERSION" 14 | cargo set-version -p sqlx-macros "$VERSION" 15 | cargo set-version -p sqlx "$VERSION" 16 | cargo set-version -p sqlx-cli "$VERSION" -------------------------------------------------------------------------------- /sqlx-bench/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-bench" 3 | version = "0.1.0" 4 | authors = ["Austin Bonander "] 5 | edition = "2021" 6 | publish = false 7 | 8 | [features] 9 | runtime-actix-native-tls = ["runtime-tokio-native-tls"] 10 | runtime-async-std-native-tls = [ 11 | "sqlx/runtime-async-std-native-tls", 12 | "sqlx-rt/runtime-async-std-native-tls", 13 | ] 14 | runtime-tokio-native-tls = [ 15 | "sqlx/runtime-tokio-native-tls", 16 | "sqlx-rt/runtime-tokio-native-tls", 17 | ] 18 | 19 | runtime-actix-rustls = ["runtime-tokio-rustls"] 20 | runtime-async-std-rustls = [ 21 | "sqlx/runtime-async-std-rustls", 22 | "sqlx-rt/runtime-async-std-rustls", 23 | ] 24 | runtime-tokio-rustls = [ 25 | "sqlx/runtime-tokio-rustls", 26 | "sqlx-rt/runtime-tokio-rustls", 27 | ] 28 | 29 | postgres = ["sqlx/postgres"] 30 | sqlite = ["sqlx/sqlite"] 31 | 32 | [dependencies] 33 | criterion = "0.3.3" 34 | dotenvy = "0.15.0" 35 | once_cell = "1.4" 36 | sqlx = { package = "sqlx-oldapi", version = "0.6", path = "../", default-features = false, features = ["macros"] } 37 | sqlx-rt = { package = "sqlx-rt-oldapi", version = "0.6", path = "../sqlx-rt", default-features = false } 38 | 39 | chrono = "0.4.19" 40 | 41 | [[bench]] 42 | name = "pg_pool" 43 | harness = false 44 | required-features = ["postgres"] 45 | 46 | [[bench]] 47 | name = "sqlite_fetch_all" 48 | harness = false 49 | required-features = ["sqlite"] 50 | -------------------------------------------------------------------------------- /sqlx-bench/benches/sqlite_fetch_all.rs: -------------------------------------------------------------------------------- 1 | use sqlx::{Connection, Executor}; 2 | 3 | use std::time::Instant; 4 | 5 | #[derive(sqlx::FromRow)] 6 | struct Test { 7 | id: i32, 8 | } 9 | 10 | fn main() -> sqlx::Result<()> { 11 | sqlx_rt::block_on(async { 12 | let mut conn = sqlx::SqliteConnection::connect("sqlite://test.db?mode=rwc").await?; 13 | let delete_sql = "DROP TABLE IF EXISTS test"; 14 | conn.execute(delete_sql).await?; 15 | 16 | let create_sql = "CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY NOT NULL)"; 17 | conn.execute(create_sql).await?; 18 | 19 | let mut tx = conn.begin().await?; 20 | for entry in 0i32..100000 { 21 | sqlx::query("INSERT INTO test (id) VALUES ($1)") 22 | .bind(entry) 23 | .execute(&mut tx) 24 | .await?; 25 | } 26 | tx.commit().await?; 27 | 28 | for _ in 0..10i8 { 29 | let start = chrono::Utc::now(); 30 | 31 | println!( 32 | "total: {}", 33 | sqlx::query!("SELECT id from test") 34 | .fetch_all(&mut conn) 35 | .await? 36 | .len() 37 | ); 38 | 39 | let elapsed = chrono::Utc::now() - start; 40 | println!("elapsed {}", elapsed); 41 | } 42 | 43 | Ok(()) 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /sqlx-bench/test.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sqlpage/sqlx-oldapi/2c05b9d86bfa61811070e27503e81587b8ea5ee6/sqlx-bench/test.db -------------------------------------------------------------------------------- /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 | dotenvy::dotenv().ok(); 17 | let Cli::Sqlx(opt) = Cli::parse(); 18 | 19 | if let Err(error) = sqlx_cli::run(opt).await { 20 | println!("{} {}", style("error:").bold().red(), error); 21 | process::exit(1); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /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 | dotenvy::dotenv().ok(); 8 | // no special handling here 9 | if let Err(error) = sqlx_cli::run(Opt::parse()).await { 10 | println!("{} {}", style("error:").bold().red(), error); 11 | std::process::exit(1); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sqlx-core/src/any/connection/establish.rs: -------------------------------------------------------------------------------- 1 | use crate::any::connection::AnyConnectionKind; 2 | use crate::any::options::{AnyConnectOptions, AnyConnectOptionsKind}; 3 | use crate::any::AnyConnection; 4 | use crate::connection::Connection; 5 | use crate::error::Error; 6 | 7 | impl AnyConnection { 8 | pub(crate) async fn establish(options: &AnyConnectOptions) -> Result { 9 | match &options.0 { 10 | #[cfg(feature = "mysql")] 11 | AnyConnectOptionsKind::MySql(options) => { 12 | crate::mysql::MySqlConnection::connect_with(options) 13 | .await 14 | .map(AnyConnectionKind::MySql) 15 | } 16 | 17 | #[cfg(feature = "postgres")] 18 | AnyConnectOptionsKind::Postgres(options) => { 19 | crate::postgres::PgConnection::connect_with(options) 20 | .await 21 | .map(AnyConnectionKind::Postgres) 22 | } 23 | 24 | #[cfg(feature = "sqlite")] 25 | AnyConnectOptionsKind::Sqlite(options) => { 26 | crate::sqlite::SqliteConnection::connect_with(options) 27 | .await 28 | .map(AnyConnectionKind::Sqlite) 29 | } 30 | 31 | #[cfg(feature = "mssql")] 32 | AnyConnectOptionsKind::Mssql(options) => { 33 | crate::mssql::MssqlConnection::connect_with(options) 34 | .await 35 | .map(AnyConnectionKind::Mssql) 36 | } 37 | } 38 | .map(AnyConnection) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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, HasArguments, HasStatement, HasStatementCache, HasValueRef}; 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 | } 27 | 28 | impl<'r> HasValueRef<'r> for Any { 29 | type Database = Any; 30 | 31 | type ValueRef = AnyValueRef<'r>; 32 | } 33 | 34 | impl<'q> HasStatement<'q> for Any { 35 | type Database = Any; 36 | 37 | type Statement = AnyStatement<'q>; 38 | } 39 | 40 | impl<'q> HasArguments<'q> for Any { 41 | type Database = Any; 42 | 43 | type Arguments = AnyArguments<'q>; 44 | 45 | type ArgumentBuffer = AnyArgumentBuffer<'q>; 46 | } 47 | 48 | // This _may_ be true, depending on the selected database 49 | impl HasStatementCache for Any {} 50 | -------------------------------------------------------------------------------- /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 | pub(crate) rows_affected: u64, 6 | pub(crate) last_insert_id: Option, 7 | } 8 | 9 | impl AnyQueryResult { 10 | pub fn rows_affected(&self) -> u64 { 11 | self.rows_affected 12 | } 13 | 14 | pub fn last_insert_id(&self) -> Option { 15 | self.last_insert_id 16 | } 17 | } 18 | 19 | impl Extend for AnyQueryResult { 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 | -------------------------------------------------------------------------------- /sqlx-core/src/common/mod.rs: -------------------------------------------------------------------------------- 1 | mod statement_cache; 2 | 3 | pub(crate) 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(crate) 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 Decode<'de, Context = ()> 6 | where 7 | Self: Sized, 8 | { 9 | fn decode(buf: Bytes) -> Result 10 | where 11 | Self: Decode<'de, ()>, 12 | { 13 | Self::decode_with(buf, ()) 14 | } 15 | 16 | fn decode_with(buf: Bytes, context: Context) -> Result; 17 | } 18 | 19 | impl Decode<'_> for Bytes { 20 | fn decode_with(buf: Bytes, _: ()) -> Result { 21 | Ok(buf) 22 | } 23 | } 24 | 25 | impl Decode<'_> for () { 26 | fn decode_with(_: Bytes, _: ()) -> Result<(), Error> { 27 | Ok(()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sqlx-core/src/io/encode.rs: -------------------------------------------------------------------------------- 1 | pub trait Encode<'en, Context = ()> { 2 | #[allow(dead_code)] 3 | fn encode(&self, buf: &mut Vec) 4 | where 5 | Self: Encode<'en, ()>, 6 | { 7 | self.encode_with(buf, ()); 8 | } 9 | 10 | fn encode_with(&self, buf: &mut Vec, context: Context); 11 | } 12 | 13 | impl<'en, C> Encode<'en, C> for &'_ [u8] { 14 | fn encode_with(&self, buf: &mut Vec, _: C) { 15 | buf.extend_from_slice(self); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sqlx-core/src/io/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | mod buf; 3 | mod buf_mut; 4 | mod buf_stream; 5 | mod decode; 6 | mod encode; 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::Decode; 13 | pub use encode::Encode; 14 | -------------------------------------------------------------------------------- /sqlx-core/src/io/write_and_flush.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use futures_core::Future; 3 | use futures_util::ready; 4 | use sqlx_rt::AsyncWrite; 5 | use std::io::{BufRead, Cursor}; 6 | use std::pin::Pin; 7 | use std::task::{Context, Poll}; 8 | 9 | // Atomic operation that writes the full buffer to the stream, flushes the stream, and then 10 | // clears the buffer (even if either of the two previous operations failed). 11 | pub struct WriteAndFlush<'a, S> { 12 | pub(super) stream: &'a mut S, 13 | pub(super) buf: Cursor<&'a mut Vec>, 14 | } 15 | 16 | impl Future for WriteAndFlush<'_, S> { 17 | type Output = Result<(), Error>; 18 | 19 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 20 | let Self { 21 | ref mut stream, 22 | ref mut buf, 23 | } = *self; 24 | 25 | loop { 26 | let read = buf.fill_buf()?; 27 | 28 | if !read.is_empty() { 29 | let written = ready!(Pin::new(&mut *stream).poll_write(cx, read)?); 30 | buf.consume(written); 31 | } else { 32 | break; 33 | } 34 | } 35 | 36 | Pin::new(stream).poll_flush(cx).map_err(Error::Io) 37 | } 38 | } 39 | 40 | impl<'a, S> Drop for WriteAndFlush<'a, S> { 41 | fn drop(&mut self) { 42 | // clear the buffer regardless of whether the flush succeeded or not 43 | self.buf.get_mut().clear(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /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("could not run migration {0}")] 7 | Execute(i64, #[source] Error), 8 | 9 | #[error("while resolving migrations: {0}")] 10 | Source(#[source] BoxDynError), 11 | 12 | #[error("migration {0} was previously applied but is missing in the resolved migrations")] 13 | VersionMissing(i64), 14 | 15 | #[error("migration {0} was previously applied but has been modified")] 16 | VersionMismatch(i64), 17 | 18 | #[error("cannot mix reversible migrations with simple migrations. All migrations should be reversible or simple migrations")] 19 | InvalidMixReversibleAndSimple, 20 | 21 | // NOTE: this will only happen with a database that does not have transactional DDL (.e.g, MySQL or Oracle) 22 | #[error( 23 | "migration {0} is partially applied; fix and remove row from `_sqlx_migrations` table" 24 | )] 25 | Dirty(i64), 26 | 27 | #[error("unable to acquire a connection to the database")] 28 | AcquireConnection(#[source] Error), 29 | 30 | #[error("an operation on the migration metadata table (_sqlx_migrations) failed")] 31 | AccessMigrationMetadata(#[source] Error), 32 | } 33 | 34 | pub type MigrateResult = std::result::Result; 35 | -------------------------------------------------------------------------------- /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 | } 15 | 16 | impl std::fmt::Display for Migration { 17 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 18 | write!(f, "Migration {} ({})", self.version, self.description) 19 | } 20 | } 21 | 22 | impl Migration { 23 | pub fn new( 24 | version: i64, 25 | description: Cow<'static, str>, 26 | migration_type: MigrationType, 27 | sql: Cow<'static, str>, 28 | ) -> Self { 29 | let checksum = Cow::Owned(Vec::from(Sha384::digest(sql.as_bytes()).as_slice())); 30 | 31 | Migration { 32 | version, 33 | description, 34 | migration_type, 35 | sql, 36 | checksum, 37 | } 38 | } 39 | } 40 | 41 | #[derive(Debug, Clone)] 42 | pub struct AppliedMigration { 43 | pub version: i64, 44 | pub checksum: Cow<'static, [u8]>, 45 | } 46 | -------------------------------------------------------------------------------- /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, MigrateResult}; 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 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/column.rs: -------------------------------------------------------------------------------- 1 | use crate::column::Column; 2 | use crate::ext::ustr::UStr; 3 | use crate::mssql::protocol::col_meta_data::{ColumnData, Flags}; 4 | use crate::mssql::{Mssql, MssqlTypeInfo}; 5 | 6 | #[derive(Debug, Clone)] 7 | #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] 8 | pub struct MssqlColumn { 9 | pub(crate) ordinal: usize, 10 | pub(crate) name: UStr, 11 | pub(crate) type_info: MssqlTypeInfo, 12 | pub(crate) flags: Flags, 13 | } 14 | 15 | impl crate::column::private_column::Sealed for MssqlColumn {} 16 | 17 | impl MssqlColumn { 18 | pub(crate) fn new(meta: ColumnData, ordinal: usize) -> Self { 19 | Self { 20 | name: UStr::from(meta.col_name), 21 | type_info: MssqlTypeInfo(meta.type_info), 22 | ordinal, 23 | flags: meta.flags, 24 | } 25 | } 26 | } 27 | 28 | impl Column for MssqlColumn { 29 | type Database = Mssql; 30 | 31 | fn ordinal(&self) -> usize { 32 | self.ordinal 33 | } 34 | 35 | fn name(&self) -> &str { 36 | &*self.name 37 | } 38 | 39 | fn type_info(&self) -> &MssqlTypeInfo { 40 | &self.type_info 41 | } 42 | } 43 | 44 | #[cfg(feature = "any")] 45 | impl From for crate::any::AnyColumn { 46 | #[inline] 47 | fn from(column: MssqlColumn) -> Self { 48 | crate::any::AnyColumn { 49 | type_info: column.type_info.clone().into(), 50 | kind: crate::any::column::AnyColumnKind::Mssql(column), 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/database.rs: -------------------------------------------------------------------------------- 1 | use crate::database::{Database, HasArguments, HasStatement, HasValueRef}; 2 | use crate::mssql::{ 3 | MssqlArguments, MssqlColumn, MssqlConnection, MssqlQueryResult, MssqlRow, MssqlStatement, 4 | MssqlTransactionManager, MssqlTypeInfo, MssqlValue, MssqlValueRef, 5 | }; 6 | 7 | /// MSSQL database driver. 8 | #[derive(Debug)] 9 | pub struct Mssql; 10 | 11 | impl Database for Mssql { 12 | type Connection = MssqlConnection; 13 | 14 | type TransactionManager = MssqlTransactionManager; 15 | 16 | type Row = MssqlRow; 17 | 18 | type QueryResult = MssqlQueryResult; 19 | 20 | type Column = MssqlColumn; 21 | 22 | type TypeInfo = MssqlTypeInfo; 23 | 24 | type Value = MssqlValue; 25 | } 26 | 27 | impl<'r> HasValueRef<'r> for Mssql { 28 | type Database = Mssql; 29 | 30 | type ValueRef = MssqlValueRef<'r>; 31 | } 32 | 33 | impl<'q> HasStatement<'q> for Mssql { 34 | type Database = Mssql; 35 | 36 | type Statement = MssqlStatement<'q>; 37 | } 38 | 39 | impl HasArguments<'_> for Mssql { 40 | type Database = Mssql; 41 | 42 | type Arguments = MssqlArguments; 43 | 44 | type ArgumentBuffer = Vec; 45 | } 46 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use std::fmt::{self, Debug, Display, Formatter}; 3 | 4 | use crate::error::DatabaseError; 5 | use crate::mssql::protocol::error::Error; 6 | 7 | /// An error returned from the MSSQL database. 8 | pub struct MssqlDatabaseError(pub(crate) Error); 9 | 10 | impl Debug for MssqlDatabaseError { 11 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 12 | f.debug_struct("MssqlDatabaseError") 13 | .field("message", &self.0.message) 14 | .field("number", &self.0.number) 15 | .field("state", &self.0.state) 16 | .field("class", &self.0.class) 17 | .field("server", &self.0.server) 18 | .field("procedure", &self.0.procedure) 19 | .field("line", &self.0.line) 20 | .finish() 21 | } 22 | } 23 | 24 | impl Display for MssqlDatabaseError { 25 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 26 | if self.0.line > 1 { 27 | write!(f, "On line {}: ", self.0.line)?; 28 | } 29 | write!(f, "{}", self.message()) 30 | } 31 | } 32 | 33 | impl StdError for MssqlDatabaseError {} 34 | 35 | impl DatabaseError for MssqlDatabaseError { 36 | #[inline] 37 | fn message(&self) -> &str { 38 | &self.0.message 39 | } 40 | 41 | #[doc(hidden)] 42 | fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static) { 43 | self 44 | } 45 | 46 | #[doc(hidden)] 47 | fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) { 48 | self 49 | } 50 | 51 | #[doc(hidden)] 52 | fn into_error(self: Box) -> Box { 53 | self 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/io/buf.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | 3 | use crate::error::Error; 4 | use crate::io::BufExt; 5 | 6 | pub trait MssqlBufExt: Buf { 7 | fn get_utf16_str(&mut self, n: usize) -> Result; 8 | 9 | fn get_b_varchar(&mut self) -> Result; 10 | 11 | fn get_us_varchar(&mut self) -> Result; 12 | 13 | fn get_b_varbyte(&mut self) -> Bytes; 14 | } 15 | 16 | impl MssqlBufExt for Bytes { 17 | fn get_utf16_str(&mut self, mut n: usize) -> Result { 18 | let mut raw = Vec::with_capacity(n * 2); 19 | 20 | while n > 0 { 21 | let ch = self.get_u16_le(); 22 | raw.push(ch); 23 | n -= 1; 24 | } 25 | 26 | String::from_utf16(&raw).map_err(Error::protocol) 27 | } 28 | 29 | fn get_b_varchar(&mut self) -> Result { 30 | let size = self.get_u8(); 31 | self.get_utf16_str(size as usize) 32 | } 33 | 34 | fn get_us_varchar(&mut self) -> Result { 35 | let size = self.get_u16_le(); 36 | self.get_utf16_str(size as usize) 37 | } 38 | 39 | fn get_b_varbyte(&mut self) -> Bytes { 40 | let size = self.get_u8(); 41 | self.get_bytes(size as usize) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/io/buf_mut.rs: -------------------------------------------------------------------------------- 1 | pub trait MssqlBufMutExt { 2 | fn put_b_varchar(&mut self, s: &str); 3 | fn put_utf16_str(&mut self, s: &str); 4 | } 5 | 6 | impl MssqlBufMutExt for Vec { 7 | fn put_utf16_str(&mut self, s: &str) { 8 | let mut enc = s.encode_utf16(); 9 | while let Some(ch) = enc.next() { 10 | self.extend_from_slice(&ch.to_le_bytes()); 11 | } 12 | } 13 | 14 | fn put_b_varchar(&mut self, s: &str) { 15 | self.extend(&(s.len() as u8).to_le_bytes()); 16 | self.put_utf16_str(s); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/io/mod.rs: -------------------------------------------------------------------------------- 1 | mod buf; 2 | mod buf_mut; 3 | 4 | pub(crate) use buf::MssqlBufExt; 5 | pub(crate) use buf_mut::MssqlBufMutExt; 6 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/options/connect.rs: -------------------------------------------------------------------------------- 1 | use crate::connection::ConnectOptions; 2 | use crate::error::Error; 3 | use crate::mssql::{MssqlConnectOptions, MssqlConnection}; 4 | use futures_core::future::BoxFuture; 5 | use log::LevelFilter; 6 | use std::time::Duration; 7 | 8 | impl ConnectOptions for MssqlConnectOptions { 9 | type Connection = MssqlConnection; 10 | 11 | fn connect(&self) -> BoxFuture<'_, Result> 12 | where 13 | Self::Connection: Sized, 14 | { 15 | Box::pin(MssqlConnection::establish(self)) 16 | } 17 | 18 | fn log_statements(&mut self, level: LevelFilter) -> &mut Self { 19 | self.log_settings.log_statements(level); 20 | self 21 | } 22 | 23 | fn log_slow_statements(&mut self, level: LevelFilter, duration: Duration) -> &mut Self { 24 | self.log_settings.log_slow_statements(level, duration); 25 | self 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/protocol/header.rs: -------------------------------------------------------------------------------- 1 | use crate::io::Encode; 2 | 3 | pub(crate) struct AllHeaders<'a>(pub(crate) &'a [Header]); 4 | 5 | impl Encode<'_> for AllHeaders<'_> { 6 | fn encode_with(&self, buf: &mut Vec, _: ()) { 7 | let offset = buf.len(); 8 | buf.resize(buf.len() + 4, 0); 9 | 10 | for header in self.0 { 11 | header.encode_with(buf, ()); 12 | } 13 | 14 | let len = buf.len() - offset; 15 | buf[offset..(offset + 4)].copy_from_slice(&(len as u32).to_le_bytes()); 16 | } 17 | } 18 | 19 | pub(crate) enum Header { 20 | TransactionDescriptor { 21 | // number of requests currently active on the connection 22 | outstanding_request_count: u32, 23 | 24 | // for each connection, a number that uniquely identifies the transaction with which the 25 | // request is associated; initially generated by the server when a new transaction is 26 | // created and returned to the client as part of the ENVCHANGE token stream 27 | transaction_descriptor: u64, 28 | }, 29 | } 30 | 31 | impl Encode<'_> for Header { 32 | fn encode_with(&self, buf: &mut Vec, _: ()) { 33 | match self { 34 | Header::TransactionDescriptor { 35 | outstanding_request_count, 36 | transaction_descriptor, 37 | } => { 38 | buf.extend(&18_u32.to_le_bytes()); // [HeaderLength] 4 + 2 + 8 + 4 39 | buf.extend(&2_u16.to_le_bytes()); // [HeaderType] 40 | buf.extend(&transaction_descriptor.to_le_bytes()); 41 | buf.extend(&outstanding_request_count.to_le_bytes()); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/protocol/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod col_meta_data; 2 | pub(crate) mod done; 3 | pub(crate) mod env_change; 4 | pub(crate) mod error; 5 | pub(crate) mod header; 6 | pub(crate) mod info; 7 | pub(crate) mod login; 8 | pub(crate) mod login_ack; 9 | pub(crate) mod message; 10 | pub(crate) mod order; 11 | pub(crate) mod packet; 12 | pub(crate) mod pre_login; 13 | pub(crate) mod return_status; 14 | pub(crate) mod return_value; 15 | pub(crate) mod row; 16 | pub(crate) mod rpc; 17 | pub(crate) mod sql_batch; 18 | pub(crate) mod type_info; 19 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/protocol/order.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | 3 | use crate::error::Error; 4 | 5 | #[derive(Debug)] 6 | pub(crate) struct Order { 7 | #[allow(dead_code)] 8 | columns: Bytes, 9 | } 10 | 11 | impl Order { 12 | pub(crate) fn get(buf: &mut Bytes) -> Result { 13 | let len = buf.get_u16_le(); 14 | let columns = buf.split_to(len as usize); 15 | 16 | Ok(Self { columns }) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/protocol/return_status.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | 3 | use crate::error::Error; 4 | 5 | #[derive(Debug)] 6 | pub(crate) struct ReturnStatus { 7 | #[allow(dead_code)] 8 | value: i32, 9 | } 10 | 11 | impl ReturnStatus { 12 | pub(crate) fn get(buf: &mut Bytes) -> Result { 13 | let value = buf.get_i32_le(); 14 | 15 | Ok(Self { value }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/protocol/row.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | 3 | use crate::error::Error; 4 | use crate::io::BufExt; 5 | use crate::mssql::{MssqlColumn, MssqlTypeInfo}; 6 | 7 | #[derive(Debug)] 8 | pub(crate) struct Row { 9 | pub(crate) column_types: Vec, 10 | pub(crate) values: Vec>, 11 | } 12 | 13 | impl Row { 14 | pub(crate) fn get( 15 | buf: &mut Bytes, 16 | nullable: bool, 17 | columns: &[MssqlColumn], 18 | ) -> Result { 19 | let mut values = Vec::with_capacity(columns.len()); 20 | let mut column_types = Vec::with_capacity(columns.len()); 21 | 22 | let nulls = if nullable { 23 | buf.get_bytes((columns.len() + 7) / 8) 24 | } else { 25 | Bytes::from_static(b"") 26 | }; 27 | 28 | for (i, column) in columns.iter().enumerate() { 29 | column_types.push(column.type_info.clone()); 30 | 31 | if !(column.type_info.0.is_null() || (nullable && (nulls[i / 8] & (1 << (i % 8))) != 0)) 32 | { 33 | values.push(column.type_info.0.get_value(buf)); 34 | } else { 35 | values.push(None); 36 | } 37 | } 38 | 39 | Ok(Self { 40 | values, 41 | column_types, 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/protocol/sql_batch.rs: -------------------------------------------------------------------------------- 1 | use crate::io::Encode; 2 | use crate::mssql::io::MssqlBufMutExt; 3 | use crate::mssql::protocol::header::{AllHeaders, Header}; 4 | 5 | #[derive(Debug)] 6 | pub(crate) struct SqlBatch<'a> { 7 | pub(crate) transaction_descriptor: u64, 8 | pub(crate) sql: &'a str, 9 | } 10 | 11 | impl Encode<'_> for SqlBatch<'_> { 12 | fn encode_with(&self, buf: &mut Vec, _: ()) { 13 | AllHeaders(&[Header::TransactionDescriptor { 14 | outstanding_request_count: 1, 15 | transaction_descriptor: self.transaction_descriptor, 16 | }]) 17 | .encode(buf); 18 | 19 | // SQLText 20 | buf.put_utf16_str(self.sql); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/query_result.rs: -------------------------------------------------------------------------------- 1 | use std::iter::{Extend, IntoIterator}; 2 | 3 | #[derive(Debug, Default)] 4 | pub struct MssqlQueryResult { 5 | pub(super) rows_affected: u64, 6 | } 7 | 8 | impl MssqlQueryResult { 9 | pub fn rows_affected(&self) -> u64 { 10 | self.rows_affected 11 | } 12 | } 13 | 14 | impl Extend for MssqlQueryResult { 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 crate::any::AnyQueryResult { 24 | fn from(done: MssqlQueryResult) -> Self { 25 | crate::any::AnyQueryResult { 26 | rows_affected: done.rows_affected, 27 | last_insert_id: None, 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/row.rs: -------------------------------------------------------------------------------- 1 | use crate::column::ColumnIndex; 2 | use crate::error::Error; 3 | use crate::ext::ustr::UStr; 4 | use crate::mssql::protocol::row::Row as ProtocolRow; 5 | use crate::mssql::{Mssql, MssqlColumn, MssqlValueRef}; 6 | use crate::row::Row; 7 | use crate::HashMap; 8 | use std::sync::Arc; 9 | 10 | pub struct MssqlRow { 11 | pub(crate) row: ProtocolRow, 12 | pub(crate) columns: Arc>, 13 | pub(crate) column_names: Arc>, 14 | } 15 | 16 | impl crate::row::private_row::Sealed for MssqlRow {} 17 | 18 | impl Row for MssqlRow { 19 | type Database = Mssql; 20 | 21 | fn columns(&self) -> &[MssqlColumn] { 22 | &self.columns 23 | } 24 | 25 | fn try_get_raw(&self, index: I) -> Result, Error> 26 | where 27 | I: ColumnIndex, 28 | { 29 | let index = index.index(self)?; 30 | let value = MssqlValueRef { 31 | data: self.row.values[index].as_ref(), 32 | type_info: self.row.column_types[index].clone(), 33 | }; 34 | 35 | Ok(value) 36 | } 37 | } 38 | 39 | impl ColumnIndex for &'_ str { 40 | fn index(&self, row: &MssqlRow) -> Result { 41 | row.column_names 42 | .get(*self) 43 | .ok_or_else(|| Error::ColumnNotFound((*self).into())) 44 | .copied() 45 | } 46 | } 47 | 48 | #[cfg(feature = "any")] 49 | impl From for crate::any::AnyRow { 50 | #[inline] 51 | fn from(row: MssqlRow) -> Self { 52 | crate::any::AnyRow { 53 | columns: row.columns.iter().map(|col| col.clone().into()).collect(), 54 | kind: crate::any::row::AnyRowKind::Mssql(row), 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/type_info.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display, Formatter}; 2 | 3 | use crate::mssql::protocol::type_info::{DataType, TypeInfo as ProtocolTypeInfo}; 4 | use crate::type_info::TypeInfo; 5 | 6 | #[derive(Debug, Clone, PartialEq, Eq)] 7 | #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] 8 | pub struct MssqlTypeInfo(pub(crate) ProtocolTypeInfo); 9 | 10 | impl TypeInfo for MssqlTypeInfo { 11 | fn is_null(&self) -> bool { 12 | matches!(self.0.ty, DataType::Null) 13 | } 14 | 15 | fn name(&self) -> &str { 16 | self.0.name() 17 | } 18 | } 19 | 20 | impl Display for MssqlTypeInfo { 21 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 22 | f.pad(self.name()) 23 | } 24 | } 25 | 26 | #[cfg(feature = "any")] 27 | impl From for crate::any::AnyTypeInfo { 28 | #[inline] 29 | fn from(ty: MssqlTypeInfo) -> Self { 30 | crate::any::AnyTypeInfo(crate::any::type_info::AnyTypeInfoKind::Mssql(ty)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/types/bool.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::Decode; 2 | use crate::encode::{Encode, IsNull}; 3 | use crate::error::BoxDynError; 4 | use crate::mssql::protocol::type_info::{DataType, TypeInfo}; 5 | use crate::mssql::{Mssql, MssqlTypeInfo, MssqlValueRef}; 6 | use crate::types::Type; 7 | 8 | impl Type for bool { 9 | fn type_info() -> MssqlTypeInfo { 10 | MssqlTypeInfo(TypeInfo::new(DataType::BitN, 1)) 11 | } 12 | 13 | fn compatible(ty: &MssqlTypeInfo) -> bool { 14 | matches!(ty.0.ty, DataType::Bit | DataType::BitN) 15 | } 16 | } 17 | 18 | impl Encode<'_, Mssql> for bool { 19 | fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { 20 | buf.push(if *self { 1 } else { 0 }); 21 | 22 | IsNull::No 23 | } 24 | } 25 | 26 | impl Decode<'_, Mssql> for bool { 27 | fn decode(value: MssqlValueRef<'_>) -> Result { 28 | Ok(value.as_bytes()?[0] == 1) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/types/json.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::Decode; 2 | use crate::encode::{Encode, IsNull}; 3 | use crate::error::BoxDynError; 4 | use crate::mssql::protocol::type_info::{DataType, TypeInfo}; 5 | use crate::mssql::{Mssql, MssqlTypeInfo, MssqlValueRef}; 6 | use crate::types::Json; 7 | use crate::types::Type; 8 | use serde::{Deserialize, Serialize}; 9 | 10 | impl Type for Json { 11 | fn type_info() -> MssqlTypeInfo { 12 | MssqlTypeInfo(TypeInfo::new(DataType::BigVarBinary, 0)) 13 | } 14 | 15 | fn compatible(ty: &MssqlTypeInfo) -> bool { 16 | matches!( 17 | ty.0.ty, 18 | DataType::VarBinary 19 | | DataType::Binary 20 | | DataType::BigVarBinary 21 | | DataType::BigBinary 22 | | DataType::VarChar 23 | | DataType::Char 24 | | DataType::BigVarChar 25 | | DataType::BigChar 26 | ) 27 | } 28 | } 29 | 30 | impl Encode<'_, Mssql> for Json 31 | where 32 | T: Serialize, 33 | { 34 | fn produces(&self) -> Option { 35 | let size = 0xFF_FF; 36 | Some(MssqlTypeInfo(TypeInfo::new(DataType::BigVarBinary, size))) 37 | } 38 | 39 | fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { 40 | serde_json::to_writer(buf, self).unwrap(); 41 | IsNull::No 42 | } 43 | } 44 | 45 | impl<'r, T: 'r> Decode<'r, Mssql> for Json 46 | where 47 | T: Deserialize<'r>, 48 | { 49 | fn decode(value: MssqlValueRef<'r>) -> Result { 50 | let buf = value.as_bytes()?; 51 | serde_json::from_slice(buf).map(Json).map_err(Into::into) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /sqlx-core/src/mssql/types/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::encode::{Encode, IsNull}; 2 | use crate::mssql::protocol::type_info::{DataType, TypeInfo}; 3 | use crate::mssql::{Mssql, MssqlTypeInfo}; 4 | 5 | mod bool; 6 | mod bytes; 7 | mod float; 8 | mod int; 9 | mod str; 10 | mod uint; 11 | 12 | mod decimal_tools; 13 | 14 | #[cfg(feature = "chrono")] 15 | mod chrono; 16 | 17 | #[cfg(feature = "json")] 18 | mod json; 19 | 20 | #[cfg(feature = "decimal")] 21 | mod decimal; 22 | 23 | #[cfg(feature = "bigdecimal")] 24 | mod bigdecimal; 25 | 26 | impl<'q, T: 'q + Encode<'q, Mssql>> Encode<'q, Mssql> for Option { 27 | fn encode(self, buf: &mut Vec) -> IsNull { 28 | if let Some(v) = self { 29 | v.encode(buf) 30 | } else { 31 | IsNull::Yes 32 | } 33 | } 34 | 35 | fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { 36 | if let Some(v) = self { 37 | v.encode_by_ref(buf) 38 | } else { 39 | IsNull::Yes 40 | } 41 | } 42 | 43 | fn produces(&self) -> Option { 44 | if let Some(v) = self { 45 | v.produces() 46 | } else { 47 | // MSSQL requires a special NULL type ID 48 | Some(MssqlTypeInfo(TypeInfo::new(DataType::Null, 0))) 49 | } 50 | } 51 | 52 | fn size_hint(&self) -> usize { 53 | self.as_ref().map_or(0, Encode::size_hint) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/arguments.rs: -------------------------------------------------------------------------------- 1 | use crate::arguments::Arguments; 2 | use crate::encode::{Encode, IsNull}; 3 | use crate::mysql::{MySql, MySqlTypeInfo}; 4 | use crate::types::Type; 5 | 6 | /// Implementation of [`Arguments`] for MySQL. 7 | #[derive(Debug, Default, Clone)] 8 | pub struct MySqlArguments { 9 | pub(crate) values: Vec, 10 | pub(crate) types: Vec, 11 | pub(crate) null_bitmap: Vec, 12 | } 13 | 14 | impl MySqlArguments { 15 | pub(crate) fn add<'q, T>(&mut self, value: T) 16 | where 17 | T: Encode<'q, MySql> + Type, 18 | { 19 | let ty = value.produces().unwrap_or_else(T::type_info); 20 | let index = self.types.len(); 21 | 22 | self.types.push(ty); 23 | self.null_bitmap.resize((index / 8) + 1, 0); 24 | 25 | if let IsNull::Yes = value.encode(&mut self.values) { 26 | self.null_bitmap[index / 8] |= (1 << (index % 8)) as u8; 27 | } 28 | } 29 | 30 | #[doc(hidden)] 31 | pub fn len(&self) -> usize { 32 | self.types.len() 33 | } 34 | } 35 | 36 | impl<'q> Arguments<'q> for MySqlArguments { 37 | type Database = MySql; 38 | 39 | fn reserve(&mut self, len: usize, size: usize) { 40 | self.types.reserve(len); 41 | self.values.reserve(size); 42 | } 43 | 44 | fn add(&mut self, value: T) 45 | where 46 | T: Encode<'q, Self::Database> + Type, 47 | { 48 | self.add(value) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/column.rs: -------------------------------------------------------------------------------- 1 | use crate::column::Column; 2 | use crate::ext::ustr::UStr; 3 | use crate::mysql::protocol::text::ColumnFlags; 4 | use crate::mysql::{MySql, MySqlTypeInfo}; 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 crate::column::private_column::Sealed for MySqlColumn {} 18 | 19 | impl Column for MySqlColumn { 20 | type Database = MySql; 21 | 22 | fn ordinal(&self) -> usize { 23 | self.ordinal 24 | } 25 | 26 | fn name(&self) -> &str { 27 | &*self.name 28 | } 29 | 30 | fn type_info(&self) -> &MySqlTypeInfo { 31 | &self.type_info 32 | } 33 | } 34 | 35 | #[cfg(feature = "any")] 36 | impl From for crate::any::AnyColumn { 37 | #[inline] 38 | fn from(column: MySqlColumn) -> Self { 39 | crate::any::AnyColumn { 40 | type_info: column.type_info.clone().into(), 41 | kind: crate::any::column::AnyColumnKind::MySql(column), 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/database.rs: -------------------------------------------------------------------------------- 1 | use crate::database::{Database, HasArguments, HasStatement, HasStatementCache, HasValueRef}; 2 | use crate::mysql::value::{MySqlValue, MySqlValueRef}; 3 | use crate::mysql::{ 4 | MySqlArguments, MySqlColumn, MySqlConnection, MySqlQueryResult, MySqlRow, MySqlStatement, 5 | MySqlTransactionManager, MySqlTypeInfo, 6 | }; 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 | } 27 | 28 | impl<'r> HasValueRef<'r> for MySql { 29 | type Database = MySql; 30 | 31 | type ValueRef = MySqlValueRef<'r>; 32 | } 33 | 34 | impl HasArguments<'_> for MySql { 35 | type Database = MySql; 36 | 37 | type Arguments = MySqlArguments; 38 | 39 | type ArgumentBuffer = Vec; 40 | } 41 | 42 | impl<'q> HasStatement<'q> for MySql { 43 | type Database = MySql; 44 | 45 | type Statement = MySqlStatement<'q>; 46 | } 47 | 48 | impl HasStatementCache for MySql {} 49 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/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) -> Bytes; 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 | self.get_str(size as usize) 35 | } 36 | 37 | fn get_bytes_lenenc(&mut self) -> Bytes { 38 | let size = self.get_uint_lenenc(); 39 | self.split_to(size as usize) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/io/mod.rs: -------------------------------------------------------------------------------- 1 | mod buf; 2 | mod buf_mut; 3 | 4 | pub use buf::MySqlBufExt; 5 | pub use buf_mut::MySqlBufMutExt; 6 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/protocol/auth.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use crate::error::Error; 4 | 5 | #[derive(Debug, Copy, Clone)] 6 | pub enum AuthPlugin { 7 | MySqlNativePassword, 8 | CachingSha2Password, 9 | Sha256Password, 10 | } 11 | 12 | impl AuthPlugin { 13 | pub(crate) fn name(self) -> &'static str { 14 | match self { 15 | AuthPlugin::MySqlNativePassword => "mysql_native_password", 16 | AuthPlugin::CachingSha2Password => "caching_sha2_password", 17 | AuthPlugin::Sha256Password => "sha256_password", 18 | } 19 | } 20 | } 21 | 22 | impl FromStr for AuthPlugin { 23 | type Err = Error; 24 | 25 | fn from_str(s: &str) -> Result { 26 | match s { 27 | "mysql_native_password" => Ok(AuthPlugin::MySqlNativePassword), 28 | "caching_sha2_password" => Ok(AuthPlugin::CachingSha2Password), 29 | "sha256_password" => Ok(AuthPlugin::Sha256Password), 30 | 31 | _ => Err(err_protocol!("unknown authentication plugin: {}", s)), 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/protocol/connect/auth_switch.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | 3 | use crate::error::Error; 4 | use crate::io::Encode; 5 | use crate::io::{BufExt, Decode}; 6 | use crate::mysql::protocol::auth::AuthPlugin; 7 | use crate::mysql::protocol::Capabilities; 8 | 9 | // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_connection_phase_packets_protocol_auth_switch_request.html 10 | 11 | #[derive(Debug)] 12 | pub struct AuthSwitchRequest { 13 | pub plugin: AuthPlugin, 14 | pub data: Bytes, 15 | } 16 | 17 | impl Decode<'_> for AuthSwitchRequest { 18 | fn decode_with(mut buf: Bytes, _: ()) -> Result { 19 | let header = buf.get_u8(); 20 | if header != 0xfe { 21 | return Err(err_protocol!( 22 | "expected 0xfe (AUTH_SWITCH) but found 0x{:x}", 23 | header 24 | )); 25 | } 26 | 27 | let plugin = buf.get_str_nul()?.parse()?; 28 | 29 | // See: https://github.com/mysql/mysql-server/blob/ea7d2e2d16ac03afdd9cb72a972a95981107bf51/sql/auth/sha2_password.cc#L942 30 | if buf.len() != 21 { 31 | return Err(err_protocol!( 32 | "expected 21 bytes but found {} bytes", 33 | buf.len() 34 | )); 35 | } 36 | let data = buf.get_bytes(20); 37 | buf.advance(1); // NUL-terminator 38 | 39 | Ok(Self { plugin, data }) 40 | } 41 | } 42 | 43 | #[derive(Debug)] 44 | pub struct AuthSwitchResponse(pub Vec); 45 | 46 | impl Encode<'_, Capabilities> for AuthSwitchResponse { 47 | fn encode_with(&self, buf: &mut Vec, _: Capabilities) { 48 | buf.extend_from_slice(&self.0); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/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-core/src/mysql/protocol/connect/ssl_request.rs: -------------------------------------------------------------------------------- 1 | use crate::io::Encode; 2 | use crate::mysql::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 Encode<'_, Capabilities> for SslRequest { 14 | fn encode_with(&self, buf: &mut Vec, capabilities: Capabilities) { 15 | buf.extend(&(capabilities.bits() as u32).to_le_bytes()); 16 | buf.extend(&self.max_packet_size.to_le_bytes()); 17 | buf.push(self.collation); 18 | 19 | // reserved: string<19> 20 | buf.extend(&[0_u8; 19]); 21 | 22 | if capabilities.contains(Capabilities::MYSQL) { 23 | // reserved: string<4> 24 | buf.extend(&[0_u8; 4]); 25 | } else { 26 | // extended client capabilities (MariaDB-specified): int<4> 27 | buf.extend(&((capabilities.bits() >> 32) as u32).to_le_bytes()); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/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-core/src/mysql/protocol/response/eof.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | 3 | use crate::error::Error; 4 | use crate::io::Decode; 5 | use crate::mysql::protocol::response::Status; 6 | use crate::mysql::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 | pub warnings: u16, 17 | pub status: Status, 18 | } 19 | 20 | impl Decode<'_, Capabilities> for EofPacket { 21 | fn decode_with(mut buf: Bytes, _: Capabilities) -> Result { 22 | let header = buf.get_u8(); 23 | if header != 0xfe { 24 | return Err(err_protocol!( 25 | "expected 0xfe (EOF_Packet) but found 0x{:x}", 26 | header 27 | )); 28 | } 29 | 30 | let warnings = buf.get_u16_le(); 31 | let status = Status::from_bits_truncate(buf.get_u16_le()); 32 | 33 | Ok(Self { status, warnings }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/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-core/src/mysql/protocol/response/ok.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | 3 | use crate::error::Error; 4 | use crate::io::Decode; 5 | use crate::mysql::io::MySqlBufExt; 6 | use crate::mysql::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 Decode<'_> 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-core/src/mysql/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] 14 | .as_ref() 15 | .map(|col| &self.storage[(col.start as usize)..(col.end as usize)]) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/protocol/statement/execute.rs: -------------------------------------------------------------------------------- 1 | use crate::io::Encode; 2 | use crate::mysql::protocol::text::ColumnFlags; 3 | use crate::mysql::protocol::Capabilities; 4 | use crate::mysql::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<'q> Encode<'_, Capabilities> for Execute<'q> { 15 | fn encode_with(&self, buf: &mut Vec, _: Capabilities) { 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(&*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 | } 39 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/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-core/src/mysql/protocol/statement/prepare.rs: -------------------------------------------------------------------------------- 1 | use crate::io::Encode; 2 | use crate::mysql::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 Encode<'_, Capabilities> for Prepare<'_> { 11 | fn encode_with(&self, buf: &mut Vec, _: Capabilities) { 12 | buf.push(0x16); // COM_STMT_PREPARE 13 | buf.extend(self.query.as_bytes()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/protocol/statement/prepare_ok.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | 3 | use crate::error::Error; 4 | use crate::io::Decode; 5 | use crate::mysql::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 Decode<'_, Capabilities> for PrepareOk { 19 | fn decode_with(mut buf: Bytes, _: Capabilities) -> Result { 20 | let status = buf.get_u8(); 21 | if status != 0x00 { 22 | return Err(err_protocol!( 23 | "expected 0x00 (COM_STMT_PREPARE_OK) but found 0x{:02x}", 24 | status 25 | )); 26 | } 27 | 28 | let statement_id = buf.get_u32_le(); 29 | let columns = buf.get_u16_le(); 30 | let params = buf.get_u16_le(); 31 | 32 | buf.advance(1); // reserved: string<1> 33 | 34 | let warnings = buf.get_u16_le(); 35 | 36 | Ok(Self { 37 | statement_id, 38 | columns, 39 | params, 40 | warnings, 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/protocol/statement/stmt_close.rs: -------------------------------------------------------------------------------- 1 | use crate::io::Encode; 2 | use crate::mysql::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 Encode<'_, Capabilities> for StmtClose { 12 | fn encode_with(&self, buf: &mut Vec, _: Capabilities) { 13 | buf.push(0x19); // COM_STMT_CLOSE 14 | buf.extend(&self.statement.to_le_bytes()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/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-core/src/mysql/protocol/text/ping.rs: -------------------------------------------------------------------------------- 1 | use crate::io::Encode; 2 | use crate::mysql::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 Encode<'_, Capabilities> for Ping { 10 | fn encode_with(&self, buf: &mut Vec, _: Capabilities) { 11 | buf.push(0x0e); // COM_PING 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/protocol/text/query.rs: -------------------------------------------------------------------------------- 1 | use crate::io::Encode; 2 | use crate::mysql::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 Encode<'_, Capabilities> for Query<'_> { 10 | fn encode_with(&self, buf: &mut Vec, _: Capabilities) { 11 | buf.push(0x03); // COM_QUERY 12 | buf.extend(self.0.as_bytes()) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/protocol/text/quit.rs: -------------------------------------------------------------------------------- 1 | use crate::io::Encode; 2 | use crate::mysql::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 Encode<'_, Capabilities> for Quit { 10 | fn encode_with(&self, buf: &mut Vec, _: Capabilities) { 11 | buf.push(0x01); // COM_QUIT 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/protocol/text/row.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | 3 | use crate::error::Error; 4 | use crate::io::Decode; 5 | use crate::mysql::io::MySqlBufExt; 6 | use crate::mysql::protocol::Row; 7 | use crate::mysql::MySqlColumn; 8 | 9 | #[derive(Debug)] 10 | pub(crate) struct TextRow(pub(crate) Row); 11 | 12 | impl<'de> Decode<'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 _ 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() as usize; 26 | let offset = offset - buf.len(); 27 | 28 | values.push(Some(offset..(offset + size))); 29 | 30 | buf.advance(size); 31 | } 32 | } 33 | 34 | Ok(TextRow(Row { values, storage })) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/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 | 28 | #[cfg(feature = "any")] 29 | impl From for crate::any::AnyQueryResult { 30 | fn from(done: MySqlQueryResult) -> Self { 31 | crate::any::AnyQueryResult { 32 | rows_affected: done.rows_affected, 33 | last_insert_id: Some(done.last_insert_id as i64), 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/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::mysql::io::MySqlBufMutExt; 7 | use crate::mysql::protocol::text::ColumnType; 8 | use crate::mysql::{MySql, MySqlTypeInfo, MySqlValueRef}; 9 | use crate::types::Type; 10 | 11 | impl Type for BigDecimal { 12 | fn type_info() -> MySqlTypeInfo { 13 | MySqlTypeInfo::binary(ColumnType::NewDecimal) 14 | } 15 | } 16 | 17 | impl Encode<'_, MySql> for BigDecimal { 18 | fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { 19 | buf.put_str_lenenc(&self.to_string()); 20 | 21 | IsNull::No 22 | } 23 | } 24 | 25 | impl Decode<'_, MySql> for BigDecimal { 26 | fn decode(value: MySqlValueRef<'_>) -> Result { 27 | Ok(value.as_str()?.parse()?) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/types/bool.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::Decode; 2 | use crate::encode::{Encode, IsNull}; 3 | use crate::error::BoxDynError; 4 | use crate::mysql::{ 5 | protocol::text::{ColumnFlags, ColumnType}, 6 | MySql, MySqlTypeInfo, MySqlValueRef, 7 | }; 8 | use crate::types::Type; 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 | char_set: 63, 16 | max_size: Some(1), 17 | r#type: ColumnType::Tiny, 18 | } 19 | } 20 | 21 | fn compatible(ty: &MySqlTypeInfo) -> bool { 22 | matches!( 23 | ty.r#type, 24 | ColumnType::Tiny 25 | | ColumnType::Short 26 | | ColumnType::Long 27 | | ColumnType::Int24 28 | | ColumnType::LongLong 29 | | ColumnType::Bit 30 | ) 31 | } 32 | } 33 | 34 | impl Encode<'_, MySql> for bool { 35 | fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { 36 | >::encode(*self as i8, buf) 37 | } 38 | } 39 | 40 | impl Decode<'_, MySql> for bool { 41 | fn decode(value: MySqlValueRef<'_>) -> Result { 42 | Ok(>::decode(value)? != 0) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/types/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::mysql::io::MySqlBufMutExt; 7 | use crate::mysql::protocol::text::ColumnType; 8 | use crate::mysql::{MySql, MySqlTypeInfo, MySqlValueRef}; 9 | use crate::types::Type; 10 | 11 | impl Type for Decimal { 12 | fn type_info() -> MySqlTypeInfo { 13 | MySqlTypeInfo::binary(ColumnType::NewDecimal) 14 | } 15 | } 16 | 17 | impl Encode<'_, MySql> for Decimal { 18 | fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { 19 | buf.put_str_lenenc(&self.to_string()); 20 | 21 | IsNull::No 22 | } 23 | } 24 | 25 | impl Decode<'_, MySql> for Decimal { 26 | fn decode(value: MySqlValueRef<'_>) -> Result { 27 | Ok(value.as_str()?.parse()?) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sqlx-core/src/mysql/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::mysql::protocol::text::ColumnType; 7 | use crate::mysql::{MySql, MySqlTypeInfo, MySqlValueRef}; 8 | use crate::types::{Json, Type}; 9 | 10 | impl Type for Json { 11 | fn type_info() -> MySqlTypeInfo { 12 | // MySql uses the `CHAR` type to pass JSON data from and to the client 13 | // NOTE: This is forwards-compatible with MySQL v8+ as CHAR is a common transmission format 14 | // and has nothing to do with the native storage ability of MySQL v8+ 15 | MySqlTypeInfo::binary(ColumnType::String) 16 | } 17 | 18 | fn compatible(ty: &MySqlTypeInfo) -> bool { 19 | ty.r#type == ColumnType::Json 20 | || <&str as Type>::compatible(ty) 21 | || <&[u8] as Type>::compatible(ty) 22 | } 23 | } 24 | 25 | impl Encode<'_, MySql> for Json 26 | where 27 | T: Serialize, 28 | { 29 | fn encode_by_ref(&self, buf: &mut Vec) -> IsNull { 30 | let json_string_value = 31 | serde_json::to_string(&self.0).expect("serde_json failed to convert to string"); 32 | 33 | <&str as Encode>::encode(json_string_value.as_str(), buf) 34 | } 35 | } 36 | 37 | impl<'r, T> Decode<'r, MySql> for Json 38 | where 39 | T: 'r + Deserialize<'r>, 40 | { 41 | fn decode(value: MySqlValueRef<'r>) -> Result { 42 | let string_value = <&str as Decode>::decode(value)?; 43 | 44 | serde_json::from_str(&string_value) 45 | .map(Json) 46 | .map_err(Into::into) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /sqlx-core/src/net/mod.rs: -------------------------------------------------------------------------------- 1 | mod socket; 2 | mod tls; 3 | 4 | #[allow(unused_imports)] 5 | pub use socket::Socket; 6 | #[allow(unused_imports)] 7 | pub use tls::CertificateInput; 8 | #[allow(unused_imports)] 9 | pub use tls::MaybeTlsStream; 10 | #[allow(unused_imports)] 11 | pub use tls::TlsConfig; 12 | 13 | #[cfg(feature = "_rt-async-std")] 14 | type PollReadBuf<'a> = [u8]; 15 | 16 | #[cfg(feature = "_rt-tokio")] 17 | type PollReadBuf<'a> = sqlx_rt::ReadBuf<'a>; 18 | 19 | #[cfg(feature = "_rt-async-std")] 20 | type PollReadOut = usize; 21 | 22 | #[cfg(feature = "_rt-tokio")] 23 | type PollReadOut = (); 24 | -------------------------------------------------------------------------------- /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(crate) enum MaybePoolConnection<'c, DB: Database> { 6 | #[allow(dead_code)] 7 | Connection(&'c mut DB::Connection), 8 | PoolConnection(PoolConnection), 9 | } 10 | 11 | impl<'c, DB: Database> Deref for MaybePoolConnection<'c, 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<'c, DB: Database> DerefMut for MaybePoolConnection<'c, 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 | #[allow(unused_macros)] 34 | macro_rules! impl_into_maybe_pool { 35 | ($DB:ident, $C:ident) => { 36 | impl<'c> From> 37 | for crate::pool::MaybePoolConnection<'c, $DB> 38 | { 39 | fn from(v: crate::pool::PoolConnection<$DB>) -> Self { 40 | crate::pool::MaybePoolConnection::PoolConnection(v) 41 | } 42 | } 43 | 44 | impl<'c> From<&'c mut $C> for crate::pool::MaybePoolConnection<'c, $DB> { 45 | fn from(v: &'c mut $C) -> Self { 46 | crate::pool::MaybePoolConnection::Connection(v) 47 | } 48 | } 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/column.rs: -------------------------------------------------------------------------------- 1 | use crate::column::Column; 2 | use crate::ext::ustr::UStr; 3 | use crate::postgres::{PgTypeInfo, Postgres}; 4 | 5 | #[derive(Debug, Clone)] 6 | #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] 7 | pub struct PgColumn { 8 | pub(crate) ordinal: usize, 9 | pub(crate) name: UStr, 10 | pub(crate) type_info: PgTypeInfo, 11 | #[cfg_attr(feature = "offline", serde(skip))] 12 | pub(crate) relation_id: Option, 13 | #[cfg_attr(feature = "offline", serde(skip))] 14 | pub(crate) relation_attribute_no: Option, 15 | } 16 | 17 | impl crate::column::private_column::Sealed for PgColumn {} 18 | 19 | impl Column for PgColumn { 20 | type Database = Postgres; 21 | 22 | fn ordinal(&self) -> usize { 23 | self.ordinal 24 | } 25 | 26 | fn name(&self) -> &str { 27 | &*self.name 28 | } 29 | 30 | fn type_info(&self) -> &PgTypeInfo { 31 | &self.type_info 32 | } 33 | } 34 | 35 | #[cfg(feature = "any")] 36 | impl From for crate::any::AnyColumn { 37 | #[inline] 38 | fn from(column: PgColumn) -> Self { 39 | crate::any::AnyColumn { 40 | type_info: column.type_info.clone().into(), 41 | kind: crate::any::column::AnyColumnKind::Postgres(column), 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/database.rs: -------------------------------------------------------------------------------- 1 | use crate::database::{Database, HasArguments, HasStatement, HasStatementCache, HasValueRef}; 2 | use crate::postgres::arguments::PgArgumentBuffer; 3 | use crate::postgres::value::{PgValue, PgValueRef}; 4 | use crate::postgres::{ 5 | PgArguments, PgColumn, PgConnection, PgQueryResult, PgRow, PgStatement, PgTransactionManager, 6 | PgTypeInfo, 7 | }; 8 | 9 | /// PostgreSQL database driver. 10 | #[derive(Debug)] 11 | pub struct Postgres; 12 | 13 | impl Database for Postgres { 14 | type Connection = PgConnection; 15 | 16 | type TransactionManager = PgTransactionManager; 17 | 18 | type Row = PgRow; 19 | 20 | type QueryResult = PgQueryResult; 21 | 22 | type Column = PgColumn; 23 | 24 | type TypeInfo = PgTypeInfo; 25 | 26 | type Value = PgValue; 27 | } 28 | 29 | impl<'r> HasValueRef<'r> for Postgres { 30 | type Database = Postgres; 31 | 32 | type ValueRef = PgValueRef<'r>; 33 | } 34 | 35 | impl HasArguments<'_> for Postgres { 36 | type Database = Postgres; 37 | 38 | type Arguments = PgArguments; 39 | 40 | type ArgumentBuffer = PgArgumentBuffer; 41 | } 42 | 43 | impl<'q> HasStatement<'q> for Postgres { 44 | type Database = Postgres; 45 | 46 | type Statement = PgStatement<'q>; 47 | } 48 | 49 | impl HasStatementCache for Postgres {} 50 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/io/buf_mut.rs: -------------------------------------------------------------------------------- 1 | use crate::postgres::types::Oid; 2 | 3 | pub trait PgBufMutExt { 4 | fn put_length_prefixed(&mut self, f: F) 5 | where 6 | F: FnOnce(&mut Vec); 7 | 8 | fn put_statement_name(&mut self, id: Oid); 9 | 10 | fn put_portal_name(&mut self, id: Option); 11 | } 12 | 13 | impl PgBufMutExt for Vec { 14 | // writes a length-prefixed message, this is used when encoding nearly all messages as postgres 15 | // wants us to send the length of the often-variable-sized messages up front 16 | fn put_length_prefixed(&mut self, f: F) 17 | where 18 | F: FnOnce(&mut Vec), 19 | { 20 | // reserve space to write the prefixed length 21 | let offset = self.len(); 22 | self.extend(&[0; 4]); 23 | 24 | // write the main body of the message 25 | f(self); 26 | 27 | // now calculate the size of what we wrote and set the length value 28 | let size = (self.len() - offset) as i32; 29 | self[offset..(offset + 4)].copy_from_slice(&size.to_be_bytes()); 30 | } 31 | 32 | // writes a statement name by ID 33 | #[inline] 34 | fn put_statement_name(&mut self, id: Oid) { 35 | // N.B. if you change this don't forget to update it in ../describe.rs 36 | self.extend(b"sqlx_s_"); 37 | 38 | self.extend(itoa::Buffer::new().format(id.0).as_bytes()); 39 | 40 | self.push(0); 41 | } 42 | 43 | // writes a portal name by ID 44 | #[inline] 45 | fn put_portal_name(&mut self, id: Option) { 46 | if let Some(id) = id { 47 | self.extend(b"sqlx_p_"); 48 | 49 | self.extend(itoa::Buffer::new().format(id.0).as_bytes()); 50 | } 51 | 52 | self.push(0); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/io/mod.rs: -------------------------------------------------------------------------------- 1 | mod buf_mut; 2 | 3 | pub use buf_mut::PgBufMutExt; 4 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/message/backend_key_data.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ByteOrder}; 2 | use bytes::Bytes; 3 | 4 | use crate::error::Error; 5 | use crate::io::Decode; 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 Decode<'_> for BackendKeyData { 19 | fn decode_with(buf: Bytes, _: ()) -> Result { 20 | let process_id = BigEndian::read_u32(&buf); 21 | let secret_key = BigEndian::read_u32(&buf[4..]); 22 | 23 | Ok(Self { 24 | process_id, 25 | secret_key, 26 | }) 27 | } 28 | } 29 | 30 | #[test] 31 | fn test_decode_backend_key_data() { 32 | const DATA: &[u8] = b"\0\0'\xc6\x89R\xc5+"; 33 | 34 | let m = BackendKeyData::decode(DATA.into()).unwrap(); 35 | 36 | assert_eq!(m.process_id, 10182); 37 | assert_eq!(m.secret_key, 2303903019); 38 | } 39 | 40 | #[cfg(all(test, not(debug_assertions)))] 41 | #[bench] 42 | fn bench_decode_backend_key_data(b: &mut test::Bencher) { 43 | const DATA: &[u8] = b"\0\0'\xc6\x89R\xc5+"; 44 | 45 | b.iter(|| { 46 | BackendKeyData::decode(test::black_box(Bytes::from_static(DATA))).unwrap(); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/message/close.rs: -------------------------------------------------------------------------------- 1 | use crate::io::Encode; 2 | use crate::postgres::io::PgBufMutExt; 3 | use crate::postgres::types::Oid; 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(Oid), 12 | // None selects the unnamed portal 13 | Portal(Option), 14 | } 15 | 16 | impl Encode<'_> for Close { 17 | fn encode_with(&self, buf: &mut Vec, _: ()) { 18 | // 15 bytes for 1-digit statement/portal IDs 19 | buf.reserve(20); 20 | buf.push(b'C'); 21 | 22 | buf.put_length_prefixed(|buf| match self { 23 | Close::Statement(id) => { 24 | buf.push(CLOSE_STATEMENT); 25 | buf.put_statement_name(*id); 26 | } 27 | 28 | Close::Portal(id) => { 29 | buf.push(CLOSE_PORTAL); 30 | buf.put_portal_name(*id); 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/message/execute.rs: -------------------------------------------------------------------------------- 1 | use crate::io::Encode; 2 | use crate::postgres::io::PgBufMutExt; 3 | use crate::postgres::types::Oid; 4 | 5 | pub struct Execute { 6 | /// The id of the portal to execute (`None` selects the unnamed portal). 7 | pub portal: Option, 8 | 9 | /// Maximum number of rows to return, if portal contains a query 10 | /// that returns rows (ignored otherwise). Zero denotes “no limit”. 11 | pub limit: u32, 12 | } 13 | 14 | impl Encode<'_> for Execute { 15 | fn encode_with(&self, buf: &mut Vec, _: ()) { 16 | buf.reserve(20); 17 | buf.push(b'E'); 18 | 19 | buf.put_length_prefixed(|buf| { 20 | buf.put_portal_name(self.portal); 21 | buf.extend(&self.limit.to_be_bytes()); 22 | }); 23 | } 24 | } 25 | 26 | #[test] 27 | fn test_encode_execute() { 28 | const EXPECTED: &[u8] = b"E\0\0\0\x11sqlx_p_5\0\0\0\0\x02"; 29 | 30 | let mut buf = Vec::new(); 31 | let m = Execute { 32 | portal: Some(Oid(5)), 33 | limit: 2, 34 | }; 35 | 36 | m.encode(&mut buf); 37 | 38 | assert_eq!(buf, EXPECTED); 39 | } 40 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/message/flush.rs: -------------------------------------------------------------------------------- 1 | use crate::io::Encode; 2 | 3 | // The Flush message does not cause any specific output to be generated, 4 | // but forces the backend to deliver any data pending in its output buffers. 5 | 6 | // A Flush must be sent after any extended-query command except Sync, if the 7 | // frontend wishes to examine the results of that command before issuing more commands. 8 | 9 | #[derive(Debug)] 10 | pub struct Flush; 11 | 12 | impl Encode<'_> for Flush { 13 | fn encode_with(&self, buf: &mut Vec, _: ()) { 14 | buf.push(b'H'); 15 | buf.extend(&4_i32.to_be_bytes()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/message/notification.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | 3 | use crate::error::Error; 4 | use crate::io::{BufExt, Decode}; 5 | 6 | #[derive(Debug, Clone, PartialEq)] 7 | pub struct Notification { 8 | pub(crate) process_id: u32, 9 | pub(crate) channel: Bytes, 10 | pub(crate) payload: Bytes, 11 | } 12 | 13 | impl Decode<'_> for Notification { 14 | #[inline] 15 | fn decode_with(mut buf: Bytes, _: ()) -> Result { 16 | let process_id = buf.get_u32(); 17 | let channel = buf.get_bytes_nul()?; 18 | let payload = buf.get_bytes_nul()?; 19 | 20 | Ok(Self { 21 | process_id, 22 | channel, 23 | payload, 24 | }) 25 | } 26 | } 27 | 28 | #[test] 29 | fn test_decode_notification_response() { 30 | const NOTIFICATION_RESPONSE: &[u8] = b"\x34\x20\x10\x02TEST-CHANNEL\0THIS IS A TEST\0"; 31 | 32 | let message = Notification::decode(Bytes::from(NOTIFICATION_RESPONSE)).unwrap(); 33 | 34 | assert_eq!(message.process_id, 0x34201002); 35 | assert_eq!(&*message.channel, &b"TEST-CHANNEL"[..]); 36 | assert_eq!(&*message.payload, &b"THIS IS A TEST"[..]); 37 | } 38 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/message/parameter_description.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | use smallvec::SmallVec; 3 | 4 | use crate::error::Error; 5 | use crate::io::Decode; 6 | use crate::postgres::types::Oid; 7 | 8 | #[derive(Debug)] 9 | pub struct ParameterDescription { 10 | pub types: SmallVec<[Oid; 6]>, 11 | } 12 | 13 | impl Decode<'_> for ParameterDescription { 14 | fn decode_with(mut buf: Bytes, _: ()) -> Result { 15 | let cnt = buf.get_u16(); 16 | let mut types = SmallVec::with_capacity(cnt as usize); 17 | 18 | for _ in 0..cnt { 19 | types.push(Oid(buf.get_u32())); 20 | } 21 | 22 | Ok(Self { types }) 23 | } 24 | } 25 | 26 | #[test] 27 | fn test_decode_parameter_description() { 28 | const DATA: &[u8] = b"\x00\x02\x00\x00\x00\x00\x00\x00\x05\x00"; 29 | 30 | let m = ParameterDescription::decode(DATA.into()).unwrap(); 31 | 32 | assert_eq!(m.types.len(), 2); 33 | assert_eq!(m.types[0], Oid(0x0000_0000)); 34 | assert_eq!(m.types[1], Oid(0x0000_0500)); 35 | } 36 | 37 | #[test] 38 | fn test_decode_empty_parameter_description() { 39 | const DATA: &[u8] = b"\x00\x00"; 40 | 41 | let m = ParameterDescription::decode(DATA.into()).unwrap(); 42 | 43 | assert!(m.types.is_empty()); 44 | } 45 | 46 | #[cfg(all(test, not(debug_assertions)))] 47 | #[bench] 48 | fn bench_decode_parameter_description(b: &mut test::Bencher) { 49 | const DATA: &[u8] = b"\x00\x02\x00\x00\x00\x00\x00\x00\x05\x00"; 50 | 51 | b.iter(|| { 52 | ParameterDescription::decode(test::black_box(Bytes::from_static(DATA))).unwrap(); 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/message/parse.rs: -------------------------------------------------------------------------------- 1 | use std::i16; 2 | 3 | use crate::io::{BufMutExt, Encode}; 4 | use crate::postgres::io::PgBufMutExt; 5 | use crate::postgres::types::Oid; 6 | 7 | #[derive(Debug)] 8 | pub struct Parse<'a> { 9 | /// The ID of the destination prepared statement. 10 | pub statement: Oid, 11 | 12 | /// The query string to be parsed. 13 | pub query: &'a str, 14 | 15 | /// The parameter data types specified (could be zero). Note that this is not an 16 | /// indication of the number of parameters that might appear in the query string, 17 | /// only the number that the frontend wants to pre-specify types for. 18 | pub param_types: &'a [Oid], 19 | } 20 | 21 | impl Encode<'_> for Parse<'_> { 22 | fn encode_with(&self, buf: &mut Vec, _: ()) { 23 | buf.push(b'P'); 24 | 25 | buf.put_length_prefixed(|buf| { 26 | buf.put_statement_name(self.statement); 27 | 28 | buf.put_str_nul(self.query); 29 | 30 | // TODO: Return an error here instead 31 | let param_len = i16::try_from(self.param_types.len()).expect("too many params"); 32 | 33 | buf.extend_from_slice(¶m_len.to_be_bytes()); 34 | 35 | for &oid in self.param_types { 36 | buf.extend_from_slice(&oid.0.to_be_bytes()); 37 | } 38 | }) 39 | } 40 | } 41 | 42 | #[test] 43 | fn test_encode_parse() { 44 | const EXPECTED: &[u8] = b"P\0\0\0\x1dsqlx_s_1\0SELECT $1\0\0\x01\0\0\0\x19"; 45 | 46 | let mut buf = Vec::new(); 47 | let m = Parse { 48 | statement: Oid(1), 49 | query: "SELECT $1", 50 | param_types: &[Oid(25)], 51 | }; 52 | 53 | m.encode(&mut buf); 54 | 55 | assert_eq!(buf, EXPECTED); 56 | } 57 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/message/query.rs: -------------------------------------------------------------------------------- 1 | use crate::io::{BufMutExt, Encode}; 2 | 3 | #[derive(Debug)] 4 | pub struct Query<'a>(pub &'a str); 5 | 6 | impl Encode<'_> for Query<'_> { 7 | fn encode_with(&self, buf: &mut Vec, _: ()) { 8 | let len = 4 + self.0.len() + 1; 9 | let len_i32 = i32::try_from(len).expect("buffer too large"); 10 | buf.reserve(len + 1); 11 | buf.push(b'Q'); 12 | buf.extend_from_slice(&len_i32.to_be_bytes()); 13 | buf.put_str_nul(self.0); 14 | } 15 | } 16 | 17 | #[test] 18 | fn test_encode_query() { 19 | const EXPECTED: &[u8] = b"Q\0\0\0\rSELECT 1\0"; 20 | 21 | let mut buf = Vec::new(); 22 | let m = Query("SELECT 1"); 23 | 24 | m.encode(&mut buf); 25 | 26 | assert_eq!(buf, EXPECTED); 27 | } 28 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/message/ready_for_query.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | 3 | use crate::error::Error; 4 | use crate::io::Decode; 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 Decode<'_> for ReadyForQuery { 25 | fn decode_with(buf: Bytes, _: ()) -> Result { 26 | let status = match buf[0] { 27 | b'I' => TransactionStatus::Idle, 28 | b'T' => TransactionStatus::Transaction, 29 | b'E' => TransactionStatus::Error, 30 | 31 | status => { 32 | return Err(err_protocol!( 33 | "unknown transaction status: {:?}", 34 | status as char 35 | )); 36 | } 37 | }; 38 | 39 | Ok(Self { 40 | transaction_status: status, 41 | }) 42 | } 43 | } 44 | 45 | #[test] 46 | fn test_decode_ready_for_query() -> Result<(), Error> { 47 | const DATA: &[u8] = b"E"; 48 | 49 | let m = ReadyForQuery::decode(Bytes::from_static(DATA))?; 50 | 51 | assert!(matches!(m.transaction_status, TransactionStatus::Error)); 52 | 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/message/sasl.rs: -------------------------------------------------------------------------------- 1 | use crate::io::{BufMutExt, Encode}; 2 | use crate::postgres::io::PgBufMutExt; 3 | 4 | pub struct SaslInitialResponse<'a> { 5 | pub response: &'a str, 6 | pub plus: bool, 7 | } 8 | 9 | impl Encode<'_> for SaslInitialResponse<'_> { 10 | fn encode_with(&self, buf: &mut Vec, _: ()) { 11 | buf.push(b'p'); 12 | buf.put_length_prefixed(|buf| { 13 | // name of the SASL authentication mechanism that the client selected 14 | buf.put_str_nul(if self.plus { 15 | "SCRAM-SHA-256-PLUS" 16 | } else { 17 | "SCRAM-SHA-256" 18 | }); 19 | let bytes = self.response.as_bytes(); 20 | let len_i32 = i32::try_from(bytes.len()).expect("buffer too large"); 21 | buf.extend_from_slice(&len_i32.to_be_bytes()); 22 | buf.extend_from_slice(bytes); 23 | }); 24 | } 25 | } 26 | 27 | pub struct SaslResponse<'a>(pub &'a str); 28 | 29 | impl Encode<'_> for SaslResponse<'_> { 30 | fn encode_with(&self, buf: &mut Vec, _: ()) { 31 | buf.push(b'p'); 32 | buf.put_length_prefixed(|buf| { 33 | buf.extend(self.0.as_bytes()); 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/message/ssl_request.rs: -------------------------------------------------------------------------------- 1 | use crate::io::Encode; 2 | 3 | pub struct SslRequest; 4 | 5 | impl Encode<'_> for SslRequest { 6 | #[inline] 7 | fn encode_with(&self, buf: &mut Vec, _: ()) { 8 | buf.extend(&8_u32.to_be_bytes()); 9 | buf.extend(&(((1234 << 16) | 5679) as u32).to_be_bytes()); 10 | } 11 | } 12 | 13 | #[test] 14 | fn test_encode_ssl_request() { 15 | const EXPECTED: &[u8] = b"\x00\x00\x00\x08\x04\xd2\x16/"; 16 | 17 | let mut buf = Vec::new(); 18 | SslRequest.encode(&mut buf); 19 | 20 | assert_eq!(buf, EXPECTED); 21 | } 22 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/message/sync.rs: -------------------------------------------------------------------------------- 1 | use crate::io::Encode; 2 | 3 | #[derive(Debug)] 4 | pub struct Sync; 5 | 6 | impl Encode<'_> for Sync { 7 | fn encode_with(&self, buf: &mut Vec, _: ()) { 8 | buf.push(b'S'); 9 | buf.extend(&4_i32.to_be_bytes()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/message/terminate.rs: -------------------------------------------------------------------------------- 1 | use crate::io::Encode; 2 | 3 | pub struct Terminate; 4 | 5 | impl Encode<'_> for Terminate { 6 | fn encode_with(&self, buf: &mut Vec, _: ()) { 7 | buf.push(b'X'); 8 | buf.extend(&4_u32.to_be_bytes()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/options/connect.rs: -------------------------------------------------------------------------------- 1 | use crate::connection::ConnectOptions; 2 | use crate::error::Error; 3 | use crate::postgres::{PgConnectOptions, PgConnection}; 4 | use futures_core::future::BoxFuture; 5 | use log::LevelFilter; 6 | use std::time::Duration; 7 | 8 | impl ConnectOptions for PgConnectOptions { 9 | type Connection = PgConnection; 10 | 11 | fn connect(&self) -> BoxFuture<'_, Result> 12 | where 13 | Self::Connection: Sized, 14 | { 15 | Box::pin(PgConnection::establish(self)) 16 | } 17 | 18 | fn log_statements(&mut self, level: LevelFilter) -> &mut Self { 19 | self.log_settings.log_statements(level); 20 | self 21 | } 22 | 23 | fn log_slow_statements(&mut self, level: LevelFilter, duration: Duration) -> &mut Self { 24 | self.log_settings.log_slow_statements(level, duration); 25 | self 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/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 crate::any::AnyQueryResult { 24 | fn from(done: PgQueryResult) -> Self { 25 | crate::any::AnyQueryResult { 26 | rows_affected: done.rows_affected, 27 | last_insert_id: None, 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/types/bool.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::Decode; 2 | use crate::encode::{Encode, IsNull}; 3 | use crate::error::BoxDynError; 4 | use crate::postgres::{ 5 | PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, 6 | }; 7 | use crate::types::Type; 8 | 9 | impl Type for bool { 10 | fn type_info() -> PgTypeInfo { 11 | PgTypeInfo::BOOL 12 | } 13 | } 14 | 15 | impl PgHasArrayType for bool { 16 | fn array_type_info() -> PgTypeInfo { 17 | PgTypeInfo::BOOL_ARRAY 18 | } 19 | } 20 | 21 | impl Encode<'_, Postgres> for bool { 22 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { 23 | buf.push(*self as u8); 24 | 25 | IsNull::No 26 | } 27 | } 28 | 29 | impl Decode<'_, Postgres> for bool { 30 | fn decode(value: PgValueRef<'_>) -> Result { 31 | Ok(match value.format() { 32 | PgValueFormat::Binary => value.as_bytes()?[0] != 0, 33 | 34 | PgValueFormat::Text => match value.as_str()? { 35 | "t" => true, 36 | "f" => false, 37 | 38 | s => { 39 | return Err(format!("unexpected value {:?} for boolean", s).into()); 40 | } 41 | }, 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/types/chrono/date.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::Decode; 2 | use crate::encode::{Encode, IsNull}; 3 | use crate::error::BoxDynError; 4 | use crate::postgres::{ 5 | PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, 6 | }; 7 | use crate::types::Type; 8 | use chrono::{Duration, NaiveDate}; 9 | use std::mem; 10 | 11 | impl Type for NaiveDate { 12 | fn type_info() -> PgTypeInfo { 13 | PgTypeInfo::DATE 14 | } 15 | } 16 | 17 | impl PgHasArrayType for NaiveDate { 18 | fn array_type_info() -> PgTypeInfo { 19 | PgTypeInfo::DATE_ARRAY 20 | } 21 | } 22 | 23 | impl Encode<'_, Postgres> for NaiveDate { 24 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { 25 | // DATE is encoded as the days since epoch 26 | let days = i32::try_from((*self - NaiveDate::from_ymd_opt(2000, 1, 1).unwrap()).num_days()) 27 | .unwrap_or(i32::MAX); 28 | Encode::::encode(days, buf) 29 | } 30 | 31 | fn size_hint(&self) -> usize { 32 | mem::size_of::() 33 | } 34 | } 35 | 36 | impl<'r> Decode<'r, Postgres> for NaiveDate { 37 | fn decode(value: PgValueRef<'r>) -> Result { 38 | Ok(match value.format() { 39 | PgValueFormat::Binary => { 40 | // DATE is encoded as the days since epoch 41 | let days: i32 = Decode::::decode(value)?; 42 | NaiveDate::from_ymd_opt(2000, 1, 1).unwrap() + Duration::days(days.into()) 43 | } 44 | 45 | PgValueFormat::Text => NaiveDate::parse_from_str(value.as_str()?, "%Y-%m-%d")?, 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/types/chrono/mod.rs: -------------------------------------------------------------------------------- 1 | mod date; 2 | mod datetime; 3 | mod time; 4 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/types/chrono/time.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::Decode; 2 | use crate::encode::{Encode, IsNull}; 3 | use crate::error::BoxDynError; 4 | use crate::postgres::{ 5 | PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, 6 | }; 7 | use crate::types::Type; 8 | use chrono::{Duration, NaiveTime}; 9 | use std::mem; 10 | 11 | impl Type for NaiveTime { 12 | fn type_info() -> PgTypeInfo { 13 | PgTypeInfo::TIME 14 | } 15 | } 16 | 17 | impl PgHasArrayType for NaiveTime { 18 | fn array_type_info() -> PgTypeInfo { 19 | PgTypeInfo::TIME_ARRAY 20 | } 21 | } 22 | 23 | impl Encode<'_, Postgres> for NaiveTime { 24 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { 25 | // TIME is encoded as the microseconds since midnight 26 | // NOTE: panic! is on overflow and 1 day does not have enough micros to overflow 27 | let us = (*self - NaiveTime::default()).num_microseconds().unwrap(); 28 | 29 | Encode::::encode(us, buf) 30 | } 31 | 32 | fn size_hint(&self) -> usize { 33 | mem::size_of::() 34 | } 35 | } 36 | 37 | impl<'r> Decode<'r, Postgres> for NaiveTime { 38 | fn decode(value: PgValueRef<'r>) -> Result { 39 | Ok(match value.format() { 40 | PgValueFormat::Binary => { 41 | // TIME is encoded as the microseconds since midnight 42 | let us: i64 = Decode::::decode(value)?; 43 | NaiveTime::default() + Duration::microseconds(us) 44 | } 45 | 46 | PgValueFormat::Text => NaiveTime::parse_from_str(value.as_str()?, "%H:%M:%S%.f")?, 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/types/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::postgres::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef, Postgres}; 9 | use crate::types::Type; 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) -> IsNull { 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-core/src/postgres/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::postgres::{ 7 | PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, 8 | }; 9 | use crate::types::Type; 10 | 11 | impl Type for MacAddress { 12 | fn type_info() -> PgTypeInfo { 13 | PgTypeInfo::MACADDR 14 | } 15 | 16 | fn compatible(ty: &PgTypeInfo) -> bool { 17 | *ty == PgTypeInfo::MACADDR 18 | } 19 | } 20 | 21 | impl PgHasArrayType for MacAddress { 22 | fn array_type_info() -> PgTypeInfo { 23 | PgTypeInfo::MACADDR_ARRAY 24 | } 25 | } 26 | 27 | impl Encode<'_, Postgres> for MacAddress { 28 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { 29 | buf.extend_from_slice(&self.bytes()); // write just the address 30 | IsNull::No 31 | } 32 | 33 | fn size_hint(&self) -> usize { 34 | 6 35 | } 36 | } 37 | 38 | impl Decode<'_, Postgres> for MacAddress { 39 | fn decode(value: PgValueRef<'_>) -> Result { 40 | let bytes = match value.format() { 41 | PgValueFormat::Binary => value.as_bytes()?, 42 | PgValueFormat::Text => { 43 | return Ok(value.as_str()?.parse()?); 44 | } 45 | }; 46 | 47 | if bytes.len() == 6 { 48 | return Ok(MacAddress::new(bytes.try_into().unwrap())); 49 | } 50 | 51 | Err("invalid data received when expecting an MACADDR".into()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/types/time/date.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::Decode; 2 | use crate::encode::{Encode, IsNull}; 3 | use crate::error::BoxDynError; 4 | use crate::postgres::types::time::PG_EPOCH; 5 | use crate::postgres::{ 6 | PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, 7 | }; 8 | use crate::types::Type; 9 | use std::mem; 10 | use time::macros::format_description; 11 | use time::{Date, Duration}; 12 | 13 | impl Type for Date { 14 | fn type_info() -> PgTypeInfo { 15 | PgTypeInfo::DATE 16 | } 17 | } 18 | 19 | impl PgHasArrayType for Date { 20 | fn array_type_info() -> PgTypeInfo { 21 | PgTypeInfo::DATE_ARRAY 22 | } 23 | } 24 | 25 | impl Encode<'_, Postgres> for Date { 26 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { 27 | // DATE is encoded as the days since epoch 28 | let days = i32::try_from((*self - PG_EPOCH).whole_days()).unwrap_or(i32::MAX); 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-core/src/postgres/types/time/mod.rs: -------------------------------------------------------------------------------- 1 | mod date; 2 | mod datetime; 3 | mod time; 4 | 5 | #[rustfmt::skip] 6 | const PG_EPOCH: ::time::Date = ::time::macros::date!(2000-1-1); 7 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/types/time/time.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::Decode; 2 | use crate::encode::{Encode, IsNull}; 3 | use crate::error::BoxDynError; 4 | use crate::postgres::{ 5 | PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, 6 | }; 7 | use crate::types::Type; 8 | use std::mem; 9 | use time::macros::format_description; 10 | use time::{Duration, Time}; 11 | 12 | impl Type for Time { 13 | fn type_info() -> PgTypeInfo { 14 | PgTypeInfo::TIME 15 | } 16 | } 17 | 18 | impl PgHasArrayType for Time { 19 | fn array_type_info() -> PgTypeInfo { 20 | PgTypeInfo::TIME_ARRAY 21 | } 22 | } 23 | 24 | impl Encode<'_, Postgres> for Time { 25 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { 26 | // TIME is encoded as the microseconds since midnight 27 | let us = i64::try_from((*self - Time::MIDNIGHT).whole_microseconds()) 28 | .expect("number of microseconds since midnight should fit in an i64"); 29 | Encode::::encode(us, buf) 30 | } 31 | 32 | fn size_hint(&self) -> usize { 33 | mem::size_of::() 34 | } 35 | } 36 | 37 | impl<'r> Decode<'r, Postgres> for Time { 38 | fn decode(value: PgValueRef<'r>) -> Result { 39 | Ok(match value.format() { 40 | PgValueFormat::Binary => { 41 | // TIME is encoded as the microseconds since midnight 42 | let us = Decode::::decode(value)?; 43 | Time::MIDNIGHT + Duration::microseconds(us) 44 | } 45 | 46 | PgValueFormat::Text => Time::parse( 47 | value.as_str()?, 48 | &format_description!("[hour]:[minute]:[second].[subsecond]"), 49 | )?, 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/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::postgres::{ 7 | PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, 8 | }; 9 | use crate::types::Type; 10 | 11 | impl Type for Uuid { 12 | fn type_info() -> PgTypeInfo { 13 | PgTypeInfo::UUID 14 | } 15 | } 16 | 17 | impl PgHasArrayType for Uuid { 18 | fn array_type_info() -> PgTypeInfo { 19 | PgTypeInfo::UUID_ARRAY 20 | } 21 | } 22 | 23 | impl Encode<'_, Postgres> for Uuid { 24 | fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull { 25 | buf.extend_from_slice(self.as_bytes()); 26 | 27 | IsNull::No 28 | } 29 | } 30 | 31 | impl Decode<'_, Postgres> for Uuid { 32 | fn decode(value: PgValueRef<'_>) -> Result { 33 | match value.format() { 34 | PgValueFormat::Binary => Uuid::from_slice(value.as_bytes()?), 35 | PgValueFormat::Text => value.as_str()?.parse(), 36 | } 37 | .map_err(Into::into) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sqlx-core/src/postgres/types/void.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::Decode; 2 | use crate::error::BoxDynError; 3 | use crate::postgres::{PgTypeInfo, PgValueRef, Postgres}; 4 | use crate::types::Type; 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-core/src/sqlite/column.rs: -------------------------------------------------------------------------------- 1 | use crate::column::Column; 2 | use crate::ext::ustr::UStr; 3 | use crate::sqlite::{Sqlite, SqliteTypeInfo}; 4 | 5 | #[derive(Debug, Clone)] 6 | #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] 7 | pub struct SqliteColumn { 8 | pub(crate) name: UStr, 9 | pub(crate) ordinal: usize, 10 | pub(crate) type_info: SqliteTypeInfo, 11 | } 12 | 13 | impl crate::column::private_column::Sealed for SqliteColumn {} 14 | 15 | impl Column for SqliteColumn { 16 | type Database = Sqlite; 17 | 18 | fn ordinal(&self) -> usize { 19 | self.ordinal 20 | } 21 | 22 | fn name(&self) -> &str { 23 | &*self.name 24 | } 25 | 26 | fn type_info(&self) -> &SqliteTypeInfo { 27 | &self.type_info 28 | } 29 | } 30 | 31 | #[cfg(feature = "any")] 32 | impl From for crate::any::AnyColumn { 33 | #[inline] 34 | fn from(column: SqliteColumn) -> Self { 35 | crate::any::AnyColumn { 36 | type_info: column.type_info.clone().into(), 37 | kind: crate::any::column::AnyColumnKind::Sqlite(column), 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /sqlx-core/src/sqlite/database.rs: -------------------------------------------------------------------------------- 1 | use crate::database::{Database, HasArguments, HasStatement, HasStatementCache, HasValueRef}; 2 | use crate::sqlite::{ 3 | SqliteArgumentValue, SqliteArguments, SqliteColumn, SqliteConnection, SqliteQueryResult, 4 | SqliteRow, SqliteStatement, SqliteTransactionManager, SqliteTypeInfo, SqliteValue, 5 | SqliteValueRef, 6 | }; 7 | 8 | /// Sqlite database driver. 9 | #[derive(Debug)] 10 | pub struct Sqlite; 11 | 12 | impl Database for Sqlite { 13 | type Connection = SqliteConnection; 14 | 15 | type TransactionManager = SqliteTransactionManager; 16 | 17 | type Row = SqliteRow; 18 | 19 | type QueryResult = SqliteQueryResult; 20 | 21 | type Column = SqliteColumn; 22 | 23 | type TypeInfo = SqliteTypeInfo; 24 | 25 | type Value = SqliteValue; 26 | } 27 | 28 | impl<'r> HasValueRef<'r> for Sqlite { 29 | type Database = Sqlite; 30 | 31 | type ValueRef = SqliteValueRef<'r>; 32 | } 33 | 34 | impl<'q> HasArguments<'q> for Sqlite { 35 | type Database = Sqlite; 36 | 37 | type Arguments = SqliteArguments<'q>; 38 | 39 | type ArgumentBuffer = Vec>; 40 | } 41 | 42 | impl<'q> HasStatement<'q> for Sqlite { 43 | type Database = Sqlite; 44 | 45 | type Statement = SqliteStatement<'q>; 46 | } 47 | 48 | impl HasStatementCache for Sqlite {} 49 | -------------------------------------------------------------------------------- /sqlx-core/src/sqlite/options/auto_vacuum.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Error; 2 | use std::str::FromStr; 3 | 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 5 | pub enum SqliteAutoVacuum { 6 | None, 7 | Full, 8 | Incremental, 9 | } 10 | 11 | impl SqliteAutoVacuum { 12 | pub(crate) fn as_str(&self) -> &'static str { 13 | match self { 14 | SqliteAutoVacuum::None => "NONE", 15 | SqliteAutoVacuum::Full => "FULL", 16 | SqliteAutoVacuum::Incremental => "INCREMENTAL", 17 | } 18 | } 19 | } 20 | 21 | impl Default for SqliteAutoVacuum { 22 | fn default() -> Self { 23 | SqliteAutoVacuum::None 24 | } 25 | } 26 | 27 | impl FromStr for SqliteAutoVacuum { 28 | type Err = Error; 29 | 30 | fn from_str(s: &str) -> Result { 31 | Ok(match &*s.to_ascii_lowercase() { 32 | "none" => SqliteAutoVacuum::None, 33 | "full" => SqliteAutoVacuum::Full, 34 | "incremental" => SqliteAutoVacuum::Incremental, 35 | 36 | _ => { 37 | return Err(Error::Configuration( 38 | format!("unknown value {:?} for `auto_vacuum`", s).into(), 39 | )); 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sqlx-core/src/sqlite/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)] 8 | pub enum SqliteLockingMode { 9 | Normal, 10 | Exclusive, 11 | } 12 | 13 | impl SqliteLockingMode { 14 | pub(crate) fn as_str(&self) -> &'static str { 15 | match self { 16 | SqliteLockingMode::Normal => "NORMAL", 17 | SqliteLockingMode::Exclusive => "EXCLUSIVE", 18 | } 19 | } 20 | } 21 | 22 | impl Default for SqliteLockingMode { 23 | fn default() -> Self { 24 | SqliteLockingMode::Normal 25 | } 26 | } 27 | 28 | impl FromStr for SqliteLockingMode { 29 | type Err = Error; 30 | 31 | fn from_str(s: &str) -> Result { 32 | Ok(match &*s.to_ascii_lowercase() { 33 | "normal" => SqliteLockingMode::Normal, 34 | "exclusive" => SqliteLockingMode::Exclusive, 35 | 36 | _ => { 37 | return Err(Error::Configuration( 38 | format!("unknown value {:?} for `locking_mode`", s).into(), 39 | )); 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sqlx-core/src/sqlite/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)] 8 | pub enum SqliteSynchronous { 9 | Off, 10 | Normal, 11 | Full, 12 | Extra, 13 | } 14 | 15 | impl SqliteSynchronous { 16 | pub(crate) fn as_str(&self) -> &'static str { 17 | match self { 18 | SqliteSynchronous::Off => "OFF", 19 | SqliteSynchronous::Normal => "NORMAL", 20 | SqliteSynchronous::Full => "FULL", 21 | SqliteSynchronous::Extra => "EXTRA", 22 | } 23 | } 24 | } 25 | 26 | impl Default for SqliteSynchronous { 27 | fn default() -> Self { 28 | SqliteSynchronous::Full 29 | } 30 | } 31 | 32 | impl FromStr for SqliteSynchronous { 33 | type Err = Error; 34 | 35 | fn from_str(s: &str) -> Result { 36 | Ok(match &*s.to_ascii_lowercase() { 37 | "off" => SqliteSynchronous::Off, 38 | "normal" => SqliteSynchronous::Normal, 39 | "full" => SqliteSynchronous::Full, 40 | "extra" => SqliteSynchronous::Extra, 41 | 42 | _ => { 43 | return Err(Error::Configuration( 44 | format!("unknown value {:?} for `synchronous`", s).into(), 45 | )); 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /sqlx-core/src/sqlite/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 crate::any::AnyQueryResult { 30 | fn from(done: SqliteQueryResult) -> Self { 31 | crate::any::AnyQueryResult { 32 | rows_affected: done.changes, 33 | last_insert_id: Some(done.last_insert_rowid), 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sqlx-core/src/sqlite/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::sqlite::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 | let slice = slice::from_raw_parts(ptr, len as usize); 31 | 32 | for notify in slice { 33 | notify.fire(); 34 | } 35 | } 36 | 37 | struct Notify { 38 | mutex: Mutex, 39 | condvar: Condvar, 40 | } 41 | 42 | impl Notify { 43 | fn new() -> Self { 44 | Self { 45 | mutex: Mutex::new(false), 46 | condvar: Condvar::new(), 47 | } 48 | } 49 | 50 | fn wait(&self) { 51 | let lock = self.mutex.lock().unwrap(); 52 | let _unused = self.condvar.wait_while(lock, |fired| !*fired).unwrap(); 53 | } 54 | 55 | fn fire(&self) { 56 | *self.mutex.lock().unwrap() = true; 57 | self.condvar.notify_one(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /sqlx-core/src/sqlite/transaction.rs: -------------------------------------------------------------------------------- 1 | use futures_core::future::BoxFuture; 2 | 3 | use crate::error::Error; 4 | use crate::sqlite::{Sqlite, SqliteConnection}; 5 | use crate::transaction::TransactionManager; 6 | 7 | /// Implementation of [`TransactionManager`] for SQLite. 8 | pub struct SqliteTransactionManager; 9 | 10 | impl TransactionManager for SqliteTransactionManager { 11 | type Database = Sqlite; 12 | 13 | fn begin(conn: &mut SqliteConnection) -> BoxFuture<'_, Result<(), Error>> { 14 | Box::pin(conn.worker.begin()) 15 | } 16 | 17 | fn commit(conn: &mut SqliteConnection) -> BoxFuture<'_, Result<(), Error>> { 18 | Box::pin(conn.worker.commit()) 19 | } 20 | 21 | fn rollback(conn: &mut SqliteConnection) -> BoxFuture<'_, Result<(), Error>> { 22 | Box::pin(conn.worker.rollback()) 23 | } 24 | 25 | fn start_rollback(conn: &mut SqliteConnection) { 26 | conn.worker.start_rollback().ok(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sqlx-core/src/sqlite/types/bigdecimal.rs: -------------------------------------------------------------------------------- 1 | use crate::bigdecimal::FromPrimitive; 2 | use crate::decode::Decode; 3 | use crate::encode::{Encode, IsNull}; 4 | use crate::error::BoxDynError; 5 | use crate::sqlite::{ 6 | type_info::DataType, Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef, 7 | }; 8 | use crate::types::Type; 9 | use crate::value::ValueRef; 10 | use bigdecimal::BigDecimal; 11 | use std::str::FromStr; 12 | 13 | impl Type for BigDecimal { 14 | fn type_info() -> SqliteTypeInfo { 15 | SqliteTypeInfo(DataType::Text) 16 | } 17 | 18 | fn compatible(ty: &SqliteTypeInfo) -> bool { 19 | <&str as Type>::compatible(ty) 20 | || >::compatible(ty) 21 | || >::compatible(ty) 22 | } 23 | } 24 | 25 | impl Encode<'_, Sqlite> for BigDecimal { 26 | fn encode_by_ref(&self, buf: &mut Vec>) -> IsNull { 27 | let string_value = self.to_string(); 28 | Encode::::encode(string_value, buf) 29 | } 30 | } 31 | 32 | impl Decode<'_, Sqlite> for BigDecimal { 33 | fn decode(value: SqliteValueRef<'_>) -> Result { 34 | let ty = &value.type_info(); 35 | if >::compatible(ty) { 36 | Ok(BigDecimal::from(value.int64())) 37 | } else if >::compatible(ty) { 38 | Ok(BigDecimal::from_f64(value.double()).ok_or("bad float")?) 39 | } else if <&str as Type>::compatible(ty) { 40 | Ok(BigDecimal::from_str(value.text()?)?) 41 | } else { 42 | Err("bad type".into()) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sqlx-core/src/sqlite/types/bool.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::Decode; 2 | use crate::encode::{Encode, IsNull}; 3 | use crate::error::BoxDynError; 4 | use crate::sqlite::type_info::DataType; 5 | use crate::sqlite::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; 6 | use crate::types::Type; 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::Int | DataType::Int64) 15 | } 16 | } 17 | 18 | impl<'q> Encode<'q, Sqlite> for bool { 19 | fn encode_by_ref(&self, args: &mut Vec>) -> IsNull { 20 | args.push(SqliteArgumentValue::Int((*self).into())); 21 | 22 | IsNull::No 23 | } 24 | } 25 | 26 | impl<'r> Decode<'r, Sqlite> for bool { 27 | fn decode(value: SqliteValueRef<'r>) -> Result { 28 | Ok(value.int() != 0) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sqlx-core/src/sqlite/types/decimal.rs: -------------------------------------------------------------------------------- 1 | use crate::value::ValueRef; 2 | use bigdecimal_::FromPrimitive; 3 | use rust_decimal::Decimal; 4 | 5 | use crate::decode::Decode; 6 | use crate::encode::{Encode, IsNull}; 7 | use crate::error::BoxDynError; 8 | use crate::sqlite::{ 9 | type_info::DataType, Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef, 10 | }; 11 | use crate::types::Type; 12 | 13 | impl Type for Decimal { 14 | fn type_info() -> SqliteTypeInfo { 15 | SqliteTypeInfo(DataType::Text) 16 | } 17 | 18 | fn compatible(ty: &SqliteTypeInfo) -> bool { 19 | <&str as Type>::compatible(ty) 20 | || >::compatible(ty) 21 | || >::compatible(ty) 22 | } 23 | } 24 | 25 | impl Encode<'_, Sqlite> for Decimal { 26 | fn encode_by_ref(&self, buf: &mut Vec>) -> IsNull { 27 | let string_value = self.to_string(); 28 | Encode::::encode(string_value, buf) 29 | } 30 | } 31 | 32 | impl Decode<'_, Sqlite> for Decimal { 33 | fn decode(value: SqliteValueRef<'_>) -> Result { 34 | match value.type_info().0 { 35 | DataType::Float => Ok(Decimal::from_f64(value.double()).ok_or("bad float")?), 36 | DataType::Int | DataType::Int64 => Ok(Decimal::from(value.int64())), 37 | _ => { 38 | let string_value = <&str as Decode>::decode(value)?; 39 | string_value.parse().map_err(Into::into) 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sqlx-core/src/sqlite/types/float.rs: -------------------------------------------------------------------------------- 1 | use crate::decode::Decode; 2 | use crate::encode::{Encode, IsNull}; 3 | use crate::error::BoxDynError; 4 | use crate::sqlite::type_info::DataType; 5 | use crate::sqlite::{Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef}; 6 | use crate::types::Type; 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(&self, args: &mut Vec>) -> IsNull { 16 | args.push(SqliteArgumentValue::Double((*self).into())); 17 | 18 | IsNull::No 19 | } 20 | } 21 | 22 | impl<'r> Decode<'r, Sqlite> for f32 { 23 | fn decode(value: SqliteValueRef<'r>) -> Result { 24 | Ok(value.double() as f32) 25 | } 26 | } 27 | 28 | impl Type for f64 { 29 | fn type_info() -> SqliteTypeInfo { 30 | SqliteTypeInfo(DataType::Float) 31 | } 32 | } 33 | 34 | impl<'q> Encode<'q, Sqlite> for f64 { 35 | fn encode_by_ref(&self, args: &mut Vec>) -> IsNull { 36 | args.push(SqliteArgumentValue::Double(*self)); 37 | 38 | IsNull::No 39 | } 40 | } 41 | 42 | impl<'r> Decode<'r, Sqlite> for f64 { 43 | fn decode(value: SqliteValueRef<'r>) -> Result { 44 | Ok(value.double()) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /sqlx-core/src/sqlite/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::sqlite::{ 7 | type_info::DataType, Sqlite, SqliteArgumentValue, SqliteTypeInfo, SqliteValueRef, 8 | }; 9 | use crate::types::{Json, Type}; 10 | 11 | impl Type for Json { 12 | fn type_info() -> SqliteTypeInfo { 13 | SqliteTypeInfo(DataType::Text) 14 | } 15 | 16 | fn compatible(ty: &SqliteTypeInfo) -> bool { 17 | <&str as Type>::compatible(ty) 18 | } 19 | } 20 | 21 | impl Encode<'_, Sqlite> for Json 22 | where 23 | T: Serialize, 24 | { 25 | fn encode_by_ref(&self, buf: &mut Vec>) -> IsNull { 26 | let json_string_value = 27 | serde_json::to_string(&self.0).expect("serde_json failed to convert to string"); 28 | 29 | Encode::::encode(json_string_value, buf) 30 | } 31 | } 32 | 33 | impl<'r, T> Decode<'r, Sqlite> for Json 34 | where 35 | T: 'r + Deserialize<'r>, 36 | { 37 | fn decode(value: SqliteValueRef<'r>) -> Result { 38 | let string_value = <&str as Decode>::decode(value)?; 39 | 40 | serde_json::from_str(string_value) 41 | .map(Json) 42 | .map_err(Into::into) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /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 | #[doc(hidden)] 13 | fn is_void(&self) -> bool { 14 | false 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sqlx-core/src/types/bstr.rs: -------------------------------------------------------------------------------- 1 | /// Conversions between `bstr` types and SQL types. 2 | use crate::database::{Database, HasArguments, HasValueRef}; 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) -> 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(&self, buf: &mut >::ArgumentBuffer) -> IsNull { 41 | <&[u8] as Encode>::encode(self.as_bytes(), buf) 42 | } 43 | } 44 | 45 | impl<'q, DB: Database> Encode<'q, DB> for BString 46 | where 47 | DB: Database, 48 | Vec: Encode<'q, DB>, 49 | { 50 | fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { 51 | as Encode>::encode(self.as_bytes().to_vec(), buf) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /sqlx-core/src/types/git2.rs: -------------------------------------------------------------------------------- 1 | /// Conversions between `git2::Oid` and SQL types. 2 | use crate::database::{Database, HasArguments, HasValueRef}; 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 git2::Oid; 10 | 11 | impl Type for Oid 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 Oid 26 | where 27 | DB: Database, 28 | &'r [u8]: Decode<'r, DB>, 29 | { 30 | fn decode(value: >::ValueRef) -> Result { 31 | <&[u8] as Decode>::decode(value).and_then(|bytes| Ok(Oid::from_bytes(bytes)?)) 32 | } 33 | } 34 | 35 | impl<'q, DB: Database> Encode<'q, DB> for Oid 36 | where 37 | DB: Database, 38 | Vec: Encode<'q, DB>, 39 | { 40 | fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { 41 | as Encode>::encode(self.as_bytes().to_vec(), buf) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sqlx-macros/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() && path.parent().is_none_or(|p| p.as_os_str().is_empty()) { 18 | return Err(syn::Error::new( 19 | err_span, 20 | "paths relative to the current file's directory are not currently supported", 21 | )); 22 | } 23 | 24 | let base_dir = env::var("CARGO_MANIFEST_DIR").map_err(|_| { 25 | syn::Error::new( 26 | err_span, 27 | "CARGO_MANIFEST_DIR is not set; please use Cargo to build", 28 | ) 29 | })?; 30 | let base_dir_path = Path::new(&base_dir); 31 | 32 | Ok(base_dir_path.join(path)) 33 | } 34 | -------------------------------------------------------------------------------- /sqlx-macros/src/database/mssql.rs: -------------------------------------------------------------------------------- 1 | use sqlx_core as sqlx; 2 | 3 | impl_database_ext! { 4 | sqlx::mssql::Mssql { 5 | bool, 6 | i8, 7 | i16, 8 | i32, 9 | i64, 10 | f32, 11 | f64, 12 | String, 13 | 14 | #[cfg(feature = "chrono")] 15 | sqlx::types::chrono::NaiveTime, 16 | 17 | #[cfg(feature = "chrono")] 18 | sqlx::types::chrono::NaiveDate, 19 | 20 | #[cfg(feature = "chrono")] 21 | sqlx::types::chrono::NaiveDateTime, 22 | 23 | #[cfg(feature = "chrono")] 24 | sqlx::types::chrono::DateTime, 25 | 26 | #[cfg(feature = "chrono")] 27 | sqlx::types::chrono::DateTime, 28 | 29 | #[cfg(feature = "bigdecimal")] 30 | sqlx::types::BigDecimal, 31 | 32 | #[cfg(feature = "decimal")] 33 | sqlx::types::Decimal, 34 | 35 | #[cfg(feature = "json")] 36 | sqlx::types::JsonValue, 37 | 38 | }, 39 | ParamChecking::Weak, 40 | feature-types: _info => None, 41 | row = sqlx::mssql::MssqlRow, 42 | name = "MSSQL" 43 | } 44 | -------------------------------------------------------------------------------- /sqlx-macros/src/database/mysql.rs: -------------------------------------------------------------------------------- 1 | use sqlx_core as sqlx; 2 | 3 | impl_database_ext! { 4 | sqlx::mysql::MySql { 5 | u8, 6 | u16, 7 | u32, 8 | u64, 9 | i8, 10 | i16, 11 | i32, 12 | i64, 13 | f32, 14 | f64, 15 | 16 | // ordering is important here as otherwise we might infer strings to be binary 17 | // CHAR, VAR_CHAR, TEXT 18 | String, 19 | 20 | // BINARY, VAR_BINARY, BLOB 21 | Vec, 22 | 23 | #[cfg(all(feature = "chrono", not(feature = "time")))] 24 | sqlx::types::chrono::NaiveTime, 25 | 26 | #[cfg(all(feature = "chrono", not(feature = "time")))] 27 | sqlx::types::chrono::NaiveDate, 28 | 29 | #[cfg(all(feature = "chrono", not(feature = "time")))] 30 | sqlx::types::chrono::NaiveDateTime, 31 | 32 | #[cfg(all(feature = "chrono", not(feature = "time")))] 33 | sqlx::types::chrono::DateTime, 34 | 35 | #[cfg(feature = "time")] 36 | sqlx::types::time::Time, 37 | 38 | #[cfg(feature = "time")] 39 | sqlx::types::time::Date, 40 | 41 | #[cfg(feature = "time")] 42 | sqlx::types::time::PrimitiveDateTime, 43 | 44 | #[cfg(feature = "time")] 45 | sqlx::types::time::OffsetDateTime, 46 | 47 | #[cfg(feature = "bigdecimal")] 48 | sqlx::types::BigDecimal, 49 | 50 | #[cfg(feature = "decimal")] 51 | sqlx::types::Decimal, 52 | 53 | #[cfg(feature = "json")] 54 | sqlx::types::JsonValue, 55 | }, 56 | ParamChecking::Weak, 57 | feature-types: info => info.__type_feature_gate(), 58 | row = sqlx::mysql::MySqlRow, 59 | name = "MySQL" 60 | } 61 | -------------------------------------------------------------------------------- /sqlx-macros/src/database/sqlite.rs: -------------------------------------------------------------------------------- 1 | use sqlx_core as sqlx; 2 | 3 | // f32 is not included below as REAL represents a floating point value 4 | // stored as an 8-byte IEEE floating point number 5 | // For more info see: https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes 6 | impl_database_ext! { 7 | sqlx::sqlite::Sqlite { 8 | bool, 9 | i32, 10 | i64, 11 | f64, 12 | String, 13 | Vec, 14 | 15 | #[cfg(feature = "chrono")] 16 | sqlx::types::chrono::NaiveDateTime, 17 | 18 | #[cfg(feature = "chrono")] 19 | sqlx::types::chrono::DateTime | sqlx::types::chrono::DateTime<_>, 20 | }, 21 | ParamChecking::Weak, 22 | feature-types: _info => None, 23 | row = sqlx::sqlite::SqliteRow, 24 | name = "SQLite" 25 | } 26 | -------------------------------------------------------------------------------- /sqlx-macros/src/derives/mod.rs: -------------------------------------------------------------------------------- 1 | mod attributes; 2 | mod decode; 3 | mod encode; 4 | mod row; 5 | mod r#type; 6 | 7 | pub(crate) use decode::expand_derive_decode; 8 | pub(crate) use encode::expand_derive_encode; 9 | pub(crate) use r#type::expand_derive_type; 10 | pub(crate) 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 std::iter::FromIterator; 16 | use syn::DeriveInput; 17 | 18 | pub(crate) fn expand_derive_type_encode_decode(input: &DeriveInput) -> syn::Result { 19 | let encode_tts = expand_derive_encode(input)?; 20 | let decode_tts = expand_derive_decode(input)?; 21 | let type_tts = expand_derive_type(input)?; 22 | 23 | let combined = TokenStream::from_iter(encode_tts.into_iter().chain(decode_tts).chain(type_tts)); 24 | 25 | Ok(combined) 26 | } 27 | 28 | pub(crate) fn rename_all(s: &str, pattern: RenameAll) -> String { 29 | match pattern { 30 | RenameAll::LowerCase => s.to_lowercase(), 31 | RenameAll::SnakeCase => s.to_snake_case(), 32 | RenameAll::UpperCase => s.to_uppercase(), 33 | RenameAll::ScreamingSnakeCase => s.to_shouty_snake_case(), 34 | RenameAll::KebabCase => s.to_kebab_case(), 35 | RenameAll::CamelCase => s.to_lower_camel_case(), 36 | RenameAll::PascalCase => s.to_upper_camel_case(), 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /sqlx-rt/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Core runtime support for SQLx. **Semver-exempt**, not for general use. 2 | 3 | #[cfg(not(any( 4 | feature = "runtime-actix-native-tls", 5 | feature = "runtime-async-std-native-tls", 6 | feature = "runtime-tokio-native-tls", 7 | feature = "runtime-actix-rustls", 8 | feature = "runtime-async-std-rustls", 9 | feature = "runtime-tokio-rustls", 10 | )))] 11 | compile_error!( 12 | "one of the features ['runtime-actix-native-tls', 'runtime-async-std-native-tls', \ 13 | 'runtime-tokio-native-tls', 'runtime-actix-rustls', 'runtime-async-std-rustls', \ 14 | 'runtime-tokio-rustls'] must be enabled" 15 | ); 16 | 17 | #[cfg(any( 18 | all(feature = "_rt-actix", feature = "_rt-async-std"), 19 | all(feature = "_rt-actix", feature = "_rt-tokio"), 20 | all(feature = "_rt-async-std", feature = "_rt-tokio"), 21 | all(feature = "_tls-native-tls", feature = "_tls-rustls"), 22 | ))] 23 | compile_error!( 24 | "only one of ['runtime-actix-native-tls', 'runtime-async-std-native-tls', \ 25 | 'runtime-tokio-native-tls', 'runtime-actix-rustls', 'runtime-async-std-rustls', \ 26 | 'runtime-tokio-rustls'] can be enabled" 27 | ); 28 | 29 | #[cfg(feature = "_rt-async-std")] 30 | mod rt_async_std; 31 | 32 | #[cfg(any(feature = "_rt-tokio", feature = "_rt-actix"))] 33 | mod rt_tokio; 34 | 35 | #[cfg(feature = "_tls-native-tls")] 36 | pub use native_tls; 37 | 38 | // 39 | // Actix *OR* Tokio 40 | // 41 | 42 | #[cfg(any(feature = "_rt-tokio", feature = "_rt-actix"))] 43 | pub use rt_tokio::*; 44 | 45 | #[cfg(all( 46 | feature = "_rt-async-std", 47 | not(any(feature = "_rt-tokio", feature = "_rt-actix")) 48 | ))] 49 | pub use rt_async_std::*; 50 | -------------------------------------------------------------------------------- /sqlx-rt/src/rt_async_std.rs: -------------------------------------------------------------------------------- 1 | pub use async_std::{ 2 | self, fs, future::timeout, io::prelude::ReadExt as AsyncReadExt, 3 | io::prelude::WriteExt as AsyncWriteExt, io::Read as AsyncRead, io::Write as AsyncWrite, 4 | net::TcpStream, sync::Mutex as AsyncMutex, task::sleep, task::spawn, task::yield_now, 5 | }; 6 | 7 | #[cfg(unix)] 8 | pub use async_std::os::unix::net::UnixStream; 9 | 10 | #[cfg(all(feature = "_tls-native-tls", not(feature = "_tls-rustls")))] 11 | pub use async_native_tls::{TlsConnector, TlsStream}; 12 | 13 | #[cfg(all(feature = "_tls-rustls", not(feature = "_tls-native-tls")))] 14 | pub use futures_rustls::{client::TlsStream, TlsConnector}; 15 | 16 | pub use async_std::task::{block_on, block_on as test_block_on}; 17 | 18 | pub fn enter_runtime(f: F) -> R 19 | where 20 | F: FnOnce() -> R, 21 | { 22 | // no-op for async-std 23 | f() 24 | } 25 | -------------------------------------------------------------------------------- /sqlx-rt/src/rt_tokio.rs: -------------------------------------------------------------------------------- 1 | pub use tokio::{ 2 | self, fs, io::AsyncRead, io::AsyncReadExt, io::AsyncWrite, io::AsyncWriteExt, io::ReadBuf, 3 | net::TcpStream, runtime::Handle, sync::Mutex as AsyncMutex, task::spawn, task::yield_now, 4 | time::sleep, time::timeout, 5 | }; 6 | 7 | #[cfg(unix)] 8 | pub use tokio::net::UnixStream; 9 | 10 | use once_cell::sync::Lazy; 11 | use tokio::runtime::{self, Runtime}; 12 | 13 | #[cfg(all(feature = "_tls-native-tls", not(feature = "_tls-rustls")))] 14 | pub use tokio_native_tls::{TlsConnector, TlsStream}; 15 | 16 | #[cfg(all(feature = "_tls-rustls", not(feature = "_tls-native-tls")))] 17 | pub use tokio_rustls::{client::TlsStream, TlsConnector}; 18 | 19 | // lazily initialize a global runtime once for multiple invocations of the macros 20 | static RUNTIME: Lazy = Lazy::new(|| { 21 | runtime::Builder::new_multi_thread() 22 | .enable_io() 23 | .enable_time() 24 | .build() 25 | .expect("failed to initialize Tokio runtime") 26 | }); 27 | 28 | pub fn block_on(future: F) -> F::Output { 29 | RUNTIME.block_on(future) 30 | } 31 | 32 | pub fn enter_runtime(f: F) -> R 33 | where 34 | F: FnOnce() -> R, 35 | { 36 | let _rt = RUNTIME.enter(); 37 | f() 38 | } 39 | 40 | pub fn test_block_on(future: F) -> F::Output { 41 | // For tests, we want a single runtime per thread for isolation. 42 | runtime::Builder::new_current_thread() 43 | .enable_all() 44 | .build() 45 | .expect("failed to initialize Tokio test runtime") 46 | .block_on(future) 47 | } 48 | -------------------------------------------------------------------------------- /sqlx-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sqlx-test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | sqlx = { package = "sqlx-oldapi", default-features = false, path = ".." } 9 | env_logger = "0.11.1" 10 | dotenvy = "0.15.0" 11 | anyhow = "1.0.26" 12 | async-std = { version = "1.8.0", features = [ "attributes" ] } 13 | tokio = { version = "1.0.1", features = [ "full" ] } 14 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | docker compose -f tests/docker-compose.yml run -it -p 5432:5432 --name postgres_16 postgres_16 3 | DATABASE_URL="postgres://postgres@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=./tests/certs/ca.crt&sslcert=./tests/certs/client.crt&sslkey=./tests/keys/client.key" cargo test --features any,postgres,macros,all-types,runtime-actix-rustls -- 4 | 5 | docker compose -f tests/docker-compose.yml run -it -p 1433:1433 --rm --name mssql_2022 mssql_2022 6 | DATABASE_URL='mssql://sa:Password123!@localhost/sqlx' cargo test --features any,mssql,macros,all-types,runtime-actix-rustls -- 7 | 8 | docker compose -f tests/docker-compose.yml run -it -p 3306:3306 --name mysql_8 mysql_8 9 | DATABASE_URL='mysql://root:password@localhost/sqlx' cargo test --features any,mysql,macros,all-types,runtime-actix-rustls -- 10 | 11 | DATABASE_URL='sqlite://./tests/sqlite/sqlite.db' cargo test --features any,sqlite,macros,all-types,runtime-actix-rustls -- 12 | -------------------------------------------------------------------------------- /tests/.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !certs/* 3 | !keys/* 4 | !mssql/*.sh 5 | !*/*.sql 6 | !postgres/pg_hba.conf 7 | !postgres/postgresql.conf -------------------------------------------------------------------------------- /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.srl: -------------------------------------------------------------------------------- 1 | B6C71F4B11C0A18A 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/migrate/macro.rs: -------------------------------------------------------------------------------- 1 | use sqlx_oldapi::migrate::Migrator; 2 | use std::path::Path; 3 | 4 | static EMBEDDED_SIMPLE: Migrator = sqlx_oldapi::migrate!("tests/migrate/migrations_simple"); 5 | static EMBEDDED_REVERSIBLE: Migrator = sqlx_oldapi::migrate!("tests/migrate/migrations_reversible"); 6 | 7 | #[sqlx_macros::test] 8 | async fn same_output() -> anyhow::Result<()> { 9 | let runtime_simple = Migrator::new(Path::new("tests/migrate/migrations_simple")).await?; 10 | let runtime_reversible = 11 | Migrator::new(Path::new("tests/migrate/migrations_reversible")).await?; 12 | 13 | assert_same(&EMBEDDED_SIMPLE, &runtime_simple); 14 | assert_same(&EMBEDDED_REVERSIBLE, &runtime_reversible); 15 | 16 | Ok(()) 17 | } 18 | 19 | fn assert_same(embedded: &Migrator, runtime: &Migrator) { 20 | assert_eq!(runtime.migrations.len(), embedded.migrations.len()); 21 | 22 | for (e, r) in embedded.iter().zip(runtime.iter()) { 23 | assert_eq!(e.version, r.version); 24 | assert_eq!(e.description, r.description); 25 | assert_eq!(e.migration_type, r.migration_type); 26 | assert_eq!(e.sql, r.sql); 27 | assert_eq!(e.checksum, r.checksum); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /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/mssql/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG VERSION 2 | FROM mcr.microsoft.com/mssql/server:${VERSION} 3 | 4 | # Create a config directory 5 | USER root 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 | 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 for SQL Server to be ready for connections 4 | until /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P $SA_PASSWORD -d master -Q "SELECT 1;" -No 5 | do 6 | echo "Waiting for SQL Server to be ready..." 7 | sleep 1 8 | done 9 | 10 | # Run the setup script to create the DB and the schema in the DB 11 | /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P $SA_PASSWORD -d master -i setup.sql -No 12 | -------------------------------------------------------------------------------- /tests/mssql/describe.rs: -------------------------------------------------------------------------------- 1 | use sqlx_oldapi::mssql::Mssql; 2 | use sqlx_oldapi::{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/mssql.conf: -------------------------------------------------------------------------------- 1 | [network] 2 | forceencryption = 0 -------------------------------------------------------------------------------- /tests/mssql/setup.sql: -------------------------------------------------------------------------------- 1 | IF DB_ID('sqlx') IS NULL 2 | BEGIN 3 | CREATE DATABASE sqlx; 4 | END; 5 | GO 6 | 7 | USE sqlx; 8 | GO 9 | 10 | IF OBJECT_ID('tweet') IS NULL 11 | BEGIN 12 | CREATE TABLE tweet 13 | ( 14 | id BIGINT NOT NULL PRIMARY KEY, 15 | text NVARCHAR(4000) NOT NULL, 16 | is_sent TINYINT NOT NULL DEFAULT 1, 17 | owner_id BIGINT 18 | ); 19 | END; 20 | GO 21 | -------------------------------------------------------------------------------- /tests/mysql/fixtures/comments.sql: -------------------------------------------------------------------------------- 1 | insert into comment(comment_id, post_id, user_id, content, created_at) 2 | values (1, 3 | 1, 4 | 2, 5 | 'lol bet ur still bad, 1v1 me', 6 | timestamp(now(), '-0:50:00')), 7 | (2, 8 | 1, 9 | 1, 10 | 'you''re on!', 11 | timestamp(now(), '-0:45:00')), 12 | (3, 13 | 2, 14 | 1, 15 | 'lol you''re just mad you lost :P', 16 | timestamp(now(), '-0:15:00')); 17 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | COPY postgres/postgresql.conf /etc/postgresql/postgresql.conf 10 | 11 | # Fix permissions 12 | RUN chown 70:70 /var/lib/postgresql/server.crt /var/lib/postgresql/server.key 13 | RUN chmod 0600 /var/lib/postgresql/server.crt /var/lib/postgresql/server.key 14 | -------------------------------------------------------------------------------- /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/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_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/postgresql.conf: -------------------------------------------------------------------------------- 1 | log_line_prefix = '%m [%p] %q%u@%d ' 2 | log_connections = on 3 | log_disconnections = on 4 | log_duration = on 5 | log_hostname = on 6 | 7 | # - Connection Settings - 8 | listen_addresses = '*' -------------------------------------------------------------------------------- /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/sql-createtype.html 5 | CREATE TYPE status AS ENUM ('new', 'open', 'closed'); 6 | 7 | -- https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-DECLARING 8 | CREATE TYPE inventory_item AS 9 | ( 10 | name TEXT, 11 | supplier_id INT, 12 | price BIGINT 13 | ); 14 | 15 | -- https://github.com/prisma/database-schema-examples/tree/master/postgres/basic-twitter#basic-twitter 16 | CREATE TABLE tweet 17 | ( 18 | id BIGSERIAL PRIMARY KEY, 19 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 20 | text TEXT NOT NULL, 21 | owner_id BIGINT 22 | ); 23 | 24 | CREATE TYPE float_range AS RANGE 25 | ( 26 | subtype = float8, 27 | subtype_diff = float8mi 28 | ); 29 | 30 | CREATE TABLE products ( 31 | product_no INTEGER, 32 | name TEXT, 33 | price NUMERIC CHECK (price > 0) 34 | ); 35 | -------------------------------------------------------------------------------- /tests/postgres/test-query.sql: -------------------------------------------------------------------------------- 1 | SELECT id "id!", name from (VALUES (1, null)) accounts(id, name) 2 | -------------------------------------------------------------------------------- /tests/sqlite/.gitignore: -------------------------------------------------------------------------------- 1 | sqlite.db 2 | 3 | -------------------------------------------------------------------------------- /tests/sqlite/derives.rs: -------------------------------------------------------------------------------- 1 | use sqlx_oldapi::Sqlite; 2 | use sqlx_test::test_type; 3 | 4 | #[derive(Debug, PartialEq, sqlx_oldapi::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 accounts ( 12 | id INTEGER NOT NULL PRIMARY KEY, 13 | name TEXT NOT NULL, 14 | is_active BOOLEAN 15 | ); 16 | INSERT INTO accounts(id, name, is_active) 17 | VALUES (1, 'Herp Derpinson', 1); 18 | CREATE VIEW accounts_view as 19 | SELECT * 20 | FROM accounts; 21 | -------------------------------------------------------------------------------- /tests/sqlite/sqlite.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sqlpage/sqlx-oldapi/2c05b9d86bfa61811070e27503e81587b8ea5ee6/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 = "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_oldapi::query!("select CONVERT(now(), DATE) date"); 3 | 4 | let _ = sqlx_oldapi::query!("select CONVERT(now(), TIME) time"); 5 | 6 | let _ = sqlx_oldapi::query!("select CONVERT(now(), DATETIME) datetime"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/ui/mysql/gated/chrono.stderr: -------------------------------------------------------------------------------- 1 | error: optional feature `chrono` required for type DATE of column #1 ("date") 2 | --> $DIR/chrono.rs:2:13 3 | | 4 | 2 | let _ = sqlx_oldapi::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 feature `chrono` required for type TIME of column #1 ("time") 10 | --> $DIR/chrono.rs:4:13 11 | | 12 | 4 | let _ = sqlx_oldapi::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 feature `chrono` required for type DATETIME of column #1 ("datetime") 18 | --> $DIR/chrono.rs:6:13 19 | | 20 | 6 | let _ = sqlx_oldapi::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_oldapi::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_oldapi::_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_oldapi::query!("select now()::date"); 3 | 4 | let _ = sqlx_oldapi::query!("select now()::time"); 5 | 6 | let _ = sqlx_oldapi::query!("select now()::timestamp"); 7 | 8 | let _ = sqlx_oldapi::query!("select now()::timestamptz"); 9 | 10 | let _ = sqlx_oldapi::query!("select $1::date", ()); 11 | 12 | let _ = sqlx_oldapi::query!("select $1::time", ()); 13 | 14 | let _ = sqlx_oldapi::query!("select $1::timestamp", ()); 15 | 16 | let _ = sqlx_oldapi::query!("select $1::timestamptz", ()); 17 | } 18 | -------------------------------------------------------------------------------- /tests/ui/postgres/gated/ipnetwork.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _ = sqlx_oldapi::query!("select '127.0.0.1'::inet"); 3 | 4 | let _ = sqlx_oldapi::query!("select '2001:4f8:3:ba::/64'::cidr"); 5 | 6 | let _ = sqlx_oldapi::query!("select $1::inet", ()); 7 | 8 | let _ = sqlx_oldapi::query!("select $1::cidr", ()); 9 | } 10 | -------------------------------------------------------------------------------- /tests/ui/postgres/gated/ipnetwork.stderr: -------------------------------------------------------------------------------- 1 | error: optional feature `ipnetwork` required for type INET of column #1 ("inet") 2 | --> $DIR/ipnetwork.rs:2:13 3 | | 4 | 2 | let _ = sqlx_oldapi::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 feature `ipnetwork` required for type CIDR of column #1 ("cidr") 10 | --> $DIR/ipnetwork.rs:4:13 11 | | 12 | 4 | let _ = sqlx_oldapi::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 feature `ipnetwork` required for type INET of param #1 18 | --> $DIR/ipnetwork.rs:6:13 19 | | 20 | 6 | let _ = sqlx_oldapi::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 feature `ipnetwork` required for type CIDR of param #1 26 | --> $DIR/ipnetwork.rs:8:13 27 | | 28 | 8 | let _ = sqlx_oldapi::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_oldapi::query!("select 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'::uuid"); 3 | let _ = sqlx_oldapi::query!("select $1::uuid", ()); 4 | } 5 | -------------------------------------------------------------------------------- /tests/ui/postgres/gated/uuid.stderr: -------------------------------------------------------------------------------- 1 | error: optional feature `uuid` required for type UUID of column #1 ("uuid") 2 | --> $DIR/uuid.rs:2:13 3 | | 4 | 2 | let _ = sqlx_oldapi::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 feature `uuid` required for type UUID of param #1 10 | --> $DIR/uuid.rs:3:13 11 | | 12 | 3 | let _ = sqlx_oldapi::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_oldapi::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_oldapi::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_oldapi::query!("select null::circle"); 4 | let _ = sqlx_oldapi::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_oldapi::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_oldapi::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_oldapi::query!("select $1::text", 0i32); 3 | 4 | let _query = sqlx_oldapi::query!("select $1::text", &0i32); 5 | 6 | let _query = sqlx_oldapi::query!("select $1::text", Some(0i32)); 7 | 8 | let arg = 0i32; 9 | let _query = sqlx_oldapi::query!("select $1::text", arg); 10 | 11 | let arg = Some(0i32); 12 | let _query = sqlx_oldapi::query!("select $1::text", arg); 13 | let _query = sqlx_oldapi::query!("select $1::text", arg.as_ref()); 14 | } 15 | -------------------------------------------------------------------------------- /tests/ui/sqlite/expression-column-type.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _ = sqlx_oldapi::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_oldapi::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 | --------------------------------------------------------------------------------