Repository: launchbadge/sqlx Branch: main Commit: 05c8dc14a82c Files: 690 Total size: 3.7 MB Directory structure: gitextract_7wdu157x/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ └── feature_request.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── examples.yml │ ├── sqlx-cli.yml │ └── sqlx.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── FAQ.md ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches/ │ └── sqlite/ │ └── describe.rs ├── clippy.toml ├── contrib/ │ └── ide/ │ └── vscode/ │ └── settings.json ├── examples/ │ ├── .gitignore │ ├── mysql/ │ │ └── todos/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── migrations/ │ │ │ └── 20200718111257_todos.sql │ │ └── src/ │ │ └── main.rs │ ├── postgres/ │ │ ├── axum-social-with-tests/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── migrations/ │ │ │ │ ├── 1_user.sql │ │ │ │ ├── 2_post.sql │ │ │ │ └── 3_comment.sql │ │ │ ├── src/ │ │ │ │ ├── http/ │ │ │ │ │ ├── error.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── post/ │ │ │ │ │ │ ├── comment.rs │ │ │ │ │ │ └── mod.rs │ │ │ │ │ └── user.rs │ │ │ │ ├── lib.rs │ │ │ │ ├── main.rs │ │ │ │ └── password.rs │ │ │ └── tests/ │ │ │ ├── comment.rs │ │ │ ├── common.rs │ │ │ ├── fixtures/ │ │ │ │ ├── comments.sql │ │ │ │ ├── posts.sql │ │ │ │ └── users.sql │ │ │ ├── post.rs │ │ │ └── user.rs │ │ ├── chat/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── files/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── migrations/ │ │ │ │ └── 20220712221654_files.sql │ │ │ ├── queries/ │ │ │ │ ├── insert_seed_data.sql │ │ │ │ └── list_all_posts.sql │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── json/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── migrations/ │ │ │ │ └── 20200824190010_json.sql │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── listen/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── mockable-todos/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── docker-compose.yml │ │ │ ├── migrations/ │ │ │ │ └── 20200718111257_todos.sql │ │ │ └── src/ │ │ │ └── main.rs │ │ ├── multi-database/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── accounts/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── migrations/ │ │ │ │ │ ├── 01_setup.sql │ │ │ │ │ ├── 02_account.sql │ │ │ │ │ └── 03_session.sql │ │ │ │ ├── sqlx.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── payments/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── migrations/ │ │ │ │ │ ├── 01_setup.sql │ │ │ │ │ └── 02_payment.sql │ │ │ │ ├── sqlx.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── sqlx.toml │ │ │ └── src/ │ │ │ ├── main.rs │ │ │ └── migrations/ │ │ │ ├── 01_setup.sql │ │ │ └── 02_purchase.sql │ │ ├── multi-tenant/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── accounts/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── migrations/ │ │ │ │ │ ├── 01_setup.sql │ │ │ │ │ ├── 02_account.sql │ │ │ │ │ └── 03_session.sql │ │ │ │ ├── sqlx.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── payments/ │ │ │ │ ├── Cargo.toml │ │ │ │ ├── migrations/ │ │ │ │ │ ├── 01_setup.sql │ │ │ │ │ └── 02_payment.sql │ │ │ │ ├── sqlx.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ ├── sqlx.toml │ │ │ └── src/ │ │ │ ├── main.rs │ │ │ └── migrations/ │ │ │ ├── 01_setup.sql │ │ │ └── 02_purchase.sql │ │ ├── preferred-crates/ │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── sqlx.toml │ │ │ ├── src/ │ │ │ │ ├── main.rs │ │ │ │ └── migrations/ │ │ │ │ ├── 01_setup.sql │ │ │ │ └── 02_users.sql │ │ │ ├── uses-rust-decimal/ │ │ │ │ ├── Cargo.toml │ │ │ │ └── src/ │ │ │ │ └── lib.rs │ │ │ └── uses-time/ │ │ │ ├── Cargo.toml │ │ │ └── src/ │ │ │ └── lib.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/ │ │ ├── extension/ │ │ │ ├── Cargo.toml │ │ │ ├── download-extension.sh │ │ │ ├── migrations/ │ │ │ │ └── 20250203094951_addresses.sql │ │ │ ├── sqlx.toml │ │ │ └── src/ │ │ │ └── main.rs │ │ └── todos/ │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── migrations/ │ │ │ └── 20200718111257_todos.sql │ │ └── src/ │ │ └── main.rs │ └── x.py ├── gen-changelog.sh ├── rust-toolchain.toml ├── sqlx-cli/ │ ├── Cargo.toml │ ├── README.md │ ├── src/ │ │ ├── bin/ │ │ │ ├── cargo-sqlx.rs │ │ │ └── sqlx.rs │ │ ├── completions.rs │ │ ├── database.rs │ │ ├── lib.rs │ │ ├── metadata.rs │ │ ├── migrate.rs │ │ ├── opt.rs │ │ └── prepare.rs │ └── tests/ │ ├── add.rs │ ├── assets/ │ │ ├── config_default_type_reversible.toml │ │ ├── config_default_versioning_sequential.toml │ │ ├── config_default_versioning_timestamp.toml │ │ └── sample_metadata.json │ ├── common/ │ │ └── mod.rs │ ├── ignored-chars/ │ │ ├── BOM/ │ │ │ ├── .gitattributes │ │ │ ├── 1_user.sql │ │ │ ├── 2_post.sql │ │ │ └── 3_comment.sql │ │ ├── CRLF/ │ │ │ ├── .gitattributes │ │ │ ├── 1_user.sql │ │ │ ├── 2_post.sql │ │ │ └── 3_comment.sql │ │ ├── LF/ │ │ │ ├── .gitattributes │ │ │ ├── 1_user.sql │ │ │ ├── 2_post.sql │ │ │ └── 3_comment.sql │ │ ├── oops-all-tabs/ │ │ │ ├── .gitattributes │ │ │ ├── 1_user.sql │ │ │ ├── 2_post.sql │ │ │ └── 3_comment.sql │ │ └── sqlx.toml │ ├── migrate.rs │ └── migrations_reversible/ │ ├── 20230101000000_test1.down.sql │ ├── 20230101000000_test1.up.sql │ ├── 20230201000000_test2.down.sql │ ├── 20230201000000_test2.up.sql │ ├── 20230301000000_test3.down.sql │ ├── 20230301000000_test3.up.sql │ ├── 20230401000000_test4.down.sql │ ├── 20230401000000_test4.up.sql │ ├── 20230501000000_test5.down.sql │ └── 20230501000000_test5.up.sql ├── sqlx-core/ │ ├── Cargo.toml │ └── src/ │ ├── acquire.rs │ ├── any/ │ │ ├── arguments.rs │ │ ├── column.rs │ │ ├── connection/ │ │ │ ├── backend.rs │ │ │ ├── executor.rs │ │ │ └── mod.rs │ │ ├── database.rs │ │ ├── driver.rs │ │ ├── error.rs │ │ ├── kind.rs │ │ ├── migrate.rs │ │ ├── mod.rs │ │ ├── options.rs │ │ ├── query_result.rs │ │ ├── row.rs │ │ ├── statement.rs │ │ ├── transaction.rs │ │ ├── type_info.rs │ │ ├── types/ │ │ │ ├── blob.rs │ │ │ ├── bool.rs │ │ │ ├── float.rs │ │ │ ├── int.rs │ │ │ ├── mod.rs │ │ │ └── str.rs │ │ └── value.rs │ ├── arguments.rs │ ├── column.rs │ ├── common/ │ │ ├── mod.rs │ │ └── statement_cache.rs │ ├── config/ │ │ ├── common.rs │ │ ├── drivers.rs │ │ ├── macros.rs │ │ ├── migrate.rs │ │ ├── mod.rs │ │ ├── reference.toml │ │ └── tests.rs │ ├── connection.rs │ ├── database.rs │ ├── decode.rs │ ├── describe.rs │ ├── encode.rs │ ├── error.rs │ ├── executor.rs │ ├── ext/ │ │ ├── async_stream.rs │ │ ├── mod.rs │ │ └── ustr.rs │ ├── from_row.rs │ ├── fs.rs │ ├── io/ │ │ ├── buf.rs │ │ ├── buf_mut.rs │ │ ├── buf_stream.rs │ │ ├── decode.rs │ │ ├── encode.rs │ │ ├── mod.rs │ │ ├── read_buf.rs │ │ └── write_and_flush.rs │ ├── lib.rs │ ├── logger.rs │ ├── migrate/ │ │ ├── error.rs │ │ ├── migrate.rs │ │ ├── migration.rs │ │ ├── migration_type.rs │ │ ├── migrator.rs │ │ ├── mod.rs │ │ └── source.rs │ ├── net/ │ │ ├── mod.rs │ │ ├── socket/ │ │ │ ├── buffered.rs │ │ │ └── mod.rs │ │ └── tls/ │ │ ├── mod.rs │ │ ├── tls_native_tls.rs │ │ ├── tls_rustls.rs │ │ └── util.rs │ ├── pool/ │ │ ├── connection.rs │ │ ├── executor.rs │ │ ├── inner.rs │ │ ├── maybe.rs │ │ ├── mod.rs │ │ └── options.rs │ ├── query.rs │ ├── query_as.rs │ ├── query_builder.rs │ ├── query_scalar.rs │ ├── raw_sql.rs │ ├── row.rs │ ├── rt/ │ │ ├── mod.rs │ │ ├── rt_async_io/ │ │ │ ├── mod.rs │ │ │ ├── socket.rs │ │ │ └── timeout.rs │ │ └── rt_tokio/ │ │ ├── mod.rs │ │ └── socket.rs │ ├── sql_str.rs │ ├── statement.rs │ ├── sync.rs │ ├── testing/ │ │ ├── fixtures.rs │ │ └── mod.rs │ ├── transaction.rs │ ├── type_checking.rs │ ├── type_info.rs │ ├── types/ │ │ ├── bstr.rs │ │ ├── json.rs │ │ ├── mod.rs │ │ ├── non_zero.rs │ │ └── text.rs │ └── value.rs ├── sqlx-macros/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── sqlx-macros-core/ │ ├── Cargo.toml │ ├── clippy.toml │ └── src/ │ ├── common.rs │ ├── database/ │ │ ├── impls.rs │ │ └── mod.rs │ ├── derives/ │ │ ├── attributes.rs │ │ ├── decode.rs │ │ ├── encode.rs │ │ ├── mod.rs │ │ ├── row.rs │ │ └── type.rs │ ├── lib.rs │ ├── migrate.rs │ ├── query/ │ │ ├── args.rs │ │ ├── cache.rs │ │ ├── data.rs │ │ ├── input.rs │ │ ├── metadata.rs │ │ ├── mod.rs │ │ └── output.rs │ └── test_attr.rs ├── sqlx-mysql/ │ ├── Cargo.toml │ └── src/ │ ├── any.rs │ ├── arguments.rs │ ├── collation.rs │ ├── column.rs │ ├── connection/ │ │ ├── auth.rs │ │ ├── establish.rs │ │ ├── executor.rs │ │ ├── mod.rs │ │ ├── stream.rs │ │ └── tls.rs │ ├── database.rs │ ├── error.rs │ ├── io/ │ │ ├── buf.rs │ │ ├── buf_mut.rs │ │ └── mod.rs │ ├── lib.rs │ ├── migrate.rs │ ├── options/ │ │ ├── connect.rs │ │ ├── mod.rs │ │ ├── parse.rs │ │ └── ssl_mode.rs │ ├── protocol/ │ │ ├── auth.rs │ │ ├── capabilities.rs │ │ ├── connect/ │ │ │ ├── auth_switch.rs │ │ │ ├── handshake.rs │ │ │ ├── handshake_response.rs │ │ │ ├── mod.rs │ │ │ └── ssl_request.rs │ │ ├── mod.rs │ │ ├── packet.rs │ │ ├── response/ │ │ │ ├── eof.rs │ │ │ ├── err.rs │ │ │ ├── mod.rs │ │ │ ├── ok.rs │ │ │ └── status.rs │ │ ├── row.rs │ │ ├── statement/ │ │ │ ├── execute.rs │ │ │ ├── mod.rs │ │ │ ├── prepare.rs │ │ │ ├── prepare_ok.rs │ │ │ ├── row.rs │ │ │ └── stmt_close.rs │ │ └── text/ │ │ ├── column.rs │ │ ├── mod.rs │ │ ├── ping.rs │ │ ├── query.rs │ │ ├── quit.rs │ │ └── row.rs │ ├── query_result.rs │ ├── row.rs │ ├── statement.rs │ ├── testing/ │ │ └── mod.rs │ ├── transaction.rs │ ├── type_checking.rs │ ├── type_info.rs │ ├── types/ │ │ ├── bigdecimal.rs │ │ ├── bool.rs │ │ ├── bytes.rs │ │ ├── chrono.rs │ │ ├── float.rs │ │ ├── inet.rs │ │ ├── int.rs │ │ ├── json.rs │ │ ├── mod.rs │ │ ├── mysql_time.rs │ │ ├── rust_decimal.rs │ │ ├── str.rs │ │ ├── text.rs │ │ ├── time.rs │ │ ├── uint.rs │ │ └── uuid.rs │ └── value.rs ├── sqlx-postgres/ │ ├── Cargo.toml │ └── src/ │ ├── advisory_lock.rs │ ├── any.rs │ ├── arguments.rs │ ├── bind_iter.rs │ ├── column.rs │ ├── connection/ │ │ ├── describe.rs │ │ ├── establish.rs │ │ ├── executor.rs │ │ ├── mod.rs │ │ ├── resolve.rs │ │ ├── sasl.rs │ │ ├── stream.rs │ │ └── tls.rs │ ├── copy.rs │ ├── database.rs │ ├── error.rs │ ├── io/ │ │ ├── buf_mut.rs │ │ └── mod.rs │ ├── lib.rs │ ├── listener.rs │ ├── message/ │ │ ├── authentication.rs │ │ ├── backend_key_data.rs │ │ ├── bind.rs │ │ ├── close.rs │ │ ├── command_complete.rs │ │ ├── copy.rs │ │ ├── data_row.rs │ │ ├── describe.rs │ │ ├── execute.rs │ │ ├── flush.rs │ │ ├── mod.rs │ │ ├── notification.rs │ │ ├── parameter_description.rs │ │ ├── parameter_status.rs │ │ ├── parse.rs │ │ ├── parse_complete.rs │ │ ├── password.rs │ │ ├── query.rs │ │ ├── ready_for_query.rs │ │ ├── response.rs │ │ ├── row_description.rs │ │ ├── sasl.rs │ │ ├── ssl_request.rs │ │ ├── startup.rs │ │ ├── sync.rs │ │ └── terminate.rs │ ├── migrate.rs │ ├── options/ │ │ ├── connect.rs │ │ ├── doc.md │ │ ├── mod.rs │ │ ├── parse.rs │ │ ├── pgpass.rs │ │ └── ssl_mode.rs │ ├── query_result.rs │ ├── row.rs │ ├── statement.rs │ ├── testing/ │ │ └── mod.rs │ ├── transaction.rs │ ├── type_checking.rs │ ├── type_info.rs │ ├── types/ │ │ ├── array.rs │ │ ├── bigdecimal-range.md │ │ ├── bigdecimal.rs │ │ ├── bit_vec.rs │ │ ├── bool.rs │ │ ├── bytes.rs │ │ ├── chrono/ │ │ │ ├── date.rs │ │ │ ├── datetime.rs │ │ │ ├── mod.rs │ │ │ └── time.rs │ │ ├── citext.rs │ │ ├── cube.rs │ │ ├── float.rs │ │ ├── geometry/ │ │ │ ├── box.rs │ │ │ ├── circle.rs │ │ │ ├── line.rs │ │ │ ├── line_segment.rs │ │ │ ├── mod.rs │ │ │ ├── path.rs │ │ │ ├── point.rs │ │ │ └── polygon.rs │ │ ├── hstore.rs │ │ ├── int.rs │ │ ├── interval.rs │ │ ├── ipnet/ │ │ │ ├── ipaddr.rs │ │ │ ├── ipnet.rs │ │ │ └── mod.rs │ │ ├── ipnetwork/ │ │ │ ├── ipaddr.rs │ │ │ ├── ipnetwork.rs │ │ │ └── mod.rs │ │ ├── json.rs │ │ ├── lquery.rs │ │ ├── ltree.rs │ │ ├── mac_address.rs │ │ ├── mod.rs │ │ ├── money.rs │ │ ├── numeric.rs │ │ ├── oid.rs │ │ ├── range.rs │ │ ├── record.rs │ │ ├── rust_decimal-range.md │ │ ├── rust_decimal.rs │ │ ├── str.rs │ │ ├── text.rs │ │ ├── time/ │ │ │ ├── date.rs │ │ │ ├── datetime.rs │ │ │ ├── mod.rs │ │ │ └── time.rs │ │ ├── time_tz.rs │ │ ├── tuple.rs │ │ ├── uuid.rs │ │ └── void.rs │ └── value.rs ├── sqlx-sqlite/ │ ├── Cargo.toml │ └── src/ │ ├── any.rs │ ├── arguments.rs │ ├── column.rs │ ├── connection/ │ │ ├── collation.rs │ │ ├── describe.rs │ │ ├── deserialize.rs │ │ ├── establish.rs │ │ ├── execute.rs │ │ ├── executor.rs │ │ ├── explain.rs │ │ ├── handle.rs │ │ ├── intmap.rs │ │ ├── mod.rs │ │ ├── preupdate_hook.rs │ │ └── worker.rs │ ├── database.rs │ ├── error.rs │ ├── lib.rs │ ├── logger.rs │ ├── migrate.rs │ ├── options/ │ │ ├── auto_vacuum.rs │ │ ├── connect.rs │ │ ├── journal_mode.rs │ │ ├── locking_mode.rs │ │ ├── mod.rs │ │ ├── parse.rs │ │ └── synchronous.rs │ ├── query_result.rs │ ├── regexp.rs │ ├── row.rs │ ├── statement/ │ │ ├── handle.rs │ │ ├── mod.rs │ │ ├── unlock_notify.rs │ │ └── virtual.rs │ ├── testing/ │ │ └── mod.rs │ ├── transaction.rs │ ├── type_checking.rs │ ├── type_info.rs │ ├── types/ │ │ ├── bool.rs │ │ ├── bytes.rs │ │ ├── chrono.rs │ │ ├── float.rs │ │ ├── int.rs │ │ ├── json.rs │ │ ├── mod.rs │ │ ├── str.rs │ │ ├── text.rs │ │ ├── time.rs │ │ ├── uint.rs │ │ └── uuid.rs │ └── value.rs ├── sqlx-test/ │ ├── Cargo.toml │ └── src/ │ └── lib.rs ├── src/ │ ├── any/ │ │ ├── install_drivers_note.md │ │ └── mod.rs │ ├── lib.md │ ├── lib.rs │ ├── macros/ │ │ ├── mod.rs │ │ └── test.md │ ├── spec_error.rs │ └── ty_match.rs └── tests/ ├── .dockerignore ├── .env ├── .gitignore ├── README.md ├── any/ │ ├── any.rs │ └── pool.rs ├── certs/ │ ├── .gitignore │ ├── README.md │ ├── ca.crt │ ├── client.crt │ ├── keys/ │ │ ├── ca.key │ │ ├── client.key │ │ └── server.key │ └── server.crt ├── docker-compose.yml ├── docker.py ├── fixtures/ │ ├── mysql/ │ │ ├── posts.sql │ │ └── users.sql │ └── postgres/ │ ├── posts.sql │ └── users.sql ├── 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-2017.dockerfile │ ├── mssql.rs │ ├── setup.sql │ └── types.rs ├── mysql/ │ ├── Dockerfile │ ├── derives.rs │ ├── describe.rs │ ├── error.rs │ ├── fixtures/ │ │ ├── comments.sql │ │ ├── posts.sql │ │ └── users.sql │ ├── macros.rs │ ├── migrate.rs │ ├── migrations/ │ │ ├── 1_user.sql │ │ ├── 2_post.sql │ │ └── 3_comment.sql │ ├── migrations_reversible/ │ │ ├── 20220721124650_add_table.down.sql │ │ ├── 20220721124650_add_table.up.sql │ │ ├── 20220721125033_modify_column.down.sql │ │ └── 20220721125033_modify_column.up.sql │ ├── migrations_simple/ │ │ ├── 20220721115250_add_test_table.sql │ │ └── 20220721115524_convert_type.sql │ ├── my.cnf │ ├── mysql.rs │ ├── rustsec.rs │ ├── setup-mariadb.sql │ ├── setup.sql │ ├── test-attr.rs │ └── types.rs ├── postgres/ │ ├── Dockerfile │ ├── derives.rs │ ├── describe.rs │ ├── error.rs │ ├── fixtures/ │ │ ├── comments.sql │ │ ├── posts.sql │ │ ├── rustsec/ │ │ │ └── 2024_0363.sql │ │ └── users.sql │ ├── macros.rs │ ├── migrate.rs │ ├── migrations/ │ │ ├── 0_setup.sql │ │ ├── 1_user.sql │ │ ├── 2_post.sql │ │ └── 3_comment.sql │ ├── migrations_no_tx/ │ │ └── 0_create_db.sql │ ├── migrations_reversible/ │ │ ├── 20220721124650_add_table.down.sql │ │ ├── 20220721124650_add_table.up.sql │ │ ├── 20220721125033_modify_column.down.sql │ │ └── 20220721125033_modify_column.up.sql │ ├── migrations_simple/ │ │ ├── 20220721115250_add_test_table.sql │ │ └── 20220721115524_convert_type.sql │ ├── pg_hba.conf │ ├── postgres.rs │ ├── query_builder.rs │ ├── rustsec.rs │ ├── setup.sql │ ├── test-attr.rs │ ├── test-query.sql │ └── types.rs ├── sqlite/ │ ├── .gitignore │ ├── any.rs │ ├── derives.rs │ ├── describe.rs │ ├── error.rs │ ├── fixtures/ │ │ ├── comments.sql │ │ ├── posts.sql │ │ └── users.sql │ ├── macros.rs │ ├── migrate.rs │ ├── migrations/ │ │ ├── 1_user.sql │ │ ├── 2_post.sql │ │ └── 3_comment.sql │ ├── migrations_no_tx/ │ │ └── 0_vacuum.sql │ ├── migrations_no_tx_reversible/ │ │ ├── 0_vacuum.down.sql │ │ └── 0_vacuum.up.sql │ ├── migrations_reversible/ │ │ ├── 20220721124650_add_table.down.sql │ │ ├── 20220721124650_add_table.up.sql │ │ ├── 20220721125033_modify_column.down.sql │ │ └── 20220721125033_modify_column.up.sql │ ├── migrations_simple/ │ │ ├── 20220721115250_add_test_table.sql │ │ └── 20220721115524_convert_type.sql │ ├── rustsec.rs │ ├── setup.sql │ ├── sqlcipher.rs │ ├── sqlite.rs │ ├── test-attr.rs │ └── types.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 ├── ui-tests.rs └── x.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true indent_style = space indent_size = 4 [*.yml] indent_size = 2 ================================================ FILE: .gitattributes ================================================ * text=auto eol=lf ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: I think I found a bug in SQLx description: Create a bug-report issue labels: [bug] body: - type: textarea id: related-issues validations: required: true attributes: label: I have found these related issues/pull requests description: | I have searched by clicking [HERE](https://github.com/launchbadge/sqlx/issues?q=) for existing issues, these are the ones I've found, and this is why I think this deserves a new issue. placeholder: "Related to ..." - type: textarea id: description validations: required: true attributes: label: Description description: Clear and concise description of what the bug is - type: textarea id: steps-to-reproduce validations: required: true attributes: label: Reproduction steps description: A small code snippet or a link to a Github repo or Gist, with instructions on reproducing the bug. - type: input id: sqlx-version attributes: label: SQLx version validations: required: true - type: input id: sqlx-features attributes: label: Enabled SQLx features validations: required: true - type: input id: db-server-and-version attributes: label: Database server and version placeholder: MySQL / Postgres / SQLite validations: required: true - type: input id: os-type attributes: label: Operating system validations: required: true - type: input id: rust-version attributes: label: Rust version description: You can get this via running `rustc --version` validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Enabling some optional features adds unexpected crates to Cargo.lock url: https://github.com/launchbadge/sqlx/issues/3211 about: See this issue. - name: I have a question or problem url: https://github.com/launchbadge/sqlx/tree/main/FAQ.md about: See our FAQ. - name: I have a question or problem not covered in the FAQ url: https://github.com/launchbadge/sqlx/discussions/new?category=q-a about: Open a Q&A discussion. - name: Join SQLx's Discord url: https://discord.gg/hPm3WqA about: Join our Discord server for help, discussions and release announcements. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yml ================================================ name: I have a feature request for SQLx description: Create a feature-request issue labels: [enhancement] body: - type: textarea id: related-issues validations: required: true attributes: label: I have found these related issues/pull requests description: "Provide context for your pull request." placeholder: | Closes \#... Relates to \#... - type: textarea id: feature-description validations: required: true attributes: label: Description description: A clear and concise description of what the problem is placeholder: You should add ... - type: textarea id: solution validations: required: true attributes: label: Prefered solution description: A clear and concise description of what you want to happen. placeholder: In my use-case, ... - type: textarea id: breaking-change validations: required: true attributes: label: Is this a breaking change? Why or why not? ================================================ FILE: .github/pull_request_template.md ================================================ ### Does your PR solve an issue? Delete this text and add "fixes #(issue number)". Do *not* just list issue numbers here as they will not be automatically closed on merging this pull request unless prefixed with "fixes" or "closes". ### Is this a breaking change? Delete this text and answer yes/no and explain. If yes, this pull request will need to wait for the next major release (`0.{x + 1}.0`) Behavior changes _can_ be breaking if significant enough. Consider [Hyrum's Law](https://www.hyrumslaw.com/): > With a sufficient number of users of an API, > it does not matter what you promise in the contract: > all observable behaviors of your system > will be depended on by somebody. ================================================ FILE: .github/workflows/examples.yml ================================================ name: Examples on: pull_request: push: branches: - main - '*-dev' jobs: sqlx-cli: name: Build SQLx CLI runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Setup Rust run: | rustup show active-toolchain || rustup toolchain install rustup override set stable - uses: Swatinem/rust-cache@v2 - run: > cargo build -p sqlx-cli --release --no-default-features --features mysql,postgres,sqlite,sqlx-toml - uses: actions/upload-artifact@v4 with: name: sqlx-cli path: | target/release/sqlx target/release/cargo-sqlx mysql: name: MySQL Examples runs-on: ubuntu-latest needs: sqlx-cli timeout-minutes: 30 strategy: matrix: offline: ['', 'offline'] services: mysql: image: mysql:latest env: MYSQL_ROOT_PASSWORD: password ports: - 3306:3306 steps: - name: Get SQLx-CLI uses: actions/download-artifact@v4 with: name: sqlx-cli # $HOME is interpreted differently by the shell path: /home/runner/.local/bin - run: | ls -R /home/runner/.local/bin chmod +x /home/runner/.local/bin/sqlx /home/runner/.local/bin/cargo-sqlx echo /home/runner/.local/bin >> $GITHUB_PATH sleep 10 - uses: actions/checkout@v4 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install - uses: Swatinem/rust-cache@v2 - name: Todos (Setup) working-directory: examples/mysql/todos env: DATABASE_URL: mysql://root:password@localhost:3306/todos?ssl-mode=disabled run: sqlx db setup - name: Todos (Prepare) if: ${{ matrix.offline }} working-directory: examples/mysql/todos env: DATABASE_URL: mysql://root:password@localhost:3306/todos?ssl-mode=disabled run: cargo sqlx prepare - name: Todos (Check Offline) if: ${{ matrix.offline }} run: | cargo clean -p sqlx-example-mysql-todos cargo check -p sqlx-example-mysql-todos - name: Todos (Prepare from .env) if: ${{ matrix.offline }} working-directory: examples/mysql/todos run: | echo "DATABASE_URL=mysql://root:password@localhost:3306/todos?ssl-mode=disabled" > .env cargo clean -p sqlx-example-mysql-todos cargo sqlx prepare rm .env - name: Todos (Run) env: DATABASE_URL: mysql://root:password@localhost:3306/todos?ssl-mode=disabled SQLX_OFFLINE: ${{ matrix.offline == 'offline' }} run: cargo run -p sqlx-example-mysql-todos postgres: name: PostgreSQL Examples runs-on: ubuntu-latest needs: sqlx-cli timeout-minutes: 30 strategy: matrix: offline: ['', 'offline'] services: postgres: image: postgres:latest env: POSTGRES_PASSWORD: password ports: - 5432:5432 steps: - name: Get SQLx-CLI uses: actions/download-artifact@v4 with: name: sqlx-cli path: /home/runner/.local/bin - run: | ls -R /home/runner/.local/bin chmod +x $HOME/.local/bin/sqlx chmod +x $HOME/.local/bin/cargo-sqlx echo $HOME/.local/bin >> $GITHUB_PATH sleep 10 - uses: actions/checkout@v4 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install - name: Axum Social with Tests (Setup) working-directory: examples/postgres/axum-social-with-tests env: DATABASE_URL: postgres://postgres:password@localhost:5432/axum-social run: sqlx db setup # Test `cargo sqlx prepare` setting `DATABASE_URL` both directly and in `.env` # This doesn't need to be done for every single example here, but should at least cover potential problem cases. - name: Axum Social with Tests (Prepare) if: ${{ matrix.offline }} env: DATABASE_URL: postgres://postgres:password@localhost:5432/axum-social run: cargo sqlx prepare -- -p sqlx-example-postgres-axum-social - name: Axum Social with Tests (Check Offline) if: ${{ matrix.offline }} run: | cargo clean -p sqlx-example-postgres-axum-social cargo check -p sqlx-example-postgres-axum-social - name: Axum Social with Tests (Prepare from .env) if: ${{ matrix.offline }} run: | echo "DATABASE_URL=postgres://postgres:password@localhost:5432/axum-social" > .env cargo clean -p sqlx-example-postgres-axum-social cargo sqlx prepare -- -p sqlx-example-postgres-axum-social rm .env - name: Axum Social with Tests (Test) env: DATABASE_URL: postgres://postgres:password@localhost:5432/axum-social SQLX_OFFLINE: ${{ matrix.offline == 'offline' }} run: cargo test -p sqlx-example-postgres-axum-social # The Chat example has an interactive TUI which is not trivial to test automatically, # so we only check that it compiles. - name: Chat (Check) run: cargo check -p sqlx-example-postgres-chat - name: Files (Setup) working-directory: examples/postgres/files env: DATABASE_URL: postgres://postgres:password@localhost:5432/files run: sqlx db setup - name: Files (Run) env: DATABASE_URL: postgres://postgres:password@localhost:5432/files run: cargo run -p sqlx-example-postgres-files - name: JSON (Setup) working-directory: examples/postgres/json env: DATABASE_URL: postgres://postgres:password@localhost:5432/json run: sqlx db setup - name: JSON (Run) env: DATABASE_URL: postgres://postgres:password@localhost:5432/json run: cargo run -p sqlx-example-postgres-json - name: Listen (Setup) working-directory: examples/postgres/listen env: DATABASE_URL: postgres://postgres:password@localhost:5432/listen run: sqlx db create - name: Listen (Run) env: DATABASE_URL: postgres://postgres:password@localhost:5432/listen run: cargo run -p sqlx-example-postgres-listen - name: Mockable TODOs (Setup) working-directory: examples/postgres/mockable-todos env: DATABASE_URL: postgres://postgres:password@localhost:5432/mockable-todos run: sqlx db setup - name: Mockable TODOs (Run) env: DATABASE_URL: postgres://postgres:password@localhost:5432/mockable-todos run: cargo run -p sqlx-example-postgres-mockable-todos - name: Multi-Database (Setup) working-directory: examples/postgres/multi-database env: DATABASE_URL: postgres://postgres:password@localhost:5432/multi-database ACCOUNTS_DATABASE_URL: postgres://postgres:password@localhost:5432/multi-database-accounts PAYMENTS_DATABASE_URL: postgres://postgres:password@localhost:5432/multi-database-payments run: | (cd accounts && sqlx db setup) (cd payments && sqlx db setup) sqlx db setup - name: Multi-Database (Prepare) if: ${{ matrix.offline }} env: DATABASE_URL: postgres://postgres:password@localhost:5432/multi-database ACCOUNTS_DATABASE_URL: postgres://postgres:password@localhost:5432/multi-database-accounts PAYMENTS_DATABASE_URL: postgres://postgres:password@localhost:5432/multi-database-payments run: | cargo clean -p sqlx-example-postgres-multi-database-accounts cargo clean -p sqlx-example-postgres-multi-database-payments cargo clean -p sqlx-example-postgres-multi-database # should include -accounts and -payments cargo sqlx prepare -- -p sqlx-example-postgres-multi-database - name: Multi-Database (Check Offline) if: ${{ matrix.offline }} run: | cargo clean -p sqlx-example-postgres-multi-database cargo check -p sqlx-example-postgres-multi-database - name: Multi-Database (Prepare from .env) if: ${{ matrix.offline }} run: | # Tried to get this to work with heredocs but had trouble writing tabs in YAML echo 'DATABASE_URL=postgres://postgres:password@localhost:5432/multi-database' >.env # Important: append, don't truncate echo 'ACCOUNTS_DATABASE_URL=postgres://postgres:password@localhost:5432/multi-database-accounts' >> .env echo 'PAYMENTS_DATABASE_URL=postgres://postgres:password@localhost:5432/multi-database-payments' >> .env cargo clean -p sqlx-example-postgres-multi-database-accounts cargo clean -p sqlx-example-postgres-multi-database-payments cargo clean -p sqlx-example-postgres-multi-database cargo sqlx prepare -- -p sqlx-example-postgres-multi-database rm .env - name: Multi-Database (Run) env: DATABASE_URL: postgres://postgres:password@localhost:5432/multi-database ACCOUNTS_DATABASE_URL: postgres://postgres:password@localhost:5432/multi-database-accounts PAYMENTS_DATABASE_URL: postgres://postgres:password@localhost:5432/multi-database-payments SQLX_OFFLINE: ${{ matrix.offline == 'offline' }} run: cargo run -p sqlx-example-postgres-multi-database - name: Multi-Tenant (Setup) working-directory: examples/postgres/multi-tenant env: DATABASE_URL: postgres://postgres:password@localhost:5432/multi-tenant run: | (cd accounts && sqlx db setup) (cd payments && sqlx migrate run) sqlx migrate run - name: Multi-Tenant (Prepare) if: ${{ matrix.offline }} env: DATABASE_URL: postgres://postgres:password@localhost:5432/multi-tenant run: | cargo clean -p sqlx-example-postgres-multi-tenant-accounts cargo clean -p sqlx-example-postgres-multi-tenant-payments cargo clean -p sqlx-example-postgres-multi-tenant # should include -accounts and -payments cargo sqlx prepare -- -p sqlx-example-postgres-multi-tenant - name: Multi-Tenant (Check Offline) if: ${{ matrix.offline }} run: cargo check -p sqlx-example-postgres-multi-tenant - name: Multi-Tenant (Prepare from .env) if: ${{ matrix.offline }} run: | echo "DATABASE_URL=postgres://postgres:password@localhost:5432/multi-tenant" > .env cargo clean -p sqlx-example-postgres-multi-tenant-accounts cargo clean -p sqlx-example-postgres-multi-tenant-payments cargo clean -p sqlx-example-postgres-multi-tenant # should include -accounts and -payments cargo sqlx prepare -- -p sqlx-example-postgres-multi-tenant rm .env - name: Multi-Tenant (Run) env: DATABASE_URL: postgres://postgres:password@localhost:5432/multi-tenant SQLX_OFFLINE: ${{ matrix.offline == 'offline' }} run: cargo run -p sqlx-example-postgres-multi-tenant - name: Preferred-Crates (Setup) working-directory: examples/postgres/preferred-crates env: DATABASE_URL: postgres://postgres:password@localhost:5432/preferred-crates run: sqlx db setup - name: Preferred-Crates (Run) env: DATABASE_URL: postgres://postgres:password@localhost:5432/preferred-crates run: cargo run -p sqlx-example-postgres-preferred-crates - name: TODOs (Setup) working-directory: examples/postgres/todos env: DATABASE_URL: postgres://postgres:password@localhost:5432/todos run: sqlx db setup - name: TODOs (Run) env: DATABASE_URL: postgres://postgres:password@localhost:5432/todos # TODO: test full CLI run: cargo run -p sqlx-example-postgres-todos - name: Transaction (Setup) working-directory: examples/postgres/transaction env: DATABASE_URL: postgres://postgres:password@localhost:5432/txn run: sqlx db setup - name: Transaction (Run) env: DATABASE_URL: postgres://postgres:password@localhost:5432/txn run: cargo run -p sqlx-example-postgres-transaction sqlite: name: SQLite Examples runs-on: ubuntu-latest needs: sqlx-cli timeout-minutes: 30 steps: - name: Get SQLx-CLI uses: actions/download-artifact@v4 with: name: sqlx-cli path: /home/runner/.local/bin - run: | ls -R /home/runner/.local/bin chmod +x /home/runner/.local/bin/sqlx echo /home/runner/.local/bin >> $GITHUB_PATH - uses: actions/checkout@v4 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install - uses: Swatinem/rust-cache@v2 - name: TODOs (Setup) env: DATABASE_URL: sqlite://todos.sqlite run: sqlx db setup --source=examples/sqlite/todos/migrations - name: Todos (Prepare) if: ${{ matrix.offline }} env: DATABASE_URL: sqlite://todos.sqlite run: cargo sqlx prepare -- -p sqlx-example-sqlite-todos - name: Todos (Check Offline) if: ${{ matrix.offline }} run: | cargo clean -p sqlx-example-sqlite-todos cargo check -p sqlx-example-sqlite-todos - name: Todos (Prepare from .env) if: ${{ matrix.offline }} run: | echo "DATABASE_URL=sqlite://todos.sqlite" > .env cargo clean -p sqlx-example-sqlite-todos cargo sqlx prepare -- -p sqlx-example-sqlite-todos rm .env - name: TODOs (Run) env: DATABASE_URL: sqlite://todos.sqlite SQLX_OFFLINE: ${{ matrix.offline == 'offline' }} run: cargo run -p sqlx-example-sqlite-todos ================================================ FILE: .github/workflows/sqlx-cli.yml ================================================ name: SQLx CLI on: pull_request: push: branches: - main - "*-dev" jobs: check: name: Check runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Setup Rust run: | rustup show active-toolchain || rustup toolchain install rustup component add clippy rustup toolchain install beta rustup component add --toolchain beta clippy - uses: Swatinem/rust-cache@v2 - run: cargo clippy --manifest-path sqlx-cli/Cargo.toml -- -D warnings # Run beta for new warnings but don't break the build. # Use a subdirectory of `target` to avoid clobbering the cache. - run: > cargo +beta clippy --manifest-path sqlx-cli/Cargo.toml --target-dir target/beta/ integration-test: name: Integration Test runs-on: ${{ matrix.os }} strategy: matrix: # Note: macOS-latest uses M1 Silicon (ARM64) os: - ubuntu-latest # FIXME: migrations tests fail on Windows for whatever reason # - windows-latest - macOS-15-intel - macOS-latest timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install - uses: Swatinem/rust-cache@v2 - run: cargo test --manifest-path sqlx-cli/Cargo.toml test-mysql: name: Functional Test (MySQL) runs-on: ubuntu-latest # Deliberately not using `tests/docker-compose.yml` because that sets up the database automatically. services: mysql: image: mysql:8 ports: - 3306:3306 env: MYSQL_ROOT_PASSWORD: password env: BASE_URL: mysql://root:password@localhost timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install - uses: Swatinem/rust-cache@v2 - name: Install SQLx-CLI run: cargo install --locked --debug --path sqlx-cli - name: Basic Test env: DATABASE_URL: ${{ env.BASE_URL }}/test1 run: | sqlx db setup --source=tests/mysql/migrations sqlx mig info --source=tests/mysql/migrations sqlx db drop -y - name: Test .env run: | echo "DATABASE_URL=${{ env.BASE_URL }}/test2" > .env sqlx db setup --source=tests/mysql/migrations sqlx mig info --source=tests/mysql/migrations sqlx db drop -y - name: Test --no-dotenv run: | # Allow subcommands to fail set +e echo "DATABASE_URL=${{ env.BASE_URL }}/test3" > .env ERROR=$(sqlx db setup --no-dotenv --source=tests/mysql/migrations) if [[ "$ERROR" == *"--database-url"* ]]; then exit 0 else echo "Unexpected error from sqlx-cli: $ERROR" exit 1 fi - name: Test Reversible Migrations env: DATABASE_URL: ${{ env.BASE_URL }}/test4 run: | sqlx db setup --source=tests/mysql/migrations_reversible INFO_BEFORE=$(sqlx mig info --source=tests/mysql/migrations_reversible) sqlx mig revert --target-version=0 --source=tests/mysql/migrations_reversible INFO_AFTER=$(sqlx mig info --source=tests/mysql/migrations_reversible) if [[ "$INFO_BEFORE" == "$INFO_AFTER" ]]; then echo "Error: migration info is identical before and after migrating: $INFO_BEFORE" exit 1 fi test-postgres: name: Functional Test (PostgreSQL) runs-on: ubuntu-latest # Deliberately not using `tests/docker-compose.yml` because that sets up the database automatically. services: mysql: image: postgres:17 ports: - 5432:5432 env: POSTGRES_PASSWORD: password env: BASE_URL: postgres://postgres:password@localhost timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install - uses: Swatinem/rust-cache@v2 - name: Install SQLx-CLI run: cargo install --locked --debug --path sqlx-cli - name: Basic Test env: DATABASE_URL: ${{ env.BASE_URL }}/test1 run: | sqlx db setup --source=tests/postgres/migrations sqlx mig info --source=tests/postgres/migrations sqlx db drop -y - name: Test .env run: | echo "DATABASE_URL=${{ env.BASE_URL }}/test2" > .env sqlx db setup --source=tests/postgres/migrations sqlx mig info --source=tests/postgres/migrations sqlx db drop -y - name: Test --no-dotenv run: | # Allow subcommands to fail set +e echo "DATABASE_URL=${{ env.BASE_URL }}/test3" > .env ERROR=$(sqlx db setup --no-dotenv --source=tests/postgres/migrations) if [[ "$ERROR" == *"--database-url"* ]]; then exit 0 else echo "Unexpected error from sqlx-cli: $ERROR" exit 1 fi - name: Test Reversible Migrations env: DATABASE_URL: ${{ env.BASE_URL }}/test4 run: | sqlx db setup --source=tests/postgres/migrations_reversible INFO_BEFORE=$(sqlx mig info --source=tests/postgres/migrations_reversible) sqlx mig revert --target-version=0 --source=tests/postgres/migrations_reversible INFO_AFTER=$(sqlx mig info --source=tests/postgres/migrations_reversible) if [[ "$INFO_BEFORE" == "$INFO_AFTER" ]]; then echo "Error: migration info is identical before and after migrating: $INFO_BEFORE" exit 1 fi test-sqlite: name: Functional Test (SQLite) runs-on: ubuntu-latest env: BASE_URL: sqlite://. timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install - uses: Swatinem/rust-cache@v2 - name: Install SQLx-CLI run: cargo install --locked --debug --path sqlx-cli - name: Basic Test env: DATABASE_URL: ${{ env.BASE_URL }}/test1 run: | sqlx db setup --source=tests/sqlite/migrations sqlx mig info --source=tests/sqlite/migrations sqlx db drop -y - name: Test .env run: | echo "DATABASE_URL=${{ env.BASE_URL }}/test2" > .env sqlx db setup --source=tests/sqlite/migrations sqlx mig info --source=tests/sqlite/migrations sqlx db drop -y - name: Test --no-dotenv run: | # Allow subcommands to fail set +e echo "DATABASE_URL=${{ env.BASE_URL }}/test3" > .env ERROR=$(sqlx db setup --no-dotenv --source=tests/sqlite/migrations) if [[ "$ERROR" == *"--database-url"* ]]; then exit 0 else echo "Unexpected error from sqlx-cli: $ERROR" exit 1 fi - name: Test Reversible Migrations env: DATABASE_URL: ${{ env.BASE_URL }}/test4 run: | sqlx db setup --source=tests/sqlite/migrations_reversible INFO_BEFORE=$(sqlx mig info --source=tests/sqlite/migrations_reversible) sqlx mig revert --target-version=0 --source=tests/sqlite/migrations_reversible INFO_AFTER=$(sqlx mig info --source=tests/sqlite/migrations_reversible) if [[ "$INFO_BEFORE" == "$INFO_AFTER" ]]; then echo "Error: migration info is identical before and after migrating: $INFO_BEFORE" exit 1 fi build: name: Build runs-on: ${{ matrix.os }} strategy: matrix: # Note: macOS-latest uses M1 Silicon (ARM64) os: - ubuntu-latest - windows-latest - macOS-15-intel - macOS-latest include: - os: ubuntu-latest target: x86_64-unknown-linux-musl args: --features openssl-vendored bin: target/debug/cargo-sqlx - os: windows-latest target: x86_64-pc-windows-msvc bin: target/debug/cargo-sqlx.exe - os: macOS-15-intel target: x86_64-apple-darwin bin: target/debug/cargo-sqlx - os: macOS-latest target: aarch64-apple-darwin bin: target/debug/cargo-sqlx timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Setup Rust run: | rustup show active-toolchain || rustup toolchain install rustup override set stable - uses: Swatinem/rust-cache@v2 - run: cargo build --manifest-path sqlx-cli/Cargo.toml --bin cargo-sqlx ${{ matrix.args }} - uses: actions/upload-artifact@v4 with: name: cargo-sqlx-${{ matrix.target }} path: ${{ matrix.bin }} ================================================ FILE: .github/workflows/sqlx.yml ================================================ name: SQLx on: pull_request: push: branches: - main - "*-dev" jobs: format: name: Format runs-on: ubuntu-24.04 timeout-minutes: 15 steps: - uses: actions/checkout@v4 - run: rustup component add rustfmt - run: cargo fmt --all -- --check check: name: Check runs-on: ubuntu-24.04 strategy: matrix: # Note: because `async-std` is deprecated, we only check it in a single job to save CI time. runtime: [ async-std, async-global-executor, smol, tokio ] tls: [ native-tls, rustls, none ] timeout-minutes: 30 steps: - uses: actions/checkout@v4 # Swatinem/rust-cache recommends setting up the rust toolchain first because it's used in cache keys - name: Setup Rust # https://blog.rust-lang.org/2025/03/02/Rustup-1.28.0.html run: | rustup show active-toolchain || rustup toolchain install rustup component add clippy rustup toolchain install beta rustup component add --toolchain beta clippy - uses: Swatinem/rust-cache@v2 - run: > cargo clippy --no-default-features --features all-databases,_unstable-all-types,sqlite-preupdate-hook,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }},macros -- -D warnings # Run beta for new warnings but don't break the build. # Use a subdirectory of `target` to avoid clobbering the cache. - run: > cargo +beta clippy --no-default-features --features all-databases,_unstable-all-types,sqlite-preupdate-hook,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }},macros --target-dir target/beta/ check-minimal-versions: name: Check build using minimal versions runs-on: ubuntu-24.04 timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Setup Rust run: | rustup show active-toolchain || rustup toolchain install rustup toolchain install nightly - run: cargo +nightly generate-lockfile -Z minimal-versions - run: cargo build --all-features test: name: Unit Tests runs-on: ubuntu-24.04 timeout-minutes: 30 steps: - uses: actions/checkout@v4 # https://blog.rust-lang.org/2025/03/02/Rustup-1.28.0.html - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install - uses: Swatinem/rust-cache@v2 - name: Test sqlx-core run: > cargo test -p sqlx-core --all-features - name: Test sqlx-mysql run: > cargo test -p sqlx-mysql --all-features - name: Test sqlx-postgres run: > cargo test -p sqlx-postgres --all-features - name: Test sqlx-sqlite run: > cargo test -p sqlx-sqlite --all-features - name: Test sqlx-macros-core run: > cargo test -p sqlx-macros-core --all-features # Note: use `--lib` to not run integration tests that require a DB - name: Test sqlx run: > cargo test -p sqlx --lib --all-features sqlite: name: SQLite runs-on: ubuntu-24.04 strategy: matrix: runtime: [ async-global-executor, smol, tokio ] linking: [ sqlite, sqlite-unbundled ] needs: check timeout-minutes: 30 steps: - uses: actions/checkout@v4 - run: mkdir /tmp/sqlite3-lib && wget -O /tmp/sqlite3-lib/ipaddr.so https://github.com/nalgeon/sqlean/releases/download/0.15.2/ipaddr.so # https://blog.rust-lang.org/2025/03/02/Rustup-1.28.0.html - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install - uses: Swatinem/rust-cache@v2 - name: Install system sqlite library if: ${{ matrix.linking == 'sqlite-unbundled' }} run: sudo apt-get install -y libsqlite3-dev - run: echo "using ${DATABASE_URL}" # Create data dir for offline mode - run: mkdir .sqlx - run: > cargo test --no-default-features --features any,macros,migrate,${{ matrix.linking }},_unstable-all-types,runtime-${{ matrix.runtime }},${{ matrix.linking == 'sqlite' && 'sqlite-preupdate-hook' || ''}} -- --test-threads=1 env: DATABASE_URL: sqlite:tests/sqlite/sqlite.db SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: --cfg sqlite_ipaddr --cfg sqlite_test_sqlcipher LD_LIBRARY_PATH: /tmp/sqlite3-lib # Run the `test-attr` test again to cover cleanup. # The `sqlite-test-attr` test requires the `sqlite` feature. - if: ${{ matrix.linking == 'sqlite' }} run: > cargo test --test sqlite-test-attr --no-default-features --features any,macros,migrate,${{ matrix.linking }},_unstable-all-types,runtime-${{ matrix.runtime }},${{ matrix.linking == 'sqlite' && 'sqlite-preupdate-hook' || ''}} -- --test-threads=1 env: DATABASE_URL: sqlite:tests/sqlite/sqlite.db SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: --cfg sqlite_ipaddr --cfg sqlite_test_sqlcipher LD_LIBRARY_PATH: /tmp/sqlite3-lib # Remove test artifacts - run: cargo clean -p sqlx # Build the macros-test in offline mode (omit DATABASE_URL) - run: > cargo build --no-default-features --test sqlite-macros --features any,macros,${{ matrix.linking }},_unstable-all-types,runtime-${{ matrix.runtime }} env: SQLX_OFFLINE: true SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: -D warnings --cfg sqlite_ipaddr LD_LIBRARY_PATH: /tmp/sqlite3-lib # Test macros in offline mode (still needs DATABASE_URL to run) - run: > cargo test --no-default-features --test sqlite-macros --features any,macros,${{ matrix.linking }},_unstable-all-types,runtime-${{ matrix.runtime }} env: DATABASE_URL: sqlite://tests/sqlite/sqlite.db SQLX_OFFLINE: true SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: --cfg sqlite_ipaddr LD_LIBRARY_PATH: /tmp/sqlite3-lib postgres: name: Postgres runs-on: ubuntu-24.04 strategy: matrix: postgres: [ 17, 13 ] runtime: [ async-global-executor, smol, tokio ] tls: [ native-tls, rustls-aws-lc-rs, rustls-ring, none ] needs: check timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install - uses: Swatinem/rust-cache@v2 - env: RUSTFLAGS: -D warnings --cfg postgres="${{ matrix.postgres }}" run: > cargo build --no-default-features --features postgres,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }},macros,migrate - run: | docker compose -f tests/docker-compose.yml run -d -p 5432:5432 --name postgres_${{ matrix.postgres }} postgres_${{ matrix.postgres }} - run: | docker exec postgres_${{ matrix.postgres }} bash -c "until pg_isready; do sleep 1; done" # Create data dir for offline mode - run: mkdir .sqlx - run: > cargo test --no-default-features --features any,postgres,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: -D warnings --cfg postgres="${{ matrix.postgres }}" # Run the `test-attr` test again to cover cleanup. - run: > cargo test --test postgres-test-attr --no-default-features --features any,postgres,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: -D warnings --cfg postgres="${{ matrix.postgres }}" - if: matrix.tls != 'none' run: > cargo test --no-default-features --features any,postgres,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: -D warnings --cfg postgres="${{ matrix.postgres }}" # Remove test artifacts - run: cargo clean -p sqlx # Build the macros-test in offline mode (omit DATABASE_URL) - run: > cargo build --no-default-features --test postgres-macros --features any,postgres,macros,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: SQLX_OFFLINE: true SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: -D warnings --cfg postgres="${{ matrix.postgres }}" # Test macros in offline mode (still needs DATABASE_URL to run) - run: > cargo test --no-default-features --test postgres-macros --features any,postgres,macros,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx SQLX_OFFLINE: true SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: -D warnings --cfg postgres="${{ matrix.postgres }}" postgres-ssl-auth: name: Postgres SSL Auth runs-on: ubuntu-24.04 strategy: matrix: postgres: [ 13, 17 ] runtime: [ async-std, tokio ] tls: [ native-tls, rustls-aws-lc-rs, rustls-ring ] needs: check timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install - uses: Swatinem/rust-cache@v2 - run: | docker compose -f tests/docker-compose.yml run -d -p 5432:5432 --name postgres_${{ matrix.postgres }}_client_ssl postgres_${{ matrix.postgres }}_client_ssl - run: | docker exec postgres_${{ matrix.postgres }}_client_ssl bash -c "until pg_isready; do sleep 1; done" - run: > cargo test --no-default-features --features any,postgres,macros,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: postgres://postgres@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt&sslkey=.%2Ftests%2Fcerts%2Fkeys%2Fclient.key&sslcert=.%2Ftests%2Fcerts%2Fclient.crt RUSTFLAGS: -D warnings --cfg postgres="${{ matrix.postgres }}" mysql: name: MySQL runs-on: ubuntu-24.04 strategy: matrix: mysql: [ 8 ] runtime: [ async-global-executor, smol, tokio ] tls: [ native-tls, rustls-aws-lc-rs, rustls-ring, none ] needs: check timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install - uses: Swatinem/rust-cache@v2 - run: cargo build --features mysql,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} - run: docker compose -f tests/docker-compose.yml run -d -p 3306:3306 --name mysql_${{ matrix.mysql }} mysql_${{ matrix.mysql }} - run: sleep 60 # Create data dir for offline mode - run: mkdir .sqlx - run: > cargo test --no-default-features --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: mysql://root:password@localhost:3306/sqlx?ssl-mode=disabled SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: --cfg mysql_${{ matrix.mysql }} # Run the `test-attr` test again to cover cleanup. - run: > cargo test --test mysql-test-attr --no-default-features --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: mysql://root:password@localhost:3306/sqlx?ssl-mode=disabled SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: --cfg mysql_${{ matrix.mysql }} # MySQL 5.7 supports TLS but not TLSv1.3 as required by RusTLS. - if: ${{ !(matrix.mysql == '5_7' && matrix.tls == 'rustls') }} run: > cargo test --no-default-features --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: mysql://root:password@localhost:3306/sqlx SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: --cfg mysql_${{ matrix.mysql }} # Remove test artifacts - run: cargo clean -p sqlx # Build the macros-test in offline mode (omit DATABASE_URL) - run: > cargo build --no-default-features --test mysql-macros --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: SQLX_OFFLINE: true SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: -D warnings --cfg mysql_${{ matrix.mysql }} # Test macros in offline mode (still needs DATABASE_URL to run) # MySQL 5.7 supports TLS but not TLSv1.3 as required by RusTLS. - run: > cargo test --no-default-features --test mysql-macros --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: mysql://root:password@localhost:3306/sqlx SQLX_OFFLINE: true SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: --cfg mysql_${{ matrix.mysql }} # client SSL authentication - if: ${{ matrix.tls != 'none' }} run: | docker stop mysql_${{ matrix.mysql }} docker compose -f tests/docker-compose.yml run -d -p 3306:3306 --name mysql_${{ matrix.mysql }}_client_ssl mysql_${{ matrix.mysql }}_client_ssl sleep 60 - if: ${{ matrix.tls != 'none' }} run: > cargo test --no-default-features --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: mysql://root@localhost:3306/sqlx?sslmode=verify_ca&ssl-ca=.%2Ftests%2Fcerts%2Fca.crt&ssl-key=.%2Ftests%2Fcerts%2Fkeys%2Fclient.key&ssl-cert=.%2Ftests%2Fcerts%2Fclient.crt RUSTFLAGS: --cfg mysql_${{ matrix.mysql }} mariadb: name: MariaDB runs-on: ubuntu-24.04 strategy: matrix: mariadb: [ verylatest, 11_8, 11_4, 10_11, 10_6 ] runtime: [ async-global-executor, smol, tokio ] tls: [ native-tls, rustls-aws-lc-rs, rustls-ring, none ] needs: check timeout-minutes: 30 steps: - uses: actions/checkout@v4 - name: Setup Rust run: rustup show active-toolchain || rustup toolchain install - uses: Swatinem/rust-cache@v2 - run: cargo build --features mysql,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} - run: docker compose -f tests/docker-compose.yml run -d -p 3306:3306 --name mariadb_${{ matrix.mariadb }} mariadb_${{ matrix.mariadb }} - run: sleep 30 # Create data dir for offline mode - run: mkdir .sqlx - run: > cargo test --no-default-features --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: mysql://root:password@localhost:3306/sqlx SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: --cfg mariadb="${{ matrix.mariadb }}" # Run the `test-attr` test again to cover cleanup. - run: > cargo test --test mysql-test-attr --no-default-features --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: mysql://root:password@localhost:3306/sqlx SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: --cfg mariadb="${{ matrix.mariadb }}" # Remove test artifacts - run: cargo clean -p sqlx # Build the macros-test in offline mode (omit DATABASE_URL) - run: > cargo build --no-default-features --test mysql-macros --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: SQLX_OFFLINE: true SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: -D warnings --cfg mariadb="${{ matrix.mariadb }}" # Test macros in offline mode (still needs DATABASE_URL to run) - run: > cargo test --no-default-features --test mysql-macros --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: mysql://root:password@localhost:3306/sqlx SQLX_OFFLINE: true SQLX_OFFLINE_DIR: .sqlx RUSTFLAGS: --cfg mariadb="${{ matrix.mariadb }}" # client SSL authentication - if: ${{ matrix.tls != 'none' }} run: | docker stop mariadb_${{ matrix.mariadb }} docker compose -f tests/docker-compose.yml run -d -p 3306:3306 --name mariadb_${{ matrix.mariadb }}_client_ssl mariadb_${{ matrix.mariadb }}_client_ssl sleep 60 - if: ${{ matrix.tls != 'none' }} run: > cargo test --no-default-features --features any,mysql,macros,migrate,_unstable-all-types,runtime-${{ matrix.runtime }},tls-${{ matrix.tls }} env: DATABASE_URL: mysql://root@localhost:3306/sqlx?sslmode=verify_ca&ssl-ca=.%2Ftests%2Fcerts%2Fca.crt&ssl-key=.%2Ftests%2Fcerts%2Fkeys%2Fclient.key&ssl-cert=.%2Ftests%2Fcerts%2Fclient.crt RUSTFLAGS: --cfg mariadb="${{ matrix.mariadb }}" ================================================ FILE: .gitignore ================================================ # Built artifacts target/ # Project and editor files .vscode/ .idea/ *.vim *.vi # Environment .env # Shared-memory and WAL files created by SQLite. *-shm *-wal # Integration testing extension library for SQLite. ipaddr.dylib ipaddr.so # Temporary files from running the tests locally like they would be run from CI .sqlx ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## 0.9.0-alpha.1 - 2025-10-14 Accumulated changes since the beginning of the alpha cycle. Effectively a draft CHANGELOG for the 0.9.0 release. This section will be replaced in subsequent alpha releases. See the Git history of this file for previous alphas. ### Breaking * [[#3383]]: feat: create `sqlx.toml` format [[@abonander]] * SQLx and `sqlx-cli` now support per-crate configuration files (`sqlx.toml`) * New functionality includes, but is not limited to: * Rename `DATABASE_URL` for a crate (for multi-database workspaces) * Set global type overrides for the macros (supporting custom types) * Rename or relocate the `_sqlx_migrations` table (for multiple crates using the same database) * Set characters to ignore when hashing migrations (e.g. ignore whitespace) * More to be implemented in future releases. * Enable feature `sqlx-toml` to use. * `sqlx-cli` has it enabled by default, but `sqlx` does **not**. * Default features of library crates can be hard to completely turn off because of [feature unification], so it's better to keep the default feature set as limited as possible. [This is something we learned the hard way.][preferred-crates] * Guide: see `sqlx::_config` module in documentation. * Reference: [[Link](sqlx-core/src/config/reference.toml)] * Examples (written for Postgres but can be adapted to other databases; PRs welcome!): * Multiple databases using `DATABASE_URL` renaming and global type overrides: [[Link](examples/postgres/multi-database)] * Multi-tenant database using `_sqlx_migrations` renaming and multiple schemas: [[Link](examples/postgres/multi-tenant)] * Force use of `chrono` when `time` is enabled (e.g. when using `tower-sessions-sqlx-store`): [[Link][preferred-crates]] * Forcing `bigdecimal` when `rust_decimal` is enabled is also shown, but problems with `chrono`/`time` are more common. * **Breaking changes**: * Significant changes to the `Migrate` trait * `sqlx::migrate::resolve_blocking()` is now `#[doc(hidden)]` and thus SemVer-exempt. * [[#3486]]: fix(logs): Correct spelling of aquired_after_secs tracing field [[@iamjpotts]] * Breaking behavior change: implementations parsing `tracing` logs from SQLx will need to update the spelling. * [[#3495]]: feat(postgres): remove lifetime from `PgAdvisoryLockGuard` [[@bonsairobo]] * [[#3526]]: Return &mut Self from the migrator set_ methods [[@nipunn1313]] * Minor breaking change: `Migrator::set_ignore_missing` and `set_locking` now return `&mut Self` instead of `&Self` which may break code in rare circumstances. * [[#3541]]: Postgres: force generic plan for better nullability inference. [[@joeydewaal]] * Breaking change: may alter the output of the `query!()` macros for certain queries in Postgres. * [[#3613]]: fix: `RawSql` lifetime issues [[@abonander]] * Breaking change: adds `DB` type parameter to all methods of `RawSql` * [[#3670]]: Bump ipnetwork to v0.21.1 [[@BeauGieskens]] * [[#3674]]: Implement `Decode`, `Encode` and `Type` for `Box`, `Arc`, `Cow` and `Rc` [[@joeydewaal]] * Breaking change: `impl Decode for Cow` now always decodes `Cow::Owned`, lifetime is unlinked * See this discussion for motivation: https://github.com/launchbadge/sqlx/pull/3674#discussion_r2008611502 * [[#3723]]: Add SqlStr [[@joeydewaal]] * Breaking change: all `query*()` functions now take `impl SqlSafeStr` which is only implemented for `&'static str` and `AssertSqlSafe`. For all others, wrap in `AssertSqlSafe()`. * This, along with [[#3960]], finally allows returning owned queries as the type will be `Query<'static, DB>`. * `SqlSafeStr` trait is deliberately similar to `std::panic::UnwindSafe`, serving as a speedbump to warn users about naïvely building queries with `format!()` while allowing a workaround for advanced usage that is easy to spot on code review. * [[#3800]]: Escape PostgreSQL Options [[@V02460]] * Breaking behavior change: options passed to `PgConnectOptions::options()` are now automatically escaped. Manual escaping of options is no longer necessary and may cause incorrect behavior. * [[#3821]]: Groundwork for 0.9.0-alpha.1 [[@abonander]] * Increased MSRV to 1.86 and set rust-version * Deleted deprecated combination runtime+TLS features (e.g. `runtime-tokio-native-tls`) * Deleted re-export of unstable `TransactionManager` trait in `sqlx`. * Not technically a breaking change because it's `#[doc(hidden)]`, but [it _will_ break SeaORM][seaorm-2600] if not proactively fixed. * [[#3924]]: breaking(mysql): assume all non-binary collations compatible with `str` [[@abonander]] * Text (or text-like) columns which previously were inferred to be `Vec` will be inferred to be `String` (this should ultimately fix more code than it breaks). * `SET NAMES utf8mb4 COLLATE utf8_general_ci` is no longer sent by default; instead, `SET NAMES utf8mb4` is sent to allow the server to select the appropriate default collation (since this is version- and configuration-dependent). * `MySqlConnectOptions::charset()` and `::collation()` now imply `::set_names(true)` because they don't do anything otherwise. * Setting `charset` doesn't change what's sent in the `Protocol::HandshakeResponse41` packet as that normally only matters for error messages before `SET NAMES` is sent. The default collation if `set_names = false` is `utf8mb4_general_ci`. * See [this comment](https://github.com/launchbadge/sqlx/blob/388c424f486bf20542a8a37d296dbcf86bb6dffd/sqlx-mysql/src/collation.rs#L1-L37) for details. * Incidental breaking change: `RawSql::fetch_optional()` now returns `sqlx::Result>` instead of `sqlx::Result`. Whoops. * [[#3928]]: breaking(sqlite): `libsqlite3-sys` versioning, feature flags, safety changes [[@abonander]] * SemVer policy changes: `libsqlite3-sys` version is now specified using a range. The maximum of the range may now be increased in any backwards-compatible release. The minimum of the range may only be increased in major releases. If you have `libsqlite3-sys` in your dependencies, Cargo should choose a compatible version automatically. If otherwise unconstrained, Cargo should choose the latest version supported. * SQLite extension loading (including through the new `sqlx-toml` feature) is now `unsafe`. * Added new **non-default** features corresponding to conditionally compiled SQLite APIs: * `sqlite-deserialize` enabling `SqliteConnection::serialize()` and `SqliteConnection::deserialize()` * `sqlite-load-extension` enabling `SqliteConnectOptions::extension()` and `::extension_with_entrypoint()` * `sqlite-unlock-notify` enables internal use of `sqlite3_unlock_notify()` * `SqliteValue` and `SqliteValueRef` changes: * The [`sqlite3_value*` interface](https://www.sqlite.org/c3ref/value_blob.html) reserves the right to be stateful. Without protection, any call could theoretically invalidate values previously returned, leading to dangling pointers. * `SqliteValue` is now `!Sync` and `SqliteValueRef` is `!Send` to prevent data races from concurrent accesses. * Instead, clone or wrap the `SqliteValue` in `Mutex`, or convert the `SqliteValueRef` to an owned value. * `SqliteValue` and any derived `SqliteValueRef`s now internally track if that value has been used to decode a borrowed `&[u8]` or `&str` and errors if it's used to decode any other type. * This is not expected to affect the vast majority of usages, which should only decode a single type per `SqliteValue`/`SqliteValueRef`. * See new docs on `SqliteValue` for details. * [[#3949]]: Postgres: move `PgLTree::from` to `From>` implementation [[@JerryQ17]] * [[#3957]]: refactor(sqlite): do not borrow bound values, delete lifetime on `SqliteArguments` [[@iamjpotts]] * [[#3958]]: refactor(any): Remove lifetime parameter from AnyArguments [[@iamjpotts]] * [[#3960]]: refactor(core): Remove lifetime parameter from Arguments trait [[@iamjpotts]] * [[#4008]]: make `#[derive(sqlx::Type)]` automatically generate `impl PgHasArrayType` by default for newtype structs [[@papaj-na-wrotkach]] * Manual implementations of PgHasArrayType for newtypes will conflict with the generated one. Delete the manual impl or add `#[sqlx(no_pg_array)]` where conflicts occur. ### Added * [[#3641]]: feat(Postgres): support nested domain types [[@joeydewaal]] * [[#3651]]: Add PgBindIter for encoding and use it as the implementation encoding &[T] [[@tylerhawkes]] * [[#3675]]: feat: implement Encode, Decode, Type for `Arc` and `Arc<[u8]>` (and `Rc` equivalents) [[@joeydewaal]] * [[#3791]]: Smol+async global executor 1.80 dev [[@martin-kolarik]] * Adds `runtime-smol` and `runtime-async-global-executor` features to replace usages of the deprecated `async-std` crate. * [[#3859]]: Add more JsonRawValue encode/decode impls. [[@Dirbaio]] * [[#3881]]: CLi: made cli-lib modules publicly available for other crates [[@silvestrpredko]] * [[#3889]]: Compile-time support for external drivers [[@bobozaur]] * [[#3917]]: feat(sqlx.toml): support SQLite extensions in macros and sqlx-cli [[@djarb]] * [[#3918]]: Feature: Add exclusion violation error kind [[@barskern]] * [[#3971]]: Allow single-field named structs to be transparent [[@Xiretza]] * [[#4015]]: feat(sqlite): `no_tx` migration support [[@AlexTMjugador]] * [[#4020]]: Add `Migrator::with_migrations()` constructor [[@xb284524239]] ### Changed * [[#3525]]: Remove unnecessary boxfutures [[@joeydewaal]] * [[#3867]]: sqlx-postgres: Bump etcetera to 0.10.0 [[@miniduikboot]] * [[#3709]]: chore: replace once_cell `OnceCell`/`Lazy` with std `OnceLock`/`LazyLock` [[@paolobarbolini]] * [[#3890]]: feat: Unify `Debug` implementations across `PgRow`, `MySqlRow` and `SqliteRow` [[@davidcornu]] * [[#3911]]: chore: upgrade async-io to v2.4.1 [[@zebrapurring]] * [[#3938]]: Move `QueryLogger` back [[@joeydewaal]] * [[#3956]]: chore(sqlite): Remove unused test of removed git2 feature [[@iamjpotts]] * [[#3962]]: Give SQLX_OFFLINE_DIR from environment precedence in macros [[@psionic-k]] * [[#3968]]: chore(ci): Add timeouts to ci jobs [[@iamjpotts]] * [[#4002]]: sqlx-postgres(tests): cleanup 2 unit tests. [[@joeydewaal]] * [[#4022]]: refactor: tweaks after #3791 [[@abonander]] ### Fixed * [[#3840]]: Fix docs.rs build of sqlx-sqlite [[@gferon]] * [[#3848]]: fix(macros): don't mutate environment variables [[@joeydewaal]] * [[#3856]]: fix(macros): slightly improve unsupported type error message [[@dyc3]] * [[#3857]]: fix(mysql): validate parameter count for prepared statements [[@cvzx]] * [[#3861]]: Fix NoHostnameTlsVerifier for rustls 0.23.24 and above [[@elichai]] * [[#3863]]: Use unnamed statement in pg when not persistent [[@ThomWright]] * [[#3874]]: Further reduce dependency on `futures` and `futures-util` [[@paolobarbolini]] * [[#3886]]: fix: use Executor::fetch in QueryAs::fetch [[@bobozaur]] * [[#3910]]: feat(ok): add correct handling of ok packets in MYSQL implementation [[@0xfourzerofour]] * [[#3914]]: fix: regenerate test certificates [[@abonander]] * [[#3915]]: fix: spec_error is used by try_from derive [[@saiintbrisson]] * [[#3919]]: fix[sqlx-postgres]: do a checked_mul to prevent panic'ing [[@nhatcher-frequenz]] * [[#3923]]: sqlx-mysql: Fix bug in cleanup test db's. [[@joeydewaal]] * [[#3950]]: chore: Fix warnings for custom postgres_## cfg flags [[@iamjpotts]] * [[#3952]]: `Pool.close`: close all connections before returning [[@jpmelos]] * [[#3975]]: fix documentation for rustls native root certificates [[@2ndDerivative]] * [[#3977]]: refactor(ci): Use separate job for postgres ssl auth tests [[@iamjpotts]] * [[#3980]]: Correctly `ROLLBACK` transaction when dropped during `BEGIN`. [[@kevincox]] * [[#3981]]: SQLite: fix transaction level accounting with bad custom command. [[@kevincox]] * [[#3986]]: chore(core): Fix docstring for Query::try_bind [[@iamjpotts]] * [[#3987]]: chore(deps): Resolve deprecation warning for chrono Date and ymd methods [[@iamjpotts]] * [[#3988]]: refactor(sqlite): Resolve duplicate test target warning for macros.rs [[@iamjpotts]] * [[#3989]]: chore(deps): Set default-features=false on sqlx in workspace.dependencies [[@iamjpotts]] * [[#3991]]: fix(sqlite): regression when decoding nulls [[@abonander]] * [[#4006]]: PostgreSQL SASL – run SHA256 in a blocking executor [[@ThomWright]] * [[#4007]]: fix(compose): use OS-assigned ports for all conatiners [[@papaj-na-wrotkach]] * [[#4009]]: Drop cached db connections in macros upon hitting an error [[@swlynch99]] * [[#4024]]: fix(sqlite) Migrate revert with no-transaction [[@Dosenpfand]] * [[#4027]]: native tls handshake: build TlsConnector in blocking threadpool [[@daviduebler]] * [[#4053]]: fix(macros): smarter `.env` loading, caching, and invalidation [[@abonander]] * Additional credit to [[@AlexTMjugador]] ([[#4018]]) and [[@Diggsey]] ([[#4039]]) for their proposed solutions which served as a useful comparison. [seaorm-2600]: https://github.com/SeaQL/sea-orm/issues/2600 [feature unification]: https://doc.rust-lang.org/cargo/reference/features.html#feature-unification [preferred-crates]: examples/postgres/preferred-crates [#3821]: https://github.com/launchbadge/sqlx/pull/3821 [#3383]: https://github.com/launchbadge/sqlx/pull/3383 [#3486]: https://github.com/launchbadge/sqlx/pull/3486 [#3495]: https://github.com/launchbadge/sqlx/pull/3495 [#3525]: https://github.com/launchbadge/sqlx/pull/3525 [#3526]: https://github.com/launchbadge/sqlx/pull/3526 [#3541]: https://github.com/launchbadge/sqlx/pull/3541 [#3613]: https://github.com/launchbadge/sqlx/pull/3613 [#3641]: https://github.com/launchbadge/sqlx/pull/3641 [#3651]: https://github.com/launchbadge/sqlx/pull/3651 [#3670]: https://github.com/launchbadge/sqlx/pull/3670 [#3674]: https://github.com/launchbadge/sqlx/pull/3674 [#3675]: https://github.com/launchbadge/sqlx/pull/3675 [#3709]: https://github.com/launchbadge/sqlx/pull/3709 [#3723]: https://github.com/launchbadge/sqlx/pull/3723 [#3791]: https://github.com/launchbadge/sqlx/pull/3791 [#3800]: https://github.com/launchbadge/sqlx/pull/3800 [#3821]: https://github.com/launchbadge/sqlx/pull/3821 [#3840]: https://github.com/launchbadge/sqlx/pull/3840 [#3848]: https://github.com/launchbadge/sqlx/pull/3848 [#3856]: https://github.com/launchbadge/sqlx/pull/3856 [#3857]: https://github.com/launchbadge/sqlx/pull/3857 [#3859]: https://github.com/launchbadge/sqlx/pull/3859 [#3861]: https://github.com/launchbadge/sqlx/pull/3861 [#3863]: https://github.com/launchbadge/sqlx/pull/3863 [#3867]: https://github.com/launchbadge/sqlx/pull/3867 [#3874]: https://github.com/launchbadge/sqlx/pull/3874 [#3881]: https://github.com/launchbadge/sqlx/pull/3881 [#3886]: https://github.com/launchbadge/sqlx/pull/3886 [#3889]: https://github.com/launchbadge/sqlx/pull/3889 [#3890]: https://github.com/launchbadge/sqlx/pull/3890 [#3910]: https://github.com/launchbadge/sqlx/pull/3910 [#3911]: https://github.com/launchbadge/sqlx/pull/3911 [#3914]: https://github.com/launchbadge/sqlx/pull/3914 [#3915]: https://github.com/launchbadge/sqlx/pull/3915 [#3917]: https://github.com/launchbadge/sqlx/pull/3917 [#3918]: https://github.com/launchbadge/sqlx/pull/3918 [#3919]: https://github.com/launchbadge/sqlx/pull/3919 [#3923]: https://github.com/launchbadge/sqlx/pull/3923 [#3924]: https://github.com/launchbadge/sqlx/pull/3924 [#3928]: https://github.com/launchbadge/sqlx/pull/3928 [#3938]: https://github.com/launchbadge/sqlx/pull/3938 [#3949]: https://github.com/launchbadge/sqlx/pull/3949 [#3950]: https://github.com/launchbadge/sqlx/pull/3950 [#3952]: https://github.com/launchbadge/sqlx/pull/3952 [#3956]: https://github.com/launchbadge/sqlx/pull/3956 [#3957]: https://github.com/launchbadge/sqlx/pull/3957 [#3958]: https://github.com/launchbadge/sqlx/pull/3958 [#3960]: https://github.com/launchbadge/sqlx/pull/3960 [#3962]: https://github.com/launchbadge/sqlx/pull/3962 [#3968]: https://github.com/launchbadge/sqlx/pull/3968 [#3971]: https://github.com/launchbadge/sqlx/pull/3971 [#3975]: https://github.com/launchbadge/sqlx/pull/3975 [#3977]: https://github.com/launchbadge/sqlx/pull/3977 [#3980]: https://github.com/launchbadge/sqlx/pull/3980 [#3981]: https://github.com/launchbadge/sqlx/pull/3981 [#3986]: https://github.com/launchbadge/sqlx/pull/3986 [#3987]: https://github.com/launchbadge/sqlx/pull/3987 [#3988]: https://github.com/launchbadge/sqlx/pull/3988 [#3989]: https://github.com/launchbadge/sqlx/pull/3989 [#3991]: https://github.com/launchbadge/sqlx/pull/3991 [#4002]: https://github.com/launchbadge/sqlx/pull/4002 [#4006]: https://github.com/launchbadge/sqlx/pull/4006 [#4007]: https://github.com/launchbadge/sqlx/pull/4007 [#4008]: https://github.com/launchbadge/sqlx/pull/4008 [#4009]: https://github.com/launchbadge/sqlx/pull/4009 [#4015]: https://github.com/launchbadge/sqlx/pull/4015 [#4018]: https://github.com/launchbadge/sqlx/pull/4018 [#4020]: https://github.com/launchbadge/sqlx/pull/4020 [#4022]: https://github.com/launchbadge/sqlx/pull/4022 [#4024]: https://github.com/launchbadge/sqlx/pull/4024 [#4027]: https://github.com/launchbadge/sqlx/pull/4027 [#4039]: https://github.com/launchbadge/sqlx/pull/4039 [#4053]: https://github.com/launchbadge/sqlx/pull/4053 ## 0.8.6 - 2025-05-19 9 pull requests were merged this release cycle. ### Added * [[#3849]]: Add color and wrapping to cli help text [[@joshka]] ### Changed * [[#3830]]: build: drop unused `tempfile` dependency [[@paolobarbolini]] * [[#3845]]: chore: clean up no longer used imports [[@tisonkun]] * [[#3863]]: Use unnamed statement in pg when not persistent [[@ThomWright]] * [[#3866]]: chore(doc): clarify compile-time verification and case conversion behavior [[@duhby]] ### Fixed * [[#3840]]: Fix docs.rs build of sqlx-sqlite [[@gferon]] * [[#3848]]: fix(macros): don't mutate environment variables [[@joeydewaal]] * [[#3855]]: fix `attrubute` typo in doc [[@kujeger]] * [[#3856]]: fix(macros): slightly improve unsupported type error message [[@dyc3]] [#3830]: https://github.com/launchbadge/sqlx/pull/3830 [#3840]: https://github.com/launchbadge/sqlx/pull/3840 [#3845]: https://github.com/launchbadge/sqlx/pull/3845 [#3848]: https://github.com/launchbadge/sqlx/pull/3848 [#3849]: https://github.com/launchbadge/sqlx/pull/3849 [#3855]: https://github.com/launchbadge/sqlx/pull/3855 [#3856]: https://github.com/launchbadge/sqlx/pull/3856 [#3863]: https://github.com/launchbadge/sqlx/pull/3863 [#3866]: https://github.com/launchbadge/sqlx/pull/3866 ## 0.8.5 - 2025-04-14 Hotfix release to address two new issues: * [[#3823]]: `sqlx-cli@0.8.4` broke `.env` default resolution mechanism * [[#3825]]: `sqlx@0.8.4` broke test fixture setup The `0.8.4` release will be yanked as of publishing this one. ### Added * In release PR: `sqlx-cli` now accepts `--no-dotenv` in subcommand arguments. * In release PR: added functionality tests for `sqlx-cli` to CI. * In release PR: test `#[sqlx::test]` twice in CI to cover cleanup. ### Fixed * In release PR: `sqlx-cli` correctly reads `.env` files by default again. * Addresses [[#3823]]. * In release PR: fix bugs in MySQL implementation of `#[sqlx::test]`. * Addresses [[#3825]]. [#3823]: https://github.com/launchbadge/sqlx/issues/3823 [#3825]: https://github.com/launchbadge/sqlx/issues/3825 ## 0.8.4 - 2025-04-13 50 pull requests were merged this release cycle. ### Added * [[#3603]]: Added missing special casing for encoding embedded arrays of custom types [[@nico-incubiq]] * [[#3625]]: feat(sqlite): add preupdate hook [[@aschey]] * [[#3655]]: docs: add example for postgres enums with type TEXT [[@tisonkun]] * [[#3677]]: Add json(nullable) macro attribute [[@seanaye]] * [[#3687]]: Derive clone and debug for postgresql arguments [[@remysaissy]] * [[#3690]]: feat: add postres geometry line segment [[@jayy-lmao]] * [[#3707]]: feat(Sqlite): add LockedSqliteHandle::last_error [[@joeydewaal]] * [[#3710]]: feat: add ipnet support [[@BeauGieskens]] * [[#3711]]: feat(postgres): add geometry box [[@jayy-lmao]] * [[#3714]]: chore: expose bstr feature [[@joeydewaal]] * [[#3716]]: feat(postgres): add geometry path [[@jayy-lmao]] * [[#3724]]: feat(sqlx-cli): Add flag to disable automatic loading of .env files [[@benwilber]] * [[#3734]]: QueryBuilder: add debug_assert when `push_values` is passed an empty set of tuples [[@chanmaoganda]] * [[#3745]]: feat: sqlx sqlite expose de/serialize [[@mattrighetti]] * [[#3765]]: Merge of #3427 (by @mpyw) and #3614 (by @bonsairobo) [[@abonander]] * [[#3427]] Expose `transaction_depth` through `get_transaction_depth()` method [[@mpyw]] * Changed to `Connection::is_in_transaction` in [[#3765]] * [[#3614]] Add `begin_with` methods to support database-specific transaction options [[@bonsairobo]] * [[#3769]]: feat(postgres): add geometry polygon [[@jayy-lmao]] * [[#3773]]: feat(postgres): add geometry circle [[@jayy-lmao]] ### Changed * [[#3665]]: build(deps): bump semver compatible dependencies [[@paolobarbolini]] * [[#3669]]: refactor(cli): replace promptly with dialoguer [[@paolobarbolini]] * [[#3672]]: add `#[track_caller]` to `Row::get()` [[@karambarakat]] * [[#3708]]: chore(MySql): Remove unnecessary box [[@joeydewaal]] * [[#3715]]: chore: add pg_copy regression tests [[@joeydewaal]] * [[#3721]]: Replace some `futures-core` / `futures-util` APIs with `std` variants [[@paolobarbolini]] * [[#3725]]: chore: replace rustls-pemfile with rustls-pki-types [[@tottoto]] * [[#3754]]: chore(cli): remove unused async-trait crate from dependencies [[@tottoto]] * [[#3762]]: docs(pool): recommend actix-web ThinData over Data to avoid two Arcs [[@jonasmalacofilho]] ### Fixed * [[#3289]]: Always set `SQLITE_OPEN_URI` on in-memory sqlite [[@LecrisUT]] * [[#3334]]: Fix: nextest cleanup race condition [[@bonega]] * [[#3666]]: fix(cli): running tests on 32bit platforms [[@paolobarbolini]] * [[#3686]]: fix: handle nullable values by printing NULL instead of panicking [[@joeydewaal]] * [[#3700]]: fix(Sqlite): stop sending rows after first error [[@joeydewaal]] * [[#3701]]: fix(postgres) use signed int for length prefix in `PgCopyIn` [[@joeydewaal]] * [[#3703]]: fix(Postgres) chunk pg_copy data [[@joeydewaal]] * [[#3712]]: FromRow: Fix documentation order [[@Turbo87]] * [[#3720]]: Fix readme: uuid feature is gating for all repos [[@jthacker]] * [[#3728]]: postgres: Fix tracing span when dropping PgListener [[@chitoku-k]] * [[#3741]]: Fix example calculation in docs [[@dns2utf8]] * [[#3749]]: docs: add some missing backticks [[@soulwa]] * [[#3753]]: Avoid privilege requirements by using an advisory lock in test setup (postgres). [[@kildrens]] * [[#3755]]: Fix FromRow docs for tuples [[@xvapx]] * [[#3768]]: chore(Sqlite): remove ci.db from repo [[@joeydewaal]] * [[#3771]]: fix(ci): breakage from Rustup 1.28 [[@abonander]] * [[#3786]]: Fix a copy-paste error on get_username docs [[@sulami]] * [[#3801]]: Fix: Enable Json type when db feature isn't enabled [[@thriller08]] * [[#3809]]: fix: PgConnectOptions docs [[@mbj]] * [[#3811]]: Fix error message typo in PgPoint::from_str [[@TeCHiScy]] * [[#3812]]: mysql: Fix panic on invalid text row length field [[@0xdeafbeef]] * [[#3815]]: fix(macros): cache macro metadata based on `CARGO_MANIFEST_DIR` [[@joeydewaal]] * Fixes in release PR [[#3819]] [[@abonander]]: * fix(postgres): send `limit: 0` for all `Execute` messages * Addresses [[#3673]]: Parallel workers not used on Postgres * fix: let `CertificateInput::from` infer any PEM-encoded document * Fixes `PGSSLKEY` not being parsed correctly when containing a PEM-encoded private key. * doc: improve documentation of `PgConnectOptions` * `PGHOSTADDR` now can be used to override `PGHOST`. * Addresses [[#3740]]: Document the URL syntax for Unix-domain sockets when connecting to postgres [#3819]: https://github.com/launchbadge/sqlx/pull/3819 [#3673]: https://github.com/launchbadge/sqlx/issues/3673 [#3740]: https://github.com/launchbadge/sqlx/issues/3740 [#3289]: https://github.com/launchbadge/sqlx/pull/3289 [#3334]: https://github.com/launchbadge/sqlx/pull/3334 [#3427]: https://github.com/launchbadge/sqlx/pull/3427 [#3603]: https://github.com/launchbadge/sqlx/pull/3603 [#3614]: https://github.com/launchbadge/sqlx/pull/3614 [#3625]: https://github.com/launchbadge/sqlx/pull/3625 [#3655]: https://github.com/launchbadge/sqlx/pull/3655 [#3665]: https://github.com/launchbadge/sqlx/pull/3665 [#3666]: https://github.com/launchbadge/sqlx/pull/3666 [#3669]: https://github.com/launchbadge/sqlx/pull/3669 [#3672]: https://github.com/launchbadge/sqlx/pull/3672 [#3677]: https://github.com/launchbadge/sqlx/pull/3677 [#3686]: https://github.com/launchbadge/sqlx/pull/3686 [#3687]: https://github.com/launchbadge/sqlx/pull/3687 [#3690]: https://github.com/launchbadge/sqlx/pull/3690 [#3700]: https://github.com/launchbadge/sqlx/pull/3700 [#3701]: https://github.com/launchbadge/sqlx/pull/3701 [#3703]: https://github.com/launchbadge/sqlx/pull/3703 [#3707]: https://github.com/launchbadge/sqlx/pull/3707 [#3708]: https://github.com/launchbadge/sqlx/pull/3708 [#3710]: https://github.com/launchbadge/sqlx/pull/3710 [#3711]: https://github.com/launchbadge/sqlx/pull/3711 [#3712]: https://github.com/launchbadge/sqlx/pull/3712 [#3714]: https://github.com/launchbadge/sqlx/pull/3714 [#3715]: https://github.com/launchbadge/sqlx/pull/3715 [#3716]: https://github.com/launchbadge/sqlx/pull/3716 [#3720]: https://github.com/launchbadge/sqlx/pull/3720 [#3721]: https://github.com/launchbadge/sqlx/pull/3721 [#3724]: https://github.com/launchbadge/sqlx/pull/3724 [#3725]: https://github.com/launchbadge/sqlx/pull/3725 [#3728]: https://github.com/launchbadge/sqlx/pull/3728 [#3734]: https://github.com/launchbadge/sqlx/pull/3734 [#3741]: https://github.com/launchbadge/sqlx/pull/3741 [#3745]: https://github.com/launchbadge/sqlx/pull/3745 [#3749]: https://github.com/launchbadge/sqlx/pull/3749 [#3753]: https://github.com/launchbadge/sqlx/pull/3753 [#3754]: https://github.com/launchbadge/sqlx/pull/3754 [#3755]: https://github.com/launchbadge/sqlx/pull/3755 [#3762]: https://github.com/launchbadge/sqlx/pull/3762 [#3765]: https://github.com/launchbadge/sqlx/pull/3765 [#3768]: https://github.com/launchbadge/sqlx/pull/3768 [#3769]: https://github.com/launchbadge/sqlx/pull/3769 [#3771]: https://github.com/launchbadge/sqlx/pull/3771 [#3773]: https://github.com/launchbadge/sqlx/pull/3773 [#3786]: https://github.com/launchbadge/sqlx/pull/3786 [#3801]: https://github.com/launchbadge/sqlx/pull/3801 [#3809]: https://github.com/launchbadge/sqlx/pull/3809 [#3811]: https://github.com/launchbadge/sqlx/pull/3811 [#3812]: https://github.com/launchbadge/sqlx/pull/3812 [#3815]: https://github.com/launchbadge/sqlx/pull/3815 ## 0.8.3 - 2025-01-03 41 pull requests were merged this release cycle. ### Added * [[#3418]]: parse timezone parameter in mysql connection url [[@dojiong]] * [[#3491]]: chore: Update async-std v1.13 [[@jayvdb]] * [[#3492]]: expose relation_id and relation_attribution_no on PgColumn [[@kurtbuilds]] * [[#3493]]: doc(sqlite): document behavior for zoned date-time types [[@abonander]] * [[#3500]]: Add sqlite commit and rollback hooks [[@gridbox]] * [[#3505]]: chore(mysql): create test for passwordless auth (#3484) [[@abonander]] * [[#3507]]: Add a "sqlite-unbundled" feature that dynamically links to system libsqlite3.so library [[@lilydjwg]] * [[#3508]]: doc(sqlite): show how to turn options into a pool [[@M3t0r]] * [[#3514]]: Support PgHstore by default in macros [[@joeydewaal]] * [[#3550]]: Implement Acquire for PgListener [[@sandhose]] * [[#3551]]: Support building with rustls but native certificates [[@IlyaBizyaev]] * [[#3553]]: Add support for Postgres lquery arrays [[@philipcristiano]] * [[#3560]]: Add PgListener::next_buffered(), to support batch processing of notifications [[@chanks]] * [[#3577]]: Derive Copy where possible for database-specific types [[@veigaribo]] * [[#3579]]: Reexport AnyTypeInfoKind [[@Norlock]] * [[#3580]]: doc(mysql): document difference between `Uuid` and `uuid::fmt::Hyphenated` [[@abonander]] * [[#3583]]: feat: point [[@jayy-lmao]] * [[#3608]]: Implement AnyQueryResult for Sqlite and MySQL [[@pxp9]] * [[#3623]]: feat: add geometry line [[@jayy-lmao]] * [[#3658]]: feat: add Transaction type aliases [[@joeydewaal]] ### Changed * [[#3519]]: Remove unused dependencies from sqlx-core, sqlx-cli and sqlx-postgres [[@vsuryamurthy]] * [[#3529]]: Box Pgconnection fields [[@joeydewaal]] * [[#3548]]: Demote `.pgpass` file warning to a debug message. [[@denschub]] * [[#3585]]: Eagerly reconnect in `PgListener::try_recv` [[@swlynch99]] * [[#3596]]: Bump thiserror to v2.0.0 [[@paolobarbolini]] * [[#3605]]: Use `UNION ALL` instead of `UNION` in nullable check [[@Suficio]] * [[#3629]]: chore: remove BoxFuture's (non-breaking) [[@joeydewaal]] * [[#3632]]: Bump hashlink to v0.10 [[@paolobarbolini]] * [[#3643]]: Roll PostgreSQL 11..=15 tests to 13..=17 [[@paolobarbolini]] * [[#3648]]: close listener connection on TimedOut and BrokenPipe errors [[@DXist]] * [[#3649]]: Bump hashbrown to v0.15 [[@paolobarbolini]] ### Fixed * [[#3528]]: fix: obey `no-transaction` flag in down migrations [[@manifest]] * [[#3536]]: fix: using sqlx::test macro inside macro's [[@joeydewaal]] * [[#3545]]: fix: remove `sqlformat` [[@tbar4]] * [[#3558]]: fix: fix example code of `query_as` [[@xuehaonan27]] * [[#3566]]: Fix: Cannot query Postgres `INTERVAL[]` [[@Ddystopia]] * [[#3593]]: fix: URL decode database name when parsing connection url [[@BenoitRanque]] * [[#3601]]: Remove default-features = false from url [[@hsivonen]] * [[#3604]]: Fix mistake in sqlx::test fixtures docs [[@andreweggleston]] * [[#3612]]: fix(mysql): percent-decode database name [[@abonander]] * [[#3640]]: Dont use `EXPLAIN` in nullability check for QuestDB [[@Suficio]] [#3418]: https://github.com/launchbadge/sqlx/pull/3418 [#3478]: https://github.com/launchbadge/sqlx/pull/3478 [#3491]: https://github.com/launchbadge/sqlx/pull/3491 [#3492]: https://github.com/launchbadge/sqlx/pull/3492 [#3493]: https://github.com/launchbadge/sqlx/pull/3493 [#3500]: https://github.com/launchbadge/sqlx/pull/3500 [#3505]: https://github.com/launchbadge/sqlx/pull/3505 [#3507]: https://github.com/launchbadge/sqlx/pull/3507 [#3508]: https://github.com/launchbadge/sqlx/pull/3508 [#3514]: https://github.com/launchbadge/sqlx/pull/3514 [#3519]: https://github.com/launchbadge/sqlx/pull/3519 [#3528]: https://github.com/launchbadge/sqlx/pull/3528 [#3529]: https://github.com/launchbadge/sqlx/pull/3529 [#3536]: https://github.com/launchbadge/sqlx/pull/3536 [#3545]: https://github.com/launchbadge/sqlx/pull/3545 [#3548]: https://github.com/launchbadge/sqlx/pull/3548 [#3550]: https://github.com/launchbadge/sqlx/pull/3550 [#3551]: https://github.com/launchbadge/sqlx/pull/3551 [#3553]: https://github.com/launchbadge/sqlx/pull/3553 [#3558]: https://github.com/launchbadge/sqlx/pull/3558 [#3560]: https://github.com/launchbadge/sqlx/pull/3560 [#3566]: https://github.com/launchbadge/sqlx/pull/3566 [#3577]: https://github.com/launchbadge/sqlx/pull/3577 [#3579]: https://github.com/launchbadge/sqlx/pull/3579 [#3580]: https://github.com/launchbadge/sqlx/pull/3580 [#3583]: https://github.com/launchbadge/sqlx/pull/3583 [#3585]: https://github.com/launchbadge/sqlx/pull/3585 [#3593]: https://github.com/launchbadge/sqlx/pull/3593 [#3596]: https://github.com/launchbadge/sqlx/pull/3596 [#3601]: https://github.com/launchbadge/sqlx/pull/3601 [#3604]: https://github.com/launchbadge/sqlx/pull/3604 [#3605]: https://github.com/launchbadge/sqlx/pull/3605 [#3608]: https://github.com/launchbadge/sqlx/pull/3608 [#3612]: https://github.com/launchbadge/sqlx/pull/3612 [#3623]: https://github.com/launchbadge/sqlx/pull/3623 [#3629]: https://github.com/launchbadge/sqlx/pull/3629 [#3632]: https://github.com/launchbadge/sqlx/pull/3632 [#3640]: https://github.com/launchbadge/sqlx/pull/3640 [#3643]: https://github.com/launchbadge/sqlx/pull/3643 [#3648]: https://github.com/launchbadge/sqlx/pull/3648 [#3649]: https://github.com/launchbadge/sqlx/pull/3649 [#3658]: https://github.com/launchbadge/sqlx/pull/3658 ## 0.8.2 - 2024-09-02 10 pull requests were merged this release cycle. This release addresses a few regressions that have occurred, and refines SQLx's MSRV policy (see [the FAQ](FAQ.md)). ### Added * [[#3447]]: Clarify usage of Json/Jsonb in query macros [[@Lachstec]] ### Changed * [[#3424]]: Remove deprecated feature-names from `Cargo.toml` files in examples [[@carschandler]] ### Fixed * [[#3403]]: Fix (#3395) sqlx::test macro in 0.8 [[@joeydewaal]] * [[#3411]]: fix: Use rfc3339 to decode date from text [[@pierre-wehbe]] * [[#3453]]: fix(#3445): PgHasArrayType [[@joeydewaal]] * Fixes `#[sqlx(no_pg_array)]` being forbidden on `#[derive(Type)]` structs. * [[#3454]]: fix: non snake case warning [[@joeydewaal]] * [[#3459]]: Pgsql cube type compile fail [[@kdesjard]] * [[#3465]]: fix(postgres): max number of binds is 65535, not 32767 (regression) [[@abonander]] * [[#3467]]: fix cancellation issues with `PgListener`, `PgStream::recv()` [[@abonander]] * Fixes cryptic `unknown message: "\\0"` error * [[#3474]]: Fix try_get example in README.md [[@luveti]] [#3403]: https://github.com/launchbadge/sqlx/pull/3403 [#3411]: https://github.com/launchbadge/sqlx/pull/3411 [#3424]: https://github.com/launchbadge/sqlx/pull/3424 [#3447]: https://github.com/launchbadge/sqlx/pull/3447 [#3453]: https://github.com/launchbadge/sqlx/pull/3453 [#3454]: https://github.com/launchbadge/sqlx/pull/3454 [#3455]: https://github.com/launchbadge/sqlx/pull/3455 [#3459]: https://github.com/launchbadge/sqlx/pull/3459 [#3465]: https://github.com/launchbadge/sqlx/pull/3465 [#3467]: https://github.com/launchbadge/sqlx/pull/3467 [#3474]: https://github.com/launchbadge/sqlx/pull/3474 ## 0.8.1 - 2024-08-23 16 pull requests were merged this release cycle. This release contains a fix for [RUSTSEC-2024-0363]. Postgres users are advised to upgrade ASAP as a possible exploit has been demonstrated: MySQL and SQLite do not _appear_ to be exploitable, but upgrading is recommended nonetheless. ### Added * [[#3421]]: correct spelling of `MySqlConnectOptions::no_engine_substitution()` [[@kolinfluence]] * Deprecates `MySqlConnectOptions::no_engine_subsitution()` (oops) in favor of the correctly spelled version. ### Changed * [[#3376]]: doc: hide `spec_error` module [[@abonander]] * This is a helper module for the macros and was not meant to be exposed. * It is not expected to receive any breaking changes for the 0.8.x release, but is not designed as a public API. Use at your own risk. * [[#3382]]: feat: bumped to `libsqlite3-sys=0.30.1` to support sqlite 3.46 [[@CommanderStorm]] * [[#3385]]: chore(examples):Migrated the pg-chat example to ratatui [[@CommanderStorm]] * [[#3399]]: Upgrade to rustls 0.23 [[@djc]] * RusTLS now has pluggable cryptography providers: `ring` (the existing implementation), and `aws-lc-rs` which has optional FIPS certification. * The existing features activating RusTLS (`runtime-tokio-rustls`, `runtime-async-std-rustls`, `tls-rustls`) enable the `ring` provider of RusTLS to match the existing behavior so this _should not_ be a breaking change. * Switch to the `tls-rustls-aws-lc-rs` feature to use the `aws-lc-rs` provider. * If using `runtime-tokio-rustls` or `runtime-async-std-rustls`, this will necessitate switching to the appropriate non-legacy runtime feature: `runtime-tokio` or `runtime-async-std` * See the RusTLS README for more details: ### Fixed * [[#2786]]: fix(sqlx-cli): do not clean sqlx during prepare [[@cycraig]] * [[#3354]]: sqlite: fix inconsistent read-after-write [[@ckampfe]] * [[#3371]]: Fix encoding and decoding of MySQL enums in `sqlx::Type` [[@alu]] * [[#3374]]: fix: usage of `node12` in `SQLx` action [[@hamirmahal]] * [[#3380]]: chore: replace structopt with clap in examples [[@tottoto]] * [[#3381]]: Fix CI after Rust 1.80, remove dead feature references [[@abonander]] * [[#3384]]: chore(tests): fixed deprecation warnings [[@CommanderStorm]] * [[#3386]]: fix(dependencys):bumped cargo_metadata to `v0.18.1` to avoid yanked `v0.14.3` [[@CommanderStorm]] * [[#3389]]: fix(cli): typo in error for required DB URL [[@ods]] * [[#3417]]: Update version to 0.8 in README [[@soucosmo]] * [[#3441]]: fix: audit protocol handling [[@abonander]] * This addresses [RUSTSEC-2024-0363] and includes regression tests for MySQL, Postgres and SQLite. [#2786]: https://github.com/launchbadge/sqlx/pull/2786 [#3354]: https://github.com/launchbadge/sqlx/pull/3354 [#3371]: https://github.com/launchbadge/sqlx/pull/3371 [#3374]: https://github.com/launchbadge/sqlx/pull/3374 [#3376]: https://github.com/launchbadge/sqlx/pull/3376 [#3380]: https://github.com/launchbadge/sqlx/pull/3380 [#3381]: https://github.com/launchbadge/sqlx/pull/3381 [#3382]: https://github.com/launchbadge/sqlx/pull/3382 [#3384]: https://github.com/launchbadge/sqlx/pull/3384 [#3385]: https://github.com/launchbadge/sqlx/pull/3385 [#3386]: https://github.com/launchbadge/sqlx/pull/3386 [#3389]: https://github.com/launchbadge/sqlx/pull/3389 [#3399]: https://github.com/launchbadge/sqlx/pull/3399 [#3417]: https://github.com/launchbadge/sqlx/pull/3417 [#3421]: https://github.com/launchbadge/sqlx/pull/3421 [#3441]: https://github.com/launchbadge/sqlx/pull/3441 [RUSTSEC-2024-0363]: https://rustsec.org/advisories/RUSTSEC-2024-0363.html ## 0.8.0 - 2024-07-22 70 pull requests were merged this release cycle. [#2697] was merged the same day as release 0.7.4 and so was missed by the automatic CHANGELOG generation. ### Breaking * [[#2697]]: fix(macros): only enable chrono when time is disabled [[@saiintbrisson]] * [[#2973]]: Generic Associated Types in Database, replacing HasValueRef, HasArguments, HasStatement [[@nitn3lav]] * [[#2482]]: chore: bump syn to 2.0 [[@saiintbrisson]] * Deprecated type ascription syntax in the query macros was removed. * [[#2736]]: Fix describe on PostgreSQL views with rules [[@tsing]] * Potentially breaking: nullability inference changes for Postgres. * [[#2869]]: Implement PgHasArrayType for all references [[@tylerhawkes]] * Conflicts with existing manual implementations. * [[#2940]]: fix: Decode and Encode derives (#1031) [[@benluelo]] * Changes lifetime obligations for field types. * [[#3064]]: Sqlite explain graph [[@tyrelr]] * Potentially breaking: nullability inference changes for SQLite. * [[#3123]]: Reorder attrs in sqlx::test macro [[@bobozaur]] * Potentially breaking: attributes on `#[sqlx::test]` usages are applied in the correct order now. * [[#3126]]: Make Encode return a result [[@FSMaxB]] * [[#3130]]: Add version information for failed cli migration (#3129) [[@FlakM]] * Breaking changes to `MigrateError`. * [[#3181]]: feat: no tx migration [[@cleverjam]] * (Postgres only) migrations that should not run in a transaction can be flagged by adding `-- no-transaction` to the beginning. * Breaking change: added field to `Migration` * [[#3184]]: [BREAKING} fix(sqlite): always use `i64` as intermediate when decoding [[@abonander]] * integer decoding will now loudly error on overflow instead of silently truncating. * some usages of the query!() macros might change an i32 to an i64. * [[#3252]]: fix `#[derive(sqlx::Type)]` in Postgres [[@abonander]] * Manual implementations of PgHasArrayType for enums will conflict with the generated one. Delete the manual impl or add `#[sqlx(no_pg_array)]` where conflicts occur. * Type equality for PgTypeInfo is now schema-aware. * [[#3329]]: fix: correct handling of arrays of custom types in Postgres [[@abonander]] * Potential breaking change: `PgTypeInfo::with_name()` infers types that start with `_` to be arrays of the un-prefixed type. Wrap type names in quotes to bypass this behavior. * [[#3356]]: breaking: fix name collision in `FromRow`, return `Error::ColumnDecode` for `TryFrom` errors [[@abonander]] * Breaking behavior change: errors with `#[sqlx(try_from = "T")]` now return `Error::ColumnDecode` instead of `Error::ColumnNotFound`. * Breaking because `#[sqlx(default)]` on an individual field or the struct itself would have previously suppressed the error. This doesn't seem like good behavior as it could result in some potentially very difficult bugs. * Instead, create a wrapper implementing `From` and apply the default explicitly. * [[#3337]]: allow rename with rename_all (close #2896) [[@DirectorX]] * Changes the precedence of `#[sqlx(rename)]` and `#[sqlx(rename_all)]` to match the expected behavior (`rename` wins). * [[#3285]]: fix: use correct names for sslmode options [[@lily-mosquitoes]] * Changes the output of `ConnectOptions::to_url_lossy()` to match what parsing expects. ### Added * [[#2917]]: Add Debug impl for PgRow [[@g-bartoszek]] * [[#3113]]: feat: new derive feature flag [[@saiintbrisson]] * [[#3154]]: feat: add `MySqlTime`, audit `mysql::types` for panics [[@abonander]] * [[#3188]]: feat(cube): support postgres cube [[@jayy-lmao]] * [[#3244]]: feat: support `NonZero*` scalar types [[@AlphaKeks]] * [[#3260]]: feat: Add set_update_hook on SqliteConnection [[@gridbox]] * [[#3291]]: feat: support the Postgres Bool type for the Any driver [[@etorreborre]] * [[#3293]]: Add LICENSE-* files to crates [[@LecrisUT]] * [[#3303]]: add array support for NonZeroI* in postgres [[@JohannesIBK]] * [[#3311]]: Add example on how to use Transaction as Executor [[@Lachstec]] * [[#3343]]: Add support for PostgreSQL HSTORE data type [[@KobusEllis]] ### Changed * [[#2652]]: MySQL: Remove collation compatibility check for strings [[@alu]] * [[#2960]]: Removed `Send` trait bound from argument binding [[@bobozaur]] * [[#2970]]: refactor: lift type mappings into driver crates [[@abonander]] * [[#3148]]: Bump libsqlite3-sys to v0.28 [[@NfNitLoop]] * Note: version bumps to `libsqlite3-sys` are not considered breaking changes as per our semver guarantees. * [[#3265]]: perf: box `MySqlConnection` to reduce sizes of futures [[@stepantubanov]] * [[#3352]]: chore:added a testcase for `sqlx migrate add ...` [[@CommanderStorm]] * [[#3340]]: ci: Add job to check that sqlx builds with its declared minimum dependencies [[@iamjpotts]] ### Fixed * [[#2702]]: Constrain cyclic associated types to themselves [[@BadBastion]] * [[#2954]]: Fix several inter doc links [[@ralpha]] * [[#3073]]: feat(logging): Log slow acquires from connection pool [[@iamjpotts]] * [[#3137]]: SqliteConnectOptions::filename() memory fix (#3136) [[@hoxxep]] * [[#3138]]: PostgreSQL Bugfix: Ensure connection is usable after failed COPY inside a transaction [[@feikesteenbergen]] * [[#3146]]: fix(sqlite): delete unused `ConnectionHandleRaw` type [[@abonander]] * [[#3162]]: Drop urlencoding dependency [[@paolobarbolini]] * [[#3165]]: Bump deps that do not need code changes [[@GnomedDev]] * [[#3167]]: fix(ci): use `docker compose` instead of `docker-compose` [[@abonander]] * [[#3172]]: fix: Option decoding in any driver [[@pxp9]] * [[#3173]]: fix(postgres) : int type conversion while decoding [[@RaghavRox]] * [[#3190]]: Update time to 0.3.36 [[@BlackSoulHub]] * [[#3191]]: Fix unclean TLS shutdown [[@levkk]] * [[#3194]]: Fix leaking connections in fetch_optional (#2647) [[@danjpgriffin]] * [[#3216]]: security: bump rustls to 0.21.11 [[@toxeus]] * [[#3230]]: fix: sqlite pragma order for auto_vacuum [[@jasonish]] * [[#3233]]: fix: get_filename should not consume self [[@jasonish]] * [[#3234]]: fix(ci): pin Rust version, ditch unmaintained actions [[@abonander]] * [[#3236]]: fix: resolve `path` ownership problems when using `sqlx_macros_unstable` [[@lily-mosquitoes]] * [[#3254]]: fix: hide `sqlx_postgres::any` [[@Zarathustra2]] * [[#3266]]: ci: MariaDB - add back 11.4 and add 11.5 [[@grooverdan]] * [[#3267]]: ci: syntax fix [[@grooverdan]] * [[#3271]]: docs(sqlite): fix typo - unixtime() -> unixepoch() [[@joelkoen]] * [[#3276]]: Invert boolean for `migrate` error message. (#3275) [[@nk9]] * [[#3279]]: fix Clippy errors [[@abonander]] * [[#3288]]: fix: sqlite update_hook char types [[@jasonish]] * [[#3297]]: Pass the `persistent` query setting when preparing queries with the `Any` driver [[@etorreborre]] * [[#3298]]: Track null arguments in order to provide the appropriate type when converting them. [[@etorreborre]] * [[#3312]]: doc: Minor rust docs fixes [[@SrGesus]] * [[#3327]]: chore: fixed one usage of `select_input_type!()` being unhygenic [[@CommanderStorm]] * [[#3328]]: fix(ci): comment not separated from other characters [[@hamirmahal]] * [[#3341]]: refactor: Resolve cargo check warnings in postgres examples [[@iamjpotts]] * [[#3346]]: fix(postgres): don't panic if `M` or `C` Notice fields are not UTF-8 [[@YgorSouza]] * [[#3350]]: fix:the `json`-feature should activate `sqlx-postgres?/json` as well [[@CommanderStorm]] * [[#3353]]: fix: build script new line at eof [[@Zarthus]] * (no PR): activate `clock` and `std` features of `workspace.dependencies.chrono`. [#2482]: https://github.com/launchbadge/sqlx/pull/2482 [#2652]: https://github.com/launchbadge/sqlx/pull/2652 [#2697]: https://github.com/launchbadge/sqlx/pull/2697 [#2702]: https://github.com/launchbadge/sqlx/pull/2702 [#2736]: https://github.com/launchbadge/sqlx/pull/2736 [#2869]: https://github.com/launchbadge/sqlx/pull/2869 [#2917]: https://github.com/launchbadge/sqlx/pull/2917 [#2940]: https://github.com/launchbadge/sqlx/pull/2940 [#2954]: https://github.com/launchbadge/sqlx/pull/2954 [#2960]: https://github.com/launchbadge/sqlx/pull/2960 [#2970]: https://github.com/launchbadge/sqlx/pull/2970 [#2973]: https://github.com/launchbadge/sqlx/pull/2973 [#3064]: https://github.com/launchbadge/sqlx/pull/3064 [#3073]: https://github.com/launchbadge/sqlx/pull/3073 [#3113]: https://github.com/launchbadge/sqlx/pull/3113 [#3123]: https://github.com/launchbadge/sqlx/pull/3123 [#3126]: https://github.com/launchbadge/sqlx/pull/3126 [#3130]: https://github.com/launchbadge/sqlx/pull/3130 [#3137]: https://github.com/launchbadge/sqlx/pull/3137 [#3138]: https://github.com/launchbadge/sqlx/pull/3138 [#3146]: https://github.com/launchbadge/sqlx/pull/3146 [#3148]: https://github.com/launchbadge/sqlx/pull/3148 [#3154]: https://github.com/launchbadge/sqlx/pull/3154 [#3162]: https://github.com/launchbadge/sqlx/pull/3162 [#3165]: https://github.com/launchbadge/sqlx/pull/3165 [#3167]: https://github.com/launchbadge/sqlx/pull/3167 [#3172]: https://github.com/launchbadge/sqlx/pull/3172 [#3173]: https://github.com/launchbadge/sqlx/pull/3173 [#3181]: https://github.com/launchbadge/sqlx/pull/3181 [#3184]: https://github.com/launchbadge/sqlx/pull/3184 [#3188]: https://github.com/launchbadge/sqlx/pull/3188 [#3190]: https://github.com/launchbadge/sqlx/pull/3190 [#3191]: https://github.com/launchbadge/sqlx/pull/3191 [#3194]: https://github.com/launchbadge/sqlx/pull/3194 [#3216]: https://github.com/launchbadge/sqlx/pull/3216 [#3230]: https://github.com/launchbadge/sqlx/pull/3230 [#3233]: https://github.com/launchbadge/sqlx/pull/3233 [#3234]: https://github.com/launchbadge/sqlx/pull/3234 [#3236]: https://github.com/launchbadge/sqlx/pull/3236 [#3244]: https://github.com/launchbadge/sqlx/pull/3244 [#3252]: https://github.com/launchbadge/sqlx/pull/3252 [#3254]: https://github.com/launchbadge/sqlx/pull/3254 [#3260]: https://github.com/launchbadge/sqlx/pull/3260 [#3265]: https://github.com/launchbadge/sqlx/pull/3265 [#3266]: https://github.com/launchbadge/sqlx/pull/3266 [#3267]: https://github.com/launchbadge/sqlx/pull/3267 [#3271]: https://github.com/launchbadge/sqlx/pull/3271 [#3276]: https://github.com/launchbadge/sqlx/pull/3276 [#3279]: https://github.com/launchbadge/sqlx/pull/3279 [#3285]: https://github.com/launchbadge/sqlx/pull/3285 [#3288]: https://github.com/launchbadge/sqlx/pull/3288 [#3291]: https://github.com/launchbadge/sqlx/pull/3291 [#3293]: https://github.com/launchbadge/sqlx/pull/3293 [#3297]: https://github.com/launchbadge/sqlx/pull/3297 [#3298]: https://github.com/launchbadge/sqlx/pull/3298 [#3303]: https://github.com/launchbadge/sqlx/pull/3303 [#3311]: https://github.com/launchbadge/sqlx/pull/3311 [#3312]: https://github.com/launchbadge/sqlx/pull/3312 [#3327]: https://github.com/launchbadge/sqlx/pull/3327 [#3328]: https://github.com/launchbadge/sqlx/pull/3328 [#3329]: https://github.com/launchbadge/sqlx/pull/3329 [#3337]: https://github.com/launchbadge/sqlx/pull/3337 [#3340]: https://github.com/launchbadge/sqlx/pull/3340 [#3341]: https://github.com/launchbadge/sqlx/pull/3341 [#3343]: https://github.com/launchbadge/sqlx/pull/3343 [#3346]: https://github.com/launchbadge/sqlx/pull/3346 [#3350]: https://github.com/launchbadge/sqlx/pull/3350 [#3352]: https://github.com/launchbadge/sqlx/pull/3352 [#3353]: https://github.com/launchbadge/sqlx/pull/3353 [#3356]: https://github.com/launchbadge/sqlx/pull/3356 ## 0.7.4 - 2024-03-11 38 pull requests were merged this release cycle. This is officially the **last** release of the 0.7.x release cycle. As of this release, development of 0.8.0 has begun on `main` and only high-priority bugfixes may be backported. ### Added * [[#2891]]: feat: expose getters for connect options fields [[@saiintbrisson]] * [[#2902]]: feat: add `to_url_lossy` to connect options [[@lily-mosquitoes]] * [[#2927]]: Support `query!` for cargo-free systems [[@kshramt]] * [[#2997]]: doc(FAQ): add entry explaining prepared statements [[@abonander]] * [[#3001]]: Update README to clarify MariaDB support [[@iangilfillan]] * [[#3004]]: feat(logging): Add numeric elapsed time field elapsed_secs [[@iamjpotts]] * [[#3007]]: feat: add `raw_sql` API [[@abonander]] * This hopefully makes it easier to find how to execute statements which are not supported by the default prepared statement interfaces `query*()` and `query!()`. * Improved documentation across the board for the `query*()` functions. * Deprecated: `execute_many()` and `fetch_many()` on interfaces that use prepared statements. * Multiple SQL statements in one query string were only supported by SQLite because its prepared statement interface is the *only* way to execute SQL. All other database flavors forbid multiple statements in one prepared statement string as an extra defense against SQL injection. * The new `raw_sql` API retains this functionality because it explicitly does *not* use prepared statements. Raw or text-mode query interfaces generally allow multiple statements in one query string, and this is supported by all current databases. Due to their nature, however, one cannot use bind parameters with them. * If this change affects you, an issue is open for discussion: https://github.com/launchbadge/sqlx/issues/3108 * [[#3011]]: Added support to IpAddr with MySQL/MariaDB. [[@Icerath]] * [[#3013]]: Add default implementation for PgInterval [[@pawurb]] * [[#3018]]: Add default implementation for PgMoney [[@pawurb]] * [[#3026]]: Update docs to reflect support for MariaDB data types [[@iangilfillan]] * [[#3037]]: feat(mysql): allow to connect with mysql driver without default behavor [[@darkecho731]] ### Changed * [[#2900]]: Show latest url to docs for macro.migrate [[@Vrajs16]] * [[#2914]]: Use `create_new` instead of `atomic-file-write` [[@mattfbacon]] * [[#2926]]: docs: update example for `PgConnectOptions` [[@Fyko]] * [[#2989]]: sqlx-core: Remove dotenvy dependency [[@joshtriplett]] * [[#2996]]: chore: Update ahash to 0.8.7 [[@takenoko-gohan]] * [[#3006]]: chore(deps): Replace unmaintained tempdir crate with tempfile [[@iamjpotts]] * [[#3008]]: chore: Ignore .sqlx folder created by running ci steps locally [[@iamjpotts]] * [[#3009]]: chore(dev-deps): Upgrade env_logger from 0.9 to 0.11 [[@iamjpotts]] * [[#3010]]: chore(deps): Upgrade criterion to 0.5.1 [[@iamjpotts]] * [[#3050]]: Optimize SASL auth in sqlx-postgres [[@mirek26]] * [[#3055]]: Set TCP_NODELAY option on TCP sockets [[@mirek26]] * [[#3065]]: Improve max_lifetime handling [[@mirek26]] * [[#3072]]: Change the name of "inner" function generated by `#[sqlx::test]` [[@ciffelia]] * [[#3083]]: Remove sha1 because it's not being used in postgres [[@rafaelGuerreiro]] ### Fixed * [[#2898]]: Fixed docs [[@Vrajs16]] * [[#2905]]: fix(mysql): Close prepared statement if persistence is disabled [[@larsschumacher]] * [[#2913]]: Fix handling of deferred constraints [[@Thomasdezeeuw]] * [[#2919]]: fix duplicate "`" in FromRow "default" attribute doc comment [[@shengsheng]] * [[#2932]]: fix(postgres): avoid unnecessary flush in PgCopyIn::read_from [[@tsing]] * [[#2955]]: Minor fixes [[@Dawsoncodes]] * [[#2963]]: Fixed ReadMe badge styling [[@tadghh]] * [[#2976]]: fix: AnyRow not support PgType::Varchar [[@holicc]] * [[#3053]]: fix: do not panic when binding a large BigDecimal [[@Ekleog]] * [[#3056]]: fix: spans in sqlite tracing (#2876) [[@zoomiti]] * [[#3089]]: fix(migrate): improve error message when parsing version from filename [[@abonander]] * [[#3098]]: Migrations fixes [[@abonander]] * Unhides `sqlx::migrate::Migrator`. * Improves I/O error message when failing to read a file in `migrate!()`. [#2891]: https://github.com/launchbadge/sqlx/pull/2891 [#2898]: https://github.com/launchbadge/sqlx/pull/2898 [#2900]: https://github.com/launchbadge/sqlx/pull/2900 [#2902]: https://github.com/launchbadge/sqlx/pull/2902 [#2905]: https://github.com/launchbadge/sqlx/pull/2905 [#2913]: https://github.com/launchbadge/sqlx/pull/2913 [#2914]: https://github.com/launchbadge/sqlx/pull/2914 [#2919]: https://github.com/launchbadge/sqlx/pull/2919 [#2926]: https://github.com/launchbadge/sqlx/pull/2926 [#2927]: https://github.com/launchbadge/sqlx/pull/2927 [#2932]: https://github.com/launchbadge/sqlx/pull/2932 [#2955]: https://github.com/launchbadge/sqlx/pull/2955 [#2963]: https://github.com/launchbadge/sqlx/pull/2963 [#2976]: https://github.com/launchbadge/sqlx/pull/2976 [#2989]: https://github.com/launchbadge/sqlx/pull/2989 [#2996]: https://github.com/launchbadge/sqlx/pull/2996 [#2997]: https://github.com/launchbadge/sqlx/pull/2997 [#3001]: https://github.com/launchbadge/sqlx/pull/3001 [#3004]: https://github.com/launchbadge/sqlx/pull/3004 [#3006]: https://github.com/launchbadge/sqlx/pull/3006 [#3007]: https://github.com/launchbadge/sqlx/pull/3007 [#3008]: https://github.com/launchbadge/sqlx/pull/3008 [#3009]: https://github.com/launchbadge/sqlx/pull/3009 [#3010]: https://github.com/launchbadge/sqlx/pull/3010 [#3011]: https://github.com/launchbadge/sqlx/pull/3011 [#3013]: https://github.com/launchbadge/sqlx/pull/3013 [#3018]: https://github.com/launchbadge/sqlx/pull/3018 [#3026]: https://github.com/launchbadge/sqlx/pull/3026 [#3037]: https://github.com/launchbadge/sqlx/pull/3037 [#3050]: https://github.com/launchbadge/sqlx/pull/3050 [#3053]: https://github.com/launchbadge/sqlx/pull/3053 [#3055]: https://github.com/launchbadge/sqlx/pull/3055 [#3056]: https://github.com/launchbadge/sqlx/pull/3056 [#3065]: https://github.com/launchbadge/sqlx/pull/3065 [#3072]: https://github.com/launchbadge/sqlx/pull/3072 [#3083]: https://github.com/launchbadge/sqlx/pull/3083 [#3089]: https://github.com/launchbadge/sqlx/pull/3089 [#3098]: https://github.com/launchbadge/sqlx/pull/3098 ## 0.7.3 - 2023-11-22 38 pull requests were merged this release cycle. ### Added * [[#2478]]: feat(citext): support postgres citext [[@hgranthorner]] * [[#2545]]: Add `fixtures_path` in sqlx::test args [[@ripa1995]] * [[#2665]]: feat(mysql): support packet splitting [[@tk2217]] * [[#2752]]: Enhancement #2747 Provide `fn PgConnectOptions::get_host(&self)` [[@boris-lok]] * [[#2769]]: Customize the macro error message based on the metadata [[@Nemo157]] * [[#2793]]: derived Hash trait for PgInterval [[@yasamoka]] * [[#2801]]: derive FromRow: sqlx(default) for all fields [[@grgi]] * [[#2827]]: Add impl `FromRow` for the unit type [[@nanoqsh]] * [[#2871]]: Add `MySqlConnectOptions::get_database()` [[@shiftrightonce]] * [[#2873]]: Sqlx Cli: Added force flag to drop database for postgres [[@Vrajs16]] * [[#2894]]: feat: `Text` adapter [[@abonander]] ### Changed * [[#2701]]: Remove documentation on offline feature [[@Baptistemontan]] * [[#2713]]: Add additional info regarding using Transaction and PoolConnection as… [[@satwanjyu]] * [[#2770]]: Update README.md [[@snspinn]] * [[#2797]]: doc(mysql): document behavior regarding `BOOLEAN` and the query macros [[@abonander]] * [[#2803]]: Don't use separate temp dir for query jsons (2) [[@mattfbacon]] * [[#2819]]: postgres begin cancel safe [[@conradludgate]] * [[#2832]]: Update extra_float_digits default to 2 instead of 3 [[@brianheineman]] * [[#2865]]: Update Faq - Bulk upsert with optional fields [[@Vrajs16]] * [[#2880]]: feat: use specific message for slow query logs [[@abonander]] * [[#2882]]: Do not require db url for prepare [[@tamasfe]] * [[#2890]]: doc(sqlite): cover lack of `NUMERIC` support [[@abonander]] * [No PR]: Upgraded `libsqlite3-sys` to 0.27.0 * Note: linkage to `libsqlite3-sys` is considered semver-exempt; see the release notes for 0.7.0 below for details. ### Fixed * [[#2640]]: fix: sqlx::macro db cleanup race condition by adding a margin to current timestamp [[@fhsgoncalves]] * [[#2655]]: [fix] Urlencode when passing filenames to sqlite3 [[@uttarayan21]] * [[#2684]]: Make PgListener recover from UnexpectedEof [[@hamiltop]] * [[#2688]]: fix: Make rust_decimal and bigdecimal decoding more lenient [[@cameronbraid]] * [[#2754]]: Is tests/x.py maintained? And I tried fix it. [[@qwerty2501]] * [[#2784]]: fix: decode postgres time without subsecond [[@granddaifuku]] * [[#2806]]: Depend on version of async-std with non-private spawn-blocking [[@A248]] * [[#2820]]: fix: correct decoding of `rust_decimal::Decimal` for high-precision values [[@abonander]] * [[#2822]]: issue #2821 Update error handling logic when opening a TCP connection [[@anupj]] * [[#2826]]: chore: bump some sqlx-core dependencies [[@djc]] * [[#2838]]: Fixes rust_decimal scale for Postgres [[@jkleinknox]] * [[#2847]]: Fix comment in `sqlx migrate add` help text [[@cryeprecision]] * [[#2850]]: fix(core): avoid unncessary wakeups in `try_stream!()` [[@abonander]] * [[#2856]]: Prevent warnings running `cargo build` [[@nyurik]] * [[#2864]]: fix(sqlite): use `AtomicUsize` for thread IDs [[@abonander]] * [[#2892]]: Fixed force dropping bug [[@Vrajs16]] [#2478]: https://github.com/launchbadge/sqlx/pull/2478 [#2545]: https://github.com/launchbadge/sqlx/pull/2545 [#2640]: https://github.com/launchbadge/sqlx/pull/2640 [#2655]: https://github.com/launchbadge/sqlx/pull/2655 [#2665]: https://github.com/launchbadge/sqlx/pull/2665 [#2684]: https://github.com/launchbadge/sqlx/pull/2684 [#2688]: https://github.com/launchbadge/sqlx/pull/2688 [#2701]: https://github.com/launchbadge/sqlx/pull/2701 [#2713]: https://github.com/launchbadge/sqlx/pull/2713 [#2752]: https://github.com/launchbadge/sqlx/pull/2752 [#2754]: https://github.com/launchbadge/sqlx/pull/2754 [#2769]: https://github.com/launchbadge/sqlx/pull/2769 [#2770]: https://github.com/launchbadge/sqlx/pull/2770 [#2782]: https://github.com/launchbadge/sqlx/pull/2782 [#2784]: https://github.com/launchbadge/sqlx/pull/2784 [#2793]: https://github.com/launchbadge/sqlx/pull/2793 [#2797]: https://github.com/launchbadge/sqlx/pull/2797 [#2801]: https://github.com/launchbadge/sqlx/pull/2801 [#2803]: https://github.com/launchbadge/sqlx/pull/2803 [#2806]: https://github.com/launchbadge/sqlx/pull/2806 [#2819]: https://github.com/launchbadge/sqlx/pull/2819 [#2820]: https://github.com/launchbadge/sqlx/pull/2820 [#2822]: https://github.com/launchbadge/sqlx/pull/2822 [#2826]: https://github.com/launchbadge/sqlx/pull/2826 [#2827]: https://github.com/launchbadge/sqlx/pull/2827 [#2832]: https://github.com/launchbadge/sqlx/pull/2832 [#2838]: https://github.com/launchbadge/sqlx/pull/2838 [#2847]: https://github.com/launchbadge/sqlx/pull/2847 [#2850]: https://github.com/launchbadge/sqlx/pull/2850 [#2856]: https://github.com/launchbadge/sqlx/pull/2856 [#2864]: https://github.com/launchbadge/sqlx/pull/2864 [#2865]: https://github.com/launchbadge/sqlx/pull/2865 [#2871]: https://github.com/launchbadge/sqlx/pull/2871 [#2873]: https://github.com/launchbadge/sqlx/pull/2873 [#2880]: https://github.com/launchbadge/sqlx/pull/2880 [#2882]: https://github.com/launchbadge/sqlx/pull/2882 [#2890]: https://github.com/launchbadge/sqlx/pull/2890 [#2892]: https://github.com/launchbadge/sqlx/pull/2892 [#2894]: https://github.com/launchbadge/sqlx/pull/2894 ## 0.7.2 - 2023-09-25 23 pull requests were merged this release cycle. ### Added * [[#2121]]: Add JSON support to `FromRow` derive [[@95ulisse]] * [[#2533]]: Implement mysql_clear_password [[@ldanilek]] * [[#2538]]: cli: add --target-version CLI flags for migrate run/revert [[@inahga]] * [[#2577]]: supplement Postgres listen example with a small chat example [[@JockeM]] * [[#2602]]: Support naming migrations sequentially [[@vmax]] * [[#2634]]: Adding PgHasArrayType for &[u8;N] [[@snf]] * [[#2646]]: Support for setting client certificate and key from bytes [[@wyhaya]] * [[#2664]]: Automatically infer migration type [[@vmax]] * [[#2712]]: Add impl for `Type`, `Decode`, and `Encode` for `Box` and `Box<[u8]>` [[@grant0417]] ### Changed * [[#2650]]: Cleanup format arguments [[@nyurik]] * [[#2695]]: remove &mut PoolConnection from Executor docs [[@olback]] * This impl was removed in 0.7.0 because of coherence issues. * [[#2706]]: Clarify where optional features should be enabled [[@kryptan]] * [[#2717]]: Update README.md [[@fermanjj]] * [[#2739]]: Bump mariadb CI images + mysql unpin [[@grooverdan]] * [[#2742]]: Implemented poll_flush for Box [[@bobozaur]] * [[#2740]]: Remove sealed trait comments from documentation [[@bobozaur]] * [[#2750]]: Fix #2384, bump flume to v0.11.0 [[@madadam]] * [[#2757]]: Remove unused `remove_dir_all` crate from `sqlx-cli`, fixes RUSTSEC-2023-0018 [[@aldur]] ### Fixed * [[#2624]]: Documentation typo: BYTE -> BINARY [[@sebastianv89]] * [[#2628]]: docs: 0.7 is stable in the entire README [[@marcusirgens]] * [[#2630]]: fix(postgres): fix buffer management in PgCopyIn::read_from [[@tsing]] * [[#2651]]: Chore: Fix few build warnings, and make CI fail on warn [[@nyurik]] * [[#2670]]: fix: ignore extra fields in Postgres describe parsing [[@abonander]] * [[#2687]]: docs: Fix description of `min_connections` [[@hakoerber]] [#2121]: https://github.com/launchbadge/sqlx/pull/2121 [#2533]: https://github.com/launchbadge/sqlx/pull/2533 [#2538]: https://github.com/launchbadge/sqlx/pull/2538 [#2577]: https://github.com/launchbadge/sqlx/pull/2577 [#2602]: https://github.com/launchbadge/sqlx/pull/2602 [#2624]: https://github.com/launchbadge/sqlx/pull/2624 [#2628]: https://github.com/launchbadge/sqlx/pull/2628 [#2630]: https://github.com/launchbadge/sqlx/pull/2630 [#2634]: https://github.com/launchbadge/sqlx/pull/2634 [#2646]: https://github.com/launchbadge/sqlx/pull/2646 [#2650]: https://github.com/launchbadge/sqlx/pull/2650 [#2651]: https://github.com/launchbadge/sqlx/pull/2651 [#2664]: https://github.com/launchbadge/sqlx/pull/2664 [#2670]: https://github.com/launchbadge/sqlx/pull/2670 [#2687]: https://github.com/launchbadge/sqlx/pull/2687 [#2695]: https://github.com/launchbadge/sqlx/pull/2695 [#2706]: https://github.com/launchbadge/sqlx/pull/2706 [#2712]: https://github.com/launchbadge/sqlx/pull/2712 [#2717]: https://github.com/launchbadge/sqlx/pull/2717 [#2739]: https://github.com/launchbadge/sqlx/pull/2739 [#2740]: https://github.com/launchbadge/sqlx/pull/2740 [#2742]: https://github.com/launchbadge/sqlx/pull/2742 [#2750]: https://github.com/launchbadge/sqlx/pull/2750 [#2757]: https://github.com/launchbadge/sqlx/pull/2757 ## 0.7.1 - 2023-07-14 This release mainly addresses issues reported with the 0.7.0 release. 16 pull requests were merged this release cycle. ### Added * [[#2551]]: Introduce build_query_scalar for QueryBuilder [[@iamquang95]] * [[#2605]]: Implement Default for QueryBuilder [[@Xydez]] * [[#2616]]: feat(sqlx-core): add table function to database error [[@saiintbrisson]] * [[#2619]]: feat: allow opt-out of `PgHasArrayType` with `#[derive(sqlx::Type)]` [[@abonander]] * TL;DR: if you're getting errors from `#[derive(sqlx::Type)]` with `#[sqlx(transparent)]` regarding `PgHasArrayType` not being implemented, add `#[sqlx(no_pg_array)]` to fix. ### Changed * [[#2566]]: improve docs about migration files [[@jnnnnn]] * [[#2576]]: Major Version Update clap to 4.0 [[@titaniumtraveler]] * [[#2597]]: Bump webpki-roots to v0.24 [[@paolobarbolini]] * [[#2603]]: docs(changelog): be more verbose about offline mode breaking change [[@mrl5]] ### Fixed * [[#2553]]: Implement `Clone` for `PoolOptions` manually (#2548) [[@alilleybrinker]] * [[#2580]]: Update README.md now that 0.7.0 is no longer in alpha [[@saolof]] * [[#2585]]: Fix for Issue #2549 - cannot use feature "rust_decimal" without also using "bigdecimal" [[@deneut]] * [[#2586]]: Fix optional dependency on sqlx-macros [[@kitterion]] * [[#2593]]: Correct mention of the `tls-native-tls` in the documentation. [[@denschub]] * [[#2599]]: Remove incorrect CAST in test database cleanup for MySQL. [[@fd]] * [[#2613]]: Fix readme.md to reduce confusion about optional features (decimal->rust_decimal) [[@vabka]] * [[#2620]]: fix(sqlite/any): encode bool as integer [[@saiintbrisson]] [#2551]: https://github.com/launchbadge/sqlx/pull/2551 [#2553]: https://github.com/launchbadge/sqlx/pull/2553 [#2566]: https://github.com/launchbadge/sqlx/pull/2566 [#2576]: https://github.com/launchbadge/sqlx/pull/2576 [#2580]: https://github.com/launchbadge/sqlx/pull/2580 [#2585]: https://github.com/launchbadge/sqlx/pull/2585 [#2586]: https://github.com/launchbadge/sqlx/pull/2586 [#2593]: https://github.com/launchbadge/sqlx/pull/2593 [#2597]: https://github.com/launchbadge/sqlx/pull/2597 [#2599]: https://github.com/launchbadge/sqlx/pull/2599 [#2603]: https://github.com/launchbadge/sqlx/pull/2603 [#2605]: https://github.com/launchbadge/sqlx/pull/2605 [#2613]: https://github.com/launchbadge/sqlx/pull/2613 [#2616]: https://github.com/launchbadge/sqlx/pull/2616 [#2619]: https://github.com/launchbadge/sqlx/pull/2619 [#2620]: https://github.com/launchbadge/sqlx/pull/2620 ## 0.7.0 - 2023-06-30 At least **70 pull requests** were merged this release cycle! (The exact count is muddied with pull requests for alpha releases and such.) And we gained 43 new contributors! Thank you to everyone who helped make this release a reality. ### Breaking Many revisions were made to query analysis in the SQLite driver; these are all potentially breaking changes as they can change the output of `sqlx::query!()` _et al_. We'd like to thank [[@tyrelr]] for their numerous PRs to this area. The MSSQL driver has been removed as it was not nearly at the same maturity level as the other drivers. [As previously announced][sqlx-pro], we have plans to introduce a fully featured replacement as a premium offering, alongside drivers for other proprietary databases, with the goal to support full-time development on SQLx. If interested, please email your inquiry to sqlx@launchbadge.com. The offline mode for the queries has been changed to use a separate file per `query!()` invocation, which is intended to reduce the number of conflicts when merging branches in a project that both modified queries. This means that CLI flag `--merged` is no longer supported. See [[#2363]] for details and make sure that your `sqlx-cli` version is in sync with the `sqlx` version in your project. The type ascription override syntax for the query macros has been deprecated, as parse support for it has been removed in `syn 2.0`, which we'll be upgrading to in the next breaking release. This can be replaced with type overrides using casting syntax (`as`). See [[#2483]] for details. * [[#1946]]: Fix compile time verification performance regression for sqlite [[@liningpan]] * [[#1960]]: Fix sqlite update return and order by type inference [[@tyrelr]] * [[#1984]]: Sqlite EXPLAIN type inference improvements [[@rongcuid]] * [[#2039]]: Break drivers out into separate crates, clean up some technical debt [[@abonander]] * All deprecated items have been removed. * The `mssql` feature and associated database driver has been deleted from the source tree. It will return as part of our planned SQLx Pro offering as a from-scratch rewrite with extra features (such as TLS) and type integrations that were previously missing. * The `runtime-actix-*` features have been deleted. They were previously changed to be aliases of their `runtime-tokio-*` counterparts for backwards compatibility reasons, but their continued existence is misleading as SQLx has no special knowledge of Actix anymore. * To fix, simply replace the `runtime-actix-*` feature with its `runtime-tokio-*` equivalent. * The `git2` feature has been removed. This was a requested integration from a while ago that over time made less and less sense to be part of SQLx itself. We have to be careful with the crates we add to our public API as each one introduces yet another semver hazard. The expected replacement is to make `#[derive(sqlx::Type)]` useful enough that users can write wrapper types for whatever they want to use without SQLx needing to be specifically aware of it. * The `Executor` impls for `Transaction` and `PoolConnection` have been deleted because they cannot exist in the new crate architecture without rewriting the `Executor` trait entirely. * To fix this breakage, simply add a dereference where an `impl Executor` is expected, as they both dereference to the inner connection type which will still implement it: * `&mut transaction` -> `&mut *transaction` * `&mut connection` -> `&mut *connection` * These cannot be blanket impls as it triggers an overflow in the compiler due to the lack of lazy normalization, and the driver crates cannot provide their own impls due to the orphan rule. * We're expecting to do another major refactor of traits to incorporate generic associated types (GAT). This will mean another major release of SQLx but ideally most API usage will not need to change significantly, if at all. * The fields of `Migrator` are now `#[doc(hidden)]` and semver-exempt; they weren't meant to be public. * The `offline` feature has been removed from the `sqlx` facade crate and is enabled unconditionally as most users are expected to have enabled it anyway and disabling it doesn't seem to appreciably affect compile times. * The `decimal` feature has been renamed to `rust_decimal` to match the crate it actually provides integrations for. * `AnyDriver` and `AnyConnection` now require either `sqlx::any::install_drivers()` or `sqlx::any::install_default_drivers()` to be called at some point during the process' lifetime before the first connection is made, as the set of possible drivers is now determined at runtime. This was determined to be the least painful way to provide knowledge of database drivers to `Any` without them being hardcoded. * The `AnyEncode` trait has been removed. * [[#2109]]: feat: better database errors [[@saiintbrisson]] * [[#2094]]: Update libsqlite3-sys to 0.25.1 [[@penberg]] * Alongside this upgrade, we are now considering the linkage to `libsqlite3-sys` to be **semver-exempt**, and we reserve the right to upgrade it as necessary. If you are using `libsqlite3-sys` directly or a crate that links it such as `rusqlite`, you should pin the versions of both crates to avoid breakages from `cargo update`: ```toml [dependencies] sqlx = { version = "=0.7.0", features = ["sqlite"] } rusqlite = "=0.29.0" ``` * [[#2132]]: fix: use owned Builder pattern for ConnectOptions [[@ar3s3ru]] * [[#2253]]: Sqlite describe fixes [[@tyrelr]] * [[#2285]]: `time`: Assume UTC when decoding a DATETIME column in sqlite [[@nstinus]] * [[#2363]]: [offline] Change prepare to one-file-per-query [[@cycraig]] * [[#2387]]: PATCH: bump libsqlite3-sys to patched version [[@grantkee]] * [[#2409]]: fix(#2407): respect the HaltIfNull opcode when determining nullability [[@arlyon]] * [[#2459]]: limit the number of instructions that can be evaluated [[@tyrelr]] * [[#2467]]: Add and improve sqlite describe performance benchmarks [[@tyrelr]] * [[#2491]]: sqlite date macro support [[@Arcayr]] * Changes `OffsetDateTime` to be the first type used when deserializing a `timestamp` type. * [[#2496]]: Bump to libsqlite3-sys 0.26 [[@mdecimus]] * [[#2508]]: Sqlite analytical [[@tyrelr]] ### Added * [[#1850]]: Add client SSL authentication using key-file for Postgres, MySQL and MariaDB [[@ThibsG]] * [[#2088]]: feat: Add set_connect_options method to Pool [[@moatra]] * [[#2113]]: Expose PoolOptions for reading [[@FSMaxB]] * [[#2115]]: Allow using complex types in `try_from` when deriving `FromRow` [[@95ulisse]] * [[#2116]]: [SQLite] Add option to execute `PRAGMA optimize;` on close of a connection [[@miles170]] * [[#2189]]: Added regexp support in sqlite [[@VictorKoenders]] * [[#2224]]: Add From impls for Json [[@dbeckwith]] * [[#2256]]: add progress handler support to sqlite [[@nbaztec]] * [[#2366]]: Allow ignoring attributes for deriving FromRow [[@grgi]] * [[#2369]]: new type support in query_as [[@0xdeafbeef]] * [[#2379]]: feat: add `Connection::shrink_buffers`, `PoolConnection::close` [[@abonander]] * [[#2400]]: fix(docs): example of `sqlx_macros_unstable` in config.toml [[@df51d]] * [[#2469]]: Add Simple format for Uuid for MySQL & SQLite. [[@MidasLamb]] * [[#2483]]: chore: add deprecation notice for type ascription use [[@saiintbrisson]] * [[#2506]]: add args to query builder (#2494) [[@cemoktra]] * [[#2554]]: Impl `AsMut` for advisory lock types (#2520) [[@alilleybrinker]] * [[#2559]]: Add CLI autocompletion using clap_complete [[@titaniumtraveler]] ### Changed * [[#2185]]: Initial work to switch to `tracing` [[@CosmicHorrorDev]] * [[#2193]]: Start testing on Postgres 15 and drop Postgres 10 [[@paolobarbolini]] * We reserve the right to drop support for end-of-lifed database versions [as discussed in our FAQ][faq-db-version]. * [[#2213]]: Use `let else` statements in favor of macro [[@OverHash]] * [[#2365]]: Update dependencies [[@paolobarbolini]] * [[#2371]]: Disable rustls crate logging feature by default up to date [[@sergeiivankov]] * [[#2373]]: chore: Use tracing's fields to get structured logs [[@jaysonsantos]] * [[#2393]]: Lower default logging level for statements to Debug [[@bnoctis]] * [[#2445]]: Traverse symlinks when resolving migrations [[@tgeoghegan]] * [[#2485]]: chore(sqlx-postgres): replace `dirs` with `home` & `etcetera` [[@utkarshgupta137]] * [[#2515]]: Bump mac_address to 1.1.5 [[@repnop]] * [[#2440]]: Update rustls to 0.21, webpki-roots to 0.23 [[@SergioBenitez]] * [[#2563]]: Update rsa to 0.9 [[@paolobarbolini]] * [[#2564]]: Update bitflags to v2 [[@paolobarbolini]] * [[#2565]]: Bump indexmap and ahash [[@paolobarbolini]] * [[#2574]]: doc: make it clear that `ConnectOptions` types impl `FromStr` [[@abonander]] ### Fixed * [[#2098]]: Fix sqlite compilation [[@cycraig]] * [[#2120]]: fix logical merge conflict [[@tyrelr]] * [[#2133]]: Postgres OID resolution query does not take into account current `search_path` [[@95ulisse]] * [[#2156]]: Fixed typo. [[@cdbfoster]] * [[#2179]]: fix: ensures recover from fail with PgCopyIn [[@andyquinterom]] * [[#2200]]: Run CI on *-dev branch [[@joehillen]] * [[#2222]]: Add context to confusing sqlx prepare parse error [[@laundmo]] * [[#2271]]: feat: support calling Postgres procedures with the macros [[@bgeron]] * [[#2282]]: Don't run EXPLAIN nullability analysis on Materialize [[@benesch]] * [[#2319]]: Set whoami default-features to false [[@thedodd]] * [[#2352]]: Preparing 0.7.0-alpha.1 release [[@abonander]] * [[#2355]]: Fixed the example code for `sqlx::test` [[@kenkoooo]] * [[#2367]]: Fix sqlx-cli create, drop, migrate [[@cycraig]] * [[#2376]]: fix(pool): close when last handle is dropped, extra check in `try_acquire` [[@abonander]] * [[#2378]]: Fix README build badge [[@dbrgn]] * [[#2398]]: fix(prepare): store temporary query files inside the workspace [[@aschey]] * [[#2402]]: fix: drop old time 0.1.44 dep [[@codahale]] * [[#2413]]: fix(macros-core): use of undeclared `tracked_path` [[@df51d]] * [[#2420]]: Enable runtime-tokio feature of sqlx when building sqlx-cli [[@paolobarbolini]] * [[#2453]]: in README.md, correct spelling and grammar [[@vizvasrj]] * [[#2454]]: fix: ensure fresh test db's aren't accidentally deleted by do_cleanup [[@phlip9]] * [[#2507]]: Exposing the Oid of PostgreSQL types [[@Razican]] * [[#2519]]: Use ::std::result::Result::Ok in output.rs [[@southball]] * [[#2569]]: Fix broken links to mysql error documentation [[@titaniumtraveler]] * [[#2570]]: Add a newline to the generated JSON files [[@nyurik]] * [[#2572]]: Do not panic when `PrepareOk` fails to decode [[@stepantubanov]] * [[#2573]]: fix(sqlite) Do not drop notify mutex guard until after condvar is triggered [[@andrewwhitehead]] [sqlx-pro]: https://github.com/launchbadge/sqlx/discussions/1616 [faq-db-version]: https://github.com/launchbadge/sqlx/blob/main/FAQ.md#what-database-versions-does-sqlx-support [#1850]: https://github.com/launchbadge/sqlx/pull/1850 [#1946]: https://github.com/launchbadge/sqlx/pull/1946 [#1960]: https://github.com/launchbadge/sqlx/pull/1960 [#1984]: https://github.com/launchbadge/sqlx/pull/1984 [#2039]: https://github.com/launchbadge/sqlx/pull/2039 [#2088]: https://github.com/launchbadge/sqlx/pull/2088 [#2092]: https://github.com/launchbadge/sqlx/pull/2092 [#2094]: https://github.com/launchbadge/sqlx/pull/2094 [#2098]: https://github.com/launchbadge/sqlx/pull/2098 [#2109]: https://github.com/launchbadge/sqlx/pull/2109 [#2113]: https://github.com/launchbadge/sqlx/pull/2113 [#2115]: https://github.com/launchbadge/sqlx/pull/2115 [#2116]: https://github.com/launchbadge/sqlx/pull/2116 [#2120]: https://github.com/launchbadge/sqlx/pull/2120 [#2132]: https://github.com/launchbadge/sqlx/pull/2132 [#2133]: https://github.com/launchbadge/sqlx/pull/2133 [#2156]: https://github.com/launchbadge/sqlx/pull/2156 [#2179]: https://github.com/launchbadge/sqlx/pull/2179 [#2185]: https://github.com/launchbadge/sqlx/pull/2185 [#2189]: https://github.com/launchbadge/sqlx/pull/2189 [#2193]: https://github.com/launchbadge/sqlx/pull/2193 [#2200]: https://github.com/launchbadge/sqlx/pull/2200 [#2213]: https://github.com/launchbadge/sqlx/pull/2213 [#2222]: https://github.com/launchbadge/sqlx/pull/2222 [#2224]: https://github.com/launchbadge/sqlx/pull/2224 [#2253]: https://github.com/launchbadge/sqlx/pull/2253 [#2256]: https://github.com/launchbadge/sqlx/pull/2256 [#2271]: https://github.com/launchbadge/sqlx/pull/2271 [#2282]: https://github.com/launchbadge/sqlx/pull/2282 [#2285]: https://github.com/launchbadge/sqlx/pull/2285 [#2319]: https://github.com/launchbadge/sqlx/pull/2319 [#2352]: https://github.com/launchbadge/sqlx/pull/2352 [#2355]: https://github.com/launchbadge/sqlx/pull/2355 [#2363]: https://github.com/launchbadge/sqlx/pull/2363 [#2365]: https://github.com/launchbadge/sqlx/pull/2365 [#2366]: https://github.com/launchbadge/sqlx/pull/2366 [#2367]: https://github.com/launchbadge/sqlx/pull/2367 [#2369]: https://github.com/launchbadge/sqlx/pull/2369 [#2371]: https://github.com/launchbadge/sqlx/pull/2371 [#2373]: https://github.com/launchbadge/sqlx/pull/2373 [#2376]: https://github.com/launchbadge/sqlx/pull/2376 [#2378]: https://github.com/launchbadge/sqlx/pull/2378 [#2379]: https://github.com/launchbadge/sqlx/pull/2379 [#2387]: https://github.com/launchbadge/sqlx/pull/2387 [#2393]: https://github.com/launchbadge/sqlx/pull/2393 [#2398]: https://github.com/launchbadge/sqlx/pull/2398 [#2400]: https://github.com/launchbadge/sqlx/pull/2400 [#2402]: https://github.com/launchbadge/sqlx/pull/2402 [#2408]: https://github.com/launchbadge/sqlx/pull/2408 [#2409]: https://github.com/launchbadge/sqlx/pull/2409 [#2413]: https://github.com/launchbadge/sqlx/pull/2413 [#2420]: https://github.com/launchbadge/sqlx/pull/2420 [#2440]: https://github.com/launchbadge/sqlx/pull/2440 [#2445]: https://github.com/launchbadge/sqlx/pull/2445 [#2453]: https://github.com/launchbadge/sqlx/pull/2453 [#2454]: https://github.com/launchbadge/sqlx/pull/2454 [#2459]: https://github.com/launchbadge/sqlx/pull/2459 [#2467]: https://github.com/launchbadge/sqlx/pull/2467 [#2469]: https://github.com/launchbadge/sqlx/pull/2469 [#2483]: https://github.com/launchbadge/sqlx/pull/2483 [#2485]: https://github.com/launchbadge/sqlx/pull/2485 [#2491]: https://github.com/launchbadge/sqlx/pull/2491 [#2496]: https://github.com/launchbadge/sqlx/pull/2496 [#2506]: https://github.com/launchbadge/sqlx/pull/2506 [#2507]: https://github.com/launchbadge/sqlx/pull/2507 [#2508]: https://github.com/launchbadge/sqlx/pull/2508 [#2515]: https://github.com/launchbadge/sqlx/pull/2515 [#2519]: https://github.com/launchbadge/sqlx/pull/2519 [#2554]: https://github.com/launchbadge/sqlx/pull/2554 [#2559]: https://github.com/launchbadge/sqlx/pull/2559 [#2563]: https://github.com/launchbadge/sqlx/pull/2563 [#2564]: https://github.com/launchbadge/sqlx/pull/2564 [#2565]: https://github.com/launchbadge/sqlx/pull/2565 [#2569]: https://github.com/launchbadge/sqlx/pull/2569 [#2570]: https://github.com/launchbadge/sqlx/pull/2570 [#2572]: https://github.com/launchbadge/sqlx/pull/2572 [#2573]: https://github.com/launchbadge/sqlx/pull/2573 [#2574]: https://github.com/launchbadge/sqlx/pull/2574 ## 0.6.3 - 2023-03-21 This is a hotfix to address the breakage caused by transitive dependencies upgrading to `syn = "2"`. We set `default-features = false` for our dependency on `syn = "1"` to be good crates.io citizens, but failed to enable the features we actually used, which went undetected because we transitively depended on `syn` with the default features enabled through other crates, and so they were also on for us because features are additive. When those other dependencies upgraded to `syn = "2"` it was no longer enabling those features for us, and so compilation broke for projects that don't also depend on `syn = "1"`, transitively or otherwise. There is no PR for this fix as there was no longer a dedicated development branch for `0.6`, but discussion can be found in [issue #2418]. As of this release, the `0.7` release is in alpha and so development is no longer occurring against `0.6`. This fix will be forward-ported to `0.7`. [issue #2418]: https://github.com/launchbadge/sqlx/issues/2418 ## 0.6.2 - 2022-09-14 [25 pull requests][0.6.2-prs] were merged this release cycle. ### Added * [[#1081]]: Add `try_from` attribute for `FromRow` derive [[@zzhengzhuo]] * Exemplifies "out of sight, out of mind." It's surprisingly easy to forget about PRs when they get pushed onto the second page. We'll be sure to clean out the backlog for 0.7.0. * [[#2014]]: Support additional SQLCipher options in SQLite driver. [[@szymek156]] * [[#2052]]: Add issue templates [[@abonander]] * [[#2053]]: Add documentation for `IpAddr` support in Postgres [[@rakshith-ravi]] * [[#2062]]: Add extension support for SQLite [[@bradfier]] * [[#2063]]: customizable db locking during migration [[@fuzzbuck]] ### Changed * [[#2025]]: Bump sqlformat to 2.0 [[@NSMustache]] * [[#2056]]: chore: Switch to sha1 crate [[@stoically]] * [[#2071]]: Use cargo check consistently in `prepare` [[@cycraig]] ### Fixed * [[#1991]]: Ensure migration progress is not lost for Postgres, MySQL and SQLite. [[@crepererum]] * [[#2023]]: Fix expansion of `#[sqlx(flatten)]` for `FromRow` derive [[@RustyYato]] * [[#2028]]: Use fully qualified path when forwarding to `#[test]` from `#[sqlx::test]` [[@alexander-jackson]] * [[#2040]]: Fix typo in `FromRow` docs [[@zlidner]] * [[#2046]]: added flag for PIPES_AS_CONCAT connection setting for MySQL to fix #2034 [[@marcustut]] * [[#2055]]: Use unlock notify also on `sqlite3_exec` [[@madadam]] * [[#2057]]: Make begin,commit,rollback cancel-safe in sqlite [[@madadam]] * [[#2058]]: fix typo in documentation [[@lovasoa]] * [[#2067]]: fix(docs): close code block in query_builder.rs [[@abonander]] * [[#2069]]: Fix `prepare` race condition in workspaces [[@cycraig]]\ * NOTE: this changes the directory structure under `target/` that `cargo sqlx prepare` depends on. If you use offline mode in your workflow, please rerun `cargo install sqlx-cli` to upgrade. * [[#2072]]: SqliteConnectOptions typo [[@fasterthanlime]] * [[#2074]]: fix: mssql uses unsigned for tinyint instead of signed [[@he4d]] * [[#2081]]: close unnamed portal after each executed extended query [[@DXist]] * [[#2086]]: PgHasArrayType for transparent types fix. [[@Wopple]] * NOTE: this is a breaking change and has been postponed to 0.7.0. * [[#2089]]: fix: Remove default chrono dep on time for sqlx-cli [[@TravisWhitehead]] * [[#2091]]: Sqlite explain plan log efficiency [[@tyrelr]] [0.6.2-prs]: https://github.com/launchbadge/sqlx/pulls?q=is%3Apr+is%3Aclosed+merged%3A2022-08-04..2022-09-14+ [#1081]: https://github.com/launchbadge/sqlx/pull/1081 [#1991]: https://github.com/launchbadge/sqlx/pull/1991 [#2014]: https://github.com/launchbadge/sqlx/pull/2014 [#2023]: https://github.com/launchbadge/sqlx/pull/2023 [#2025]: https://github.com/launchbadge/sqlx/pull/2025 [#2028]: https://github.com/launchbadge/sqlx/pull/2028 [#2040]: https://github.com/launchbadge/sqlx/pull/2040 [#2046]: https://github.com/launchbadge/sqlx/pull/2046 [#2052]: https://github.com/launchbadge/sqlx/pull/2052 [#2053]: https://github.com/launchbadge/sqlx/pull/2053 [#2055]: https://github.com/launchbadge/sqlx/pull/2055 [#2056]: https://github.com/launchbadge/sqlx/pull/2056 [#2057]: https://github.com/launchbadge/sqlx/pull/2057 [#2058]: https://github.com/launchbadge/sqlx/pull/2058 [#2062]: https://github.com/launchbadge/sqlx/pull/2062 [#2063]: https://github.com/launchbadge/sqlx/pull/2063 [#2067]: https://github.com/launchbadge/sqlx/pull/2067 [#2069]: https://github.com/launchbadge/sqlx/pull/2069 [#2071]: https://github.com/launchbadge/sqlx/pull/2071 [#2072]: https://github.com/launchbadge/sqlx/pull/2072 [#2074]: https://github.com/launchbadge/sqlx/pull/2074 [#2081]: https://github.com/launchbadge/sqlx/pull/2081 [#2086]: https://github.com/launchbadge/sqlx/pull/2086 [#2089]: https://github.com/launchbadge/sqlx/pull/2089 [#2091]: https://github.com/launchbadge/sqlx/pull/2091 ## 0.6.1 - 2022-08-02 [33 pull requests][0.6.1-prs] were merged this release cycle. ### Added * [[#1495]]: Add example for manual implementation of the `FromRow` trait [[@Erik1000]] * [[#1822]]: (Postgres) Add support for `std::net::IpAddr` [[@meh]] * Decoding returns an error if the `INET` value in Postgres is a prefix and not a full address (`/32` for IPv4, `/128` for IPv6). * [[#1865]]: Add SQLite support for the `time` crate [[@johnbcodes]] * [[#1902]]: Add an example of how to use `QueryBuilder::separated()` [[@sbeckeriv]] * [[#1917]]: Added docs for `sqlx::types::Json` [[@jayy-lmao]] * [[#1919]]: Implement `Clone` for `PoolOptions` [[@Thomasdezeeuw]] * [[#1953]]: Support Rust arrays in Postgres [[@e00E]] * [[#1954]]: Add `push_tuples` for `QueryBuilder` [[@0xdeafbeef]] * [[#1959]]: Support `#[sqlx(flatten)]` attribute in `FromRow` [[@TheoOiry]] * [[#1967]]: Add example with external query files [[@JoeyMckenzie]] * [[#1985]]: Add `query_builder::Separated::push_bind_unseparated()` [[@0xdeafbeef]] * [[#2001]]: Implement `#[sqlx::test]` for general use * Includes automatic database management, migration and fixture application. * Drops support for end-of-lifed database versions, see PR for details. * [[#2005]]: `QueryBuilder` improvements [[@abonander]] * Raw SQL getters, new method to build `QueryAs` instead of `Query`. * [[#2013]]: (SQLite) Allow VFS to be set as URL query parameter [[@liningpan]] ### Changed * [[#1679]]: refactor: alias actix-* features to their equivalent tokio-* features [[@robjtede]] * [[#1906]]: replaced all uses of "uri" to "url" [[@RomainStorai]] * [[#1965]]: SQLite improvements [[@abonander]] * [[#1977]]: Docs: clarify relationship between `query_as!()` and `FromRow` [[@abonander]] * [[#2003]]: Replace `dotenv` with `dotenvy` [[@abonander]] ### Fixed * [[#1802]]: Try avoiding a full clean in `cargo sqlx prepare --merged` [[@LovecraftianHorror]] * [[#1848]]: Fix type info access in `Any` database driver [[@raviqqe]] * [[#1910]]: Set `CARGO_TARGET_DIR` when compiling queries [[@sedrik]] * [[#1915]]: Pool: fix panic when using callbacks [[@abonander]] * [[#1930]]: Don't cache SQLite connection for macros [[@LovecraftianHorror]] * [[#1948]]: Fix panic in Postgres `BYTEA` decode [[@e00E]] * [[#1955]]: Fix typo in FAQ [[@kenkoooo]] * [[#1968]]: (Postgres) don't panic if `S` or `V` notice fields are not UTF-8 [[@abonander]] * [[#1969]]: Fix sqlx-cli build [[@ivan]] * [[#1974]]: Use the `rust-cache` action for CI [[@abonander]] * [[#1988]]: Agree on a single default runtime for the whole workspace [[@crepererum]] * [[#1989]]: Fix panics in `PgListener` [[@crepererum]] * [[#1990]]: Switch `master` to `main` in docs [[@crepererum]] * The change had already been made in the repo, the docs were out of date. * [[#1993]]: Update versions in quickstart examples in README [[@UramnOIL]] [0.6.1-prs]: https://github.com/launchbadge/sqlx/pulls?page=1&q=is%3Apr+is%3Aclosed+merged%3A2022-06-17..2022-08-02 [#1906]: https://github.com/launchbadge/sqlx/pull/1906 [#1495]: https://github.com/launchbadge/sqlx/pull/1495 [#1679]: https://github.com/launchbadge/sqlx/pull/1679 [#1802]: https://github.com/launchbadge/sqlx/pull/1802 [#1822]: https://github.com/launchbadge/sqlx/pull/1822 [#1848]: https://github.com/launchbadge/sqlx/pull/1848 [#1865]: https://github.com/launchbadge/sqlx/pull/1865 [#1902]: https://github.com/launchbadge/sqlx/pull/1902 [#1910]: https://github.com/launchbadge/sqlx/pull/1910 [#1915]: https://github.com/launchbadge/sqlx/pull/1915 [#1917]: https://github.com/launchbadge/sqlx/pull/1917 [#1919]: https://github.com/launchbadge/sqlx/pull/1919 [#1930]: https://github.com/launchbadge/sqlx/pull/1930 [#1948]: https://github.com/launchbadge/sqlx/pull/1948 [#1953]: https://github.com/launchbadge/sqlx/pull/1953 [#1954]: https://github.com/launchbadge/sqlx/pull/1954 [#1955]: https://github.com/launchbadge/sqlx/pull/1955 [#1959]: https://github.com/launchbadge/sqlx/pull/1959 [#1965]: https://github.com/launchbadge/sqlx/pull/1965 [#1967]: https://github.com/launchbadge/sqlx/pull/1967 [#1968]: https://github.com/launchbadge/sqlx/pull/1968 [#1969]: https://github.com/launchbadge/sqlx/pull/1969 [#1974]: https://github.com/launchbadge/sqlx/pull/1974 [#1977]: https://github.com/launchbadge/sqlx/pull/1977 [#1985]: https://github.com/launchbadge/sqlx/pull/1985 [#1988]: https://github.com/launchbadge/sqlx/pull/1988 [#1989]: https://github.com/launchbadge/sqlx/pull/1989 [#1990]: https://github.com/launchbadge/sqlx/pull/1990 [#1993]: https://github.com/launchbadge/sqlx/pull/1993 [#2001]: https://github.com/launchbadge/sqlx/pull/2001 [#2003]: https://github.com/launchbadge/sqlx/pull/2003 [#2005]: https://github.com/launchbadge/sqlx/pull/2005 [#2013]: https://github.com/launchbadge/sqlx/pull/2013 ## 0.6.0 - 2022-06-16 This release marks the end of the 0.5.x series of releases and contains a number of breaking changes, mainly to do with backwards-incompatible dependency upgrades. As we foresee many more of these in the future, we [surveyed the community] on how to handle this; the consensus appears to be "just release breaking changes more often." As such, we expect the 0.6.x release series to be a shorter one. [39 pull requests(!)][0.6.0-prs] (not counting "prepare 0.5.12 release", of course) were merged this release cycle. ### Breaking * [[#1384]]: (Postgres) Move `server_version_num` from trait to inherent impl [[@AtkinsChang]] * [[#1426]]: Bump `ipnetwork` to 0.19 [[@paolobarbolini]] * [[#1455]]: Upgrade `time` to 0.3 [[@paolobarbolini]] * [[#1505]]: Upgrade `rustls` to 0.20 [[@paolobarbolini]] * Fortunately, future upgrades should not be breaking as `webpki` is no longer exposed in the API. * [[#1529]]: Upgrade `bigdecimal` to 0.3 [[@e00E]] * [[#1602]]: postgres: use `Oid` everywhere instead of `u32` [[@paolobarbolini]] * This drops the `Type`, `Decode`, `Encode` impls for `u32` for Postgres as it was misleading. Postgres doesn't support unsigned ints without using an extension. These impls were decoding Postgres `OID`s as bare `u32`s without any context (and trying to bind a `u32` to a query would produce an `OID` value in SQL). This changes that to use a newtype instead, for clarity. * [[#1612]]: Make all `ConnectOptions` types cloneable [[@05storm26]] * [[#1618]]: SQLite `chrono::DateTime` timezone fix [[@05storm26]] * `DateTime` will be stored in SQLite with the correct timezone instead of always in UTC. This was flagged as a "potentially breaking change" since it changes how dates are sent to SQLite. * [[#1733]]: Update `git2` to 0.14 [[@joshtriplett]] * [[#1734]]: Make `PgLTree::push()` infallible and take `PgLTreeLabel` directly [[@sebpuetz]] * [[#1785]]: Fix Rust type for SQLite `REAL` [[@pruthvikar]] * Makes the macros always map a `REAL` column to `f64` instead of `f32` as SQLite uses **only** 64-bit floats. * [[#1816]]: Improve SQLite support for sub-queries and CTEs [[@tyrelr]] * This likely will change the generated code for some invocations `sqlx::query!()` with SQLite. * [[#1821]]: Update `uuid` crate to v1 [[@paolobarbolini]] * [[#1901]]: Pool fixes and breaking changes [[@abonander]] * Renamed `PoolOptions::connect_timeout` to `acquire_timeout` for clarity. * Changed the expected signatures for `PoolOptions::after_connect`, `before_acquire`, `after_release` * Changed the signature for `Pool::close()` slightly * Now eagerly starts the pool closing, `.await`ing is only necessary if you want to ensure a graceful shutdown. * Deleted `PoolConnection::release()` which was previously deprecated in favor of `PoolConnection::detach()`. * Fixed connections getting leaked even when calling `.close()`. * [[#1748]]: Derive `PgHasArrayType` for `#[sqlx(transparent)]` types [[@carols10cents]] * This change was released with 0.5.12 but [we didn't realize it was a breaking change] at the time. It was reverted in 0.5.13 and postponed until this release. ### Added * [[#1843]]: Expose some useful methods on `PgValueRef` [[@mfreeborn]] * [[#1889]]: SQLx-CLI: add `--connect-timeout` [[@abonander]] * Adds a default 10 second connection timeout to all commands. * [[#1890]]: Added test for mssql LoginAck [[@walf443]] * [[#1891]]: Added test for mssql ProtocolInfo [[@walf443]] * [[#1892]]: Added test for mssql ReturnValue [[@walf443]] * [[#1895]]: Add support for `i16` to `Any` driver [[@EthanYuan]] * [[#1897]]: Expose `ConnectOptions` and `PoolOptions` on `Pool` and database name on `PgConnectOptions` [[@Nukesor]] ### Changed * [[#1782]]: Reuse a cached DB connection instead of always opening a new one for `sqlx-macros` [[@LovecraftianHorror]] * [[#1807]]: Bump remaining dependencies [[@paolobarbolini]] * [[#1808]]: Update to edition 2021 [[@paolobarbolini]] * Note that while SQLx [does not officially track an MSRV] and only officially supports the latest stable Rust, this effectively places a lower bound of 1.56.0 on the range of versions it may work with. * [[#1823]]: (sqlx-macros) Ignore deps when getting metadata for workspace root [[@LovecraftianHorror]] * [[#1831]]: Update `crc` to 3.0 [[@djc]] * [[#1887]]: query_as: don't stop stream after decoding error [[@lovasoa]] ### Fixed * [[#1814]]: SQLx-cli README: move `Usage` to the same level as `Install` [[@tobymurray]] * [[#1815]]: SQLx-cli README: reword "building in offline mode" [[@tobymurray]] * [[#1818]]: Trim `[]` from host string before passing to TcpStream [[@smonv]] * This fixes handling of database URLs with IPv6 hosts. * [[#1842]]: Fix usage of `serde_json` in macros [[@mfreeborn]] * [[#1855]]: Postgres: fix panics on unknown type OID when decoding [[@demurgos]] * [[#1856]]: MySQL: support COLLATE_UTF8MB4_0900_AI_CI [[@scottwey]] * Fixes the MySQL driver thinking text columns are bytestring columns when querying against a Planetscale DB. * [[#1861]]: MySQL: avoid panic when streaming packets are empty [[@e-rhodes]] * [[#1863]]: Fix nullability check for inner joins in Postgres [[@OskarPersson]] * [[#1881]]: Fix `field is never read` warnings on Postgres test [[@walf443]] * [[#1882]]: Fix `unused result must be used` warnings [[@walf443]] * [[#1888]]: Fix migration checksum comparison during `sqlx migrate info` [[@mdtusz]] * [[#1894]]: Fix typos [[@kianmeng]] [surveyed the community]: https://github.com/launchbadge/sqlx/issues/1796 [0.6.0-prs]: https://github.com/launchbadge/sqlx/pulls?page=2&q=is%3Apr+is%3Amerged+merged%3A2022-04-14..2022-06-16 [does not officially track an MSRV]: /FAQ.md#what-versions-of-rust-does-sqlx-support-what-is-sqlxs-msrv [we didn't realize it was a breaking change]: https://github.com/launchbadge/sqlx/pull/1800#issuecomment-1099898932 [#1384]: https://github.com/launchbadge/sqlx/pull/1384 [#1426]: https://github.com/launchbadge/sqlx/pull/1426 [#1455]: https://github.com/launchbadge/sqlx/pull/1455 [#1505]: https://github.com/launchbadge/sqlx/pull/1505 [#1529]: https://github.com/launchbadge/sqlx/pull/1529 [#1602]: https://github.com/launchbadge/sqlx/pull/1602 [#1612]: https://github.com/launchbadge/sqlx/pull/1612 [#1618]: https://github.com/launchbadge/sqlx/pull/1618 [#1733]: https://github.com/launchbadge/sqlx/pull/1733 [#1734]: https://github.com/launchbadge/sqlx/pull/1734 [#1782]: https://github.com/launchbadge/sqlx/pull/1782 [#1785]: https://github.com/launchbadge/sqlx/pull/1785 [#1807]: https://github.com/launchbadge/sqlx/pull/1807 [#1808]: https://github.com/launchbadge/sqlx/pull/1808 [#1814]: https://github.com/launchbadge/sqlx/pull/1814 [#1815]: https://github.com/launchbadge/sqlx/pull/1815 [#1816]: https://github.com/launchbadge/sqlx/pull/1816 [#1818]: https://github.com/launchbadge/sqlx/pull/1818 [#1821]: https://github.com/launchbadge/sqlx/pull/1821 [#1823]: https://github.com/launchbadge/sqlx/pull/1823 [#1831]: https://github.com/launchbadge/sqlx/pull/1831 [#1842]: https://github.com/launchbadge/sqlx/pull/1842 [#1843]: https://github.com/launchbadge/sqlx/pull/1843 [#1855]: https://github.com/launchbadge/sqlx/pull/1855 [#1856]: https://github.com/launchbadge/sqlx/pull/1856 [#1861]: https://github.com/launchbadge/sqlx/pull/1861 [#1863]: https://github.com/launchbadge/sqlx/pull/1863 [#1881]: https://github.com/launchbadge/sqlx/pull/1881 [#1882]: https://github.com/launchbadge/sqlx/pull/1882 [#1887]: https://github.com/launchbadge/sqlx/pull/1887 [#1888]: https://github.com/launchbadge/sqlx/pull/1888 [#1889]: https://github.com/launchbadge/sqlx/pull/1889 [#1890]: https://github.com/launchbadge/sqlx/pull/1890 [#1891]: https://github.com/launchbadge/sqlx/pull/1891 [#1892]: https://github.com/launchbadge/sqlx/pull/1892 [#1894]: https://github.com/launchbadge/sqlx/pull/1894 [#1895]: https://github.com/launchbadge/sqlx/pull/1895 [#1897]: https://github.com/launchbadge/sqlx/pull/1897 [#1901]: https://github.com/launchbadge/sqlx/pull/1901 ## 0.5.13 - 2022-04-15 This is a hotfix that reverts [#1748] as that was an accidental breaking change: the generated `PgHasArrayType` impl conflicts with manual impls of the trait. This change will have to wait for 0.6.0. ## 0.5.12 - 2022-04-13 (Yanked; use 0.5.13) [27 pull requests][0.5.12-prs] were merged this release cycle. ### Added * [[#1641]]: Postgres: Convenient wrapper for advisory locks [[@abonander]] * [[#1675]]: Add function to undo migrations [[@jdrouet]] * [[#1722]]: Postgres: implement `PgHasArrayType` for `serde_json::{Value, RawValue}` [[@abreis]] * [[#1736]]: Derive `Clone` for `MySqlArguments` and `MssqlArguments` [[@0xdeafbeef]] * [[#1748]]: Derive `PgHasArrayType` for `#[sqlx(transparent)]` types [[@carols10cents]] * [[#1754]]: Include affected rows alongside returned rows in query logging [[@david-mcgillicuddy-moixa]] * [[#1757]]: Implement `Type` for `Cow` for MySQL, MSSQL and SQLite [[@ipetkov]] * [[#1769]]: sqlx-cli: add `--source` to migration subcommands [[@pedromfedricci]] * [[#1774]]: Postgres: make `extra_float_digits` settable [[@abonander]] * Can be set to `None` for Postgres or third-party database servers that don't support the option. * [[#1776]]: Implement close-event notification for Pool [[@abonander]] * Also fixes `PgListener` preventing `Pool::close()` from resolving. * [[#1780]]: Implement query builder [[@crajcan]] * See also [[#1790]]: Document and expand query builder [[@abonander]] * [[#1781]]: Postgres: support `NUMERIC[]` using `decimal` feature [[@tm-drtina]] * [[#1784]]: SQLite: add `FromStr`, `Copy`, `PartialEq`, `Eq` impls for options enums [[@andrewwhitehead]] ### Changed * [[#1625]]: Update RustCrypto crates [[@paolobarbolini]] * [[#1725]]: Update `heck` to 0.4 [[@paolobarbolini]] * [[#1738]]: Update `regex` [[@Dylan-DPC]] * [[#1763]]: SQLite: update `libsqlite3-sys` [[@espindola]] ### Fixed * [[#1719]]: Fix a link in `query!()` docs [[@vbmade2000]] * [[#1731]]: Postgres: fix option passing logic [[@liushuyu]] * [[#1735]]: sqlx-cli: pass `DATABASE_URL` to command spawned in `prepare` [[@LovecraftianHorror]] * [[#1741]]: Postgres: fix typo in `TSTZRANGE` [[@mgrachev]] * [[#1761]]: Fix link from `QueryAs` to `query_as()` in docs [[@mgrachev]] * [[#1786]]: MySQL: silence compile warnings for unused fields [[@andrewwhitehead]] * [[#1789]]: SQLite: fix left-joins breaking `query!()` macros [[@tyrelr]] * [[#1791]]: Postgres: fix newline parsing of `.pgpass` files [[@SebastienGllmt]] * [[#1799]]: `PoolConnection`: don't leak connection permit if drop task fails to run [[@abonander]] [#1625]: https://github.com/launchbadge/sqlx/pull/1625 [#1641]: https://github.com/launchbadge/sqlx/pull/1641 [#1675]: https://github.com/launchbadge/sqlx/pull/1675 [#1719]: https://github.com/launchbadge/sqlx/pull/1719 [#1722]: https://github.com/launchbadge/sqlx/pull/1722 [#1725]: https://github.com/launchbadge/sqlx/pull/1725 [#1731]: https://github.com/launchbadge/sqlx/pull/1731 [#1735]: https://github.com/launchbadge/sqlx/pull/1735 [#1736]: https://github.com/launchbadge/sqlx/pull/1736 [#1738]: https://github.com/launchbadge/sqlx/pull/1738 [#1741]: https://github.com/launchbadge/sqlx/pull/1741 [#1748]: https://github.com/launchbadge/sqlx/pull/1748 [#1754]: https://github.com/launchbadge/sqlx/pull/1754 [#1757]: https://github.com/launchbadge/sqlx/pull/1757 [#1761]: https://github.com/launchbadge/sqlx/pull/1761 [#1763]: https://github.com/launchbadge/sqlx/pull/1763 [#1769]: https://github.com/launchbadge/sqlx/pull/1769 [#1774]: https://github.com/launchbadge/sqlx/pull/1774 [#1776]: https://github.com/launchbadge/sqlx/pull/1776 [#1780]: https://github.com/launchbadge/sqlx/pull/1780 [#1781]: https://github.com/launchbadge/sqlx/pull/1781 [#1784]: https://github.com/launchbadge/sqlx/pull/1784 [#1786]: https://github.com/launchbadge/sqlx/pull/1786 [#1789]: https://github.com/launchbadge/sqlx/pull/1789 [#1790]: https://github.com/launchbadge/sqlx/pull/1790 [#1791]: https://github.com/launchbadge/sqlx/pull/1791 [#1799]: https://github.com/launchbadge/sqlx/pull/1799 [0.5.12-prs]: https://github.com/launchbadge/sqlx/pulls?q=is%3Apr+is%3Amerged+merged%3A2022-02-19..2022-04-13 ## 0.5.11 - 2022-02-17 [20 pull requests][0.5.11-prs] were merged this release cycle. ### Added * [[#1610]]: Allow converting `AnyConnectOptions` to a specific `ConnectOptions` [[@05storm26]] * [[#1652]]: Implement `From` for `AnyConnection` [[@genusistimelord]] * [[#1658]]: Handle `SQLITE_LOCKED` [[@madadam]] * [[#1665]]: Document offline mode usage with feature flags [[@sedrik]] * [[#1680]]: Show checksum mismatches in `sqlx migrate info` [[@ifn3]] * [[#1685]]: Add tip for setting `opt-level` for `sqlx-macros` [[@LovecraftianHorror]] * [[#1687]]: Docs: `Acquire` examples and alternative [[@stoically]] * [[#1696]]: Postgres: support for `ltree` [[@cemoktra]] * [[#1710]]: Postgres: support for `lquery` [[@cemoktra]] ### Changed * [[#1605]]: Remove unused dependencies [[@paolobarbolini]] * [[#1606]]: Add target context to Postgres `NOTICE` logs [[@dbeckwith]] * [[#1684]]: Macros: Cache parsed `sqlx-data.json` instead of reparsing [[@LovecraftianHorror]] ### Fixed * [[#1608]]: Drop worker shared state in shutdown (SQLite) [[@andrewwhitehead]] * [[#1619]]: Docs(macros): remove sentences banning usage of `as _` [[@k-jun]] * [[#1626]]: Simplify `cargo-sqlx` command-line definition [[@tranzystorek-io]] * [[#1636]]: Fix and extend Postgres transaction example [[@taladar]] * [[#1657]]: Fix typo in macro docs [[@p9s]] * [[#1661]]: Fix binding `Option` for `Any` driver [[@ArGGu]] * [[#1667]]: MySQL: Avoid panicking if packet is empty [[@nappa85]] * [[#1692]]: Postgres: Fix power calculation when encoding `BigDecimal` into `NUMERIC` [[@VersBinarii]] Additionally, we have introduced two mitigations for [the issue of the cyclic dependency on `ahash`][aHash#95]: * We re-downgraded our version requirement on `indexmap` from `1.7.0` back to `1.6.2` so users can pin it to that version [as recommended in aHash#95][ahash-fix]. * [This was regressed accidentally during a sweeping dependency upgrade before the last release][indexmap-regression], sorry about that. * Thanks to the work of [@LovecraftianHorror] in [#1684], we no longer require the `preserve_order` feature of `serde_json` which gives users another place to break the cycle by simply not enabling that feature. * This may introduce extra churn in Git diffs for `sqlx-data.json`, however. If this is an issue for you but the dependency cycle isn't, you can re-enable the `preserve_order` feature: ```toml [dependencies] serde_json = { version = "1", features = ["preserve_order"] } ``` [aHash#95]: https://github.com/tkaitchuck/aHash/issues/95 [ahash-fix]: https://github.com/tkaitchuck/aHash/issues/95#issuecomment-874150078 [indexmap-regression]: https://github.com/launchbadge/sqlx/pull/1603#issuecomment-1010827637 [#1605]: https://github.com/launchbadge/sqlx/pull/1605 [#1606]: https://github.com/launchbadge/sqlx/pull/1606 [#1608]: https://github.com/launchbadge/sqlx/pull/1608 [#1610]: https://github.com/launchbadge/sqlx/pull/1610 [#1619]: https://github.com/launchbadge/sqlx/pull/1619 [#1626]: https://github.com/launchbadge/sqlx/pull/1626 [#1636]: https://github.com/launchbadge/sqlx/pull/1636 [#1652]: https://github.com/launchbadge/sqlx/pull/1652 [#1657]: https://github.com/launchbadge/sqlx/pull/1657 [#1658]: https://github.com/launchbadge/sqlx/pull/1658 [#1661]: https://github.com/launchbadge/sqlx/pull/1661 [#1665]: https://github.com/launchbadge/sqlx/pull/1665 [#1667]: https://github.com/launchbadge/sqlx/pull/1667 [#1680]: https://github.com/launchbadge/sqlx/pull/1680 [#1684]: https://github.com/launchbadge/sqlx/pull/1684 [#1685]: https://github.com/launchbadge/sqlx/pull/1685 [#1687]: https://github.com/launchbadge/sqlx/pull/1687 [#1692]: https://github.com/launchbadge/sqlx/pull/1692 [#1696]: https://github.com/launchbadge/sqlx/pull/1696 [#1710]: https://github.com/launchbadge/sqlx/pull/1710 [0.5.11-prs]: https://github.com/launchbadge/sqlx/pulls?q=is%3Apr+is%3Amerged+merged%3A2021-12-30..2022-02-17 ## 0.5.10 - 2021-12-29 [A whopping 31 pull requests][0.5.10-prs] were merged this release cycle! According to this changelog, we saw 18 new contributors! However, some of these folks may have missed getting mentioned in previous entries since we only listed highlights. To avoid anyone feeling left out, I put in the effort this time and tried to list every single one here. ### Added * [[#1228]]: Add `Pool::any_kind()` [[@nitnelave]] * [[#1343]]: Add `Encode/Decode` impl for `Cow<'_, str>` [[@Drevoed]] * [[#1474]]: Derive `Clone`, `Copy` for `AnyKind` [[@yuyawk]] * [[#1497]]: Update FAQ to explain how to configure docs.rs to build a project using SQLx [[@russweas]] * [[#1498]]: Add description of migration file structure to `migrate!()` docs [[@zbigniewzolnierowicz]] * [[#1508]]: Add `.persistent(bool)` to `QueryAs`, `QueryScalar` [[@akiradeveloper]] * [[#1514]]: Add support for serialized threading mode to SQLite [[@LLBlumire]] * [[#1523]]: Allow `rust_decimal::Decimal` in `PgRange` [[@meh]] * [[#1539]]: Support `PGOPTIONS` and adding custom configuration options in `PgConnectOptions` [[@liushuyu]] * [[#1562]]: Re-export `either::Either` used by `Executor::fetch_many()` [[@DoumanAsh]] * [[#1584]]: Add feature to use RusTLS instead of `native-tls` for `sqlx-cli` [[@SonicZentropy]] * [[#1592]]: Add `AnyConnection::kind()` [[@05storm26]] ### Changes * [[#1385]]: Rewrite Postgres array handling to reduce boilerplate and allow custom types [[@jplatte]] * [[#1479]]: Remove outdated mention of `runtime-async-std-native-tls` as the default runtime in README.md [[@yerke]] * [[#1526]]: Revise `Pool` docs in a couple places [[@abonander]] * [[#1535]]: Bump `libsqlite-sys` to `0.23.1` [[@nitsky]] * [[#1551]]: SQLite: make worker thread responsible for all FFI calls [[@abonander]] * If you were encountering segfaults with the SQLite driver, there's a good chance this will fix it! * [[#1557]]: CI: test with Postgres 14 [[@paolobarbolini]] * [[#1571]]: Make `whoami` dep optional, only pull it in for Postgres [[@joshtriplett]] * [[#1572]]: Update `rsa` crate to 0.5 [[@paolobarbolini]] * [[#1591]]: List SeaORM as an ORM option in the README [[@kunjee17]] * [[#1601]]: Update `itoa` and `dirs` [[@paolobarbolini]] ### Fixes * [[#1475]]: Fix panic when converting a negative `chrono::Duration` to `PgInterval` [[@yuyawk]] * [[#1483]]: Fix error when decoding array of custom types from Postgres [[@demurgos] * [[#1501]]: Reduce `indexmap` version requirement to `1.6.2` [[@dimfeld]] * [[#1511]]: Fix element type given to Postgres for arrays of custom enums [[@chesedo]] * [[#1517]]: Fix mismatched type errors in MySQL type tests [[@abonander]] * [[#1537]]: Fix missing re-export of `PgCopyIn` [[@akiradeveloper]] * [[#1566]]: Match `~/.pgpass` password after URL parsing and fix user and database ordering [[@D1plo1d]] * [[#1582]]: `cargo sqlx prepare`: Append to existing `RUSTFLAGS` instead of overwriting [[@tkintscher]] * [[#1587]]: SQLite: if set, send `PRAGMA key` on a new connection before anything else. [[@parazyd]] * This should fix problems with being unable to open databases using SQLCipher. [#1228]: https://github.com/launchbadge/sqlx/pull/1228 [#1343]: https://github.com/launchbadge/sqlx/pull/1343 [#1385]: https://github.com/launchbadge/sqlx/pull/1385 [#1474]: https://github.com/launchbadge/sqlx/pull/1474 [#1475]: https://github.com/launchbadge/sqlx/pull/1475 [#1479]: https://github.com/launchbadge/sqlx/pull/1479 [#1483]: https://github.com/launchbadge/sqlx/pull/1483 [#1497]: https://github.com/launchbadge/sqlx/pull/1497 [#1498]: https://github.com/launchbadge/sqlx/pull/1498 [#1501]: https://github.com/launchbadge/sqlx/pull/1501 [#1508]: https://github.com/launchbadge/sqlx/pull/1508 [#1511]: https://github.com/launchbadge/sqlx/pull/1511 [#1514]: https://github.com/launchbadge/sqlx/pull/1514 [#1517]: https://github.com/launchbadge/sqlx/pull/1517 [#1523]: https://github.com/launchbadge/sqlx/pull/1523 [#1526]: https://github.com/launchbadge/sqlx/pull/1526 [#1535]: https://github.com/launchbadge/sqlx/pull/1535 [#1537]: https://github.com/launchbadge/sqlx/pull/1537 [#1539]: https://github.com/launchbadge/sqlx/pull/1539 [#1551]: https://github.com/launchbadge/sqlx/pull/1551 [#1557]: https://github.com/launchbadge/sqlx/pull/1557 [#1562]: https://github.com/launchbadge/sqlx/pull/1562 [#1566]: https://github.com/launchbadge/sqlx/pull/1566 [#1571]: https://github.com/launchbadge/sqlx/pull/1571 [#1572]: https://github.com/launchbadge/sqlx/pull/1572 [#1582]: https://github.com/launchbadge/sqlx/pull/1582 [#1584]: https://github.com/launchbadge/sqlx/pull/1584 [#1587]: https://github.com/launchbadge/sqlx/pull/1587 [#1591]: https://github.com/launchbadge/sqlx/pull/1591 [#1592]: https://github.com/launchbadge/sqlx/pull/1592 [#1601]: https://github.com/launchbadge/sqlx/pull/1601 [0.5.10-prs]: https://github.com/launchbadge/sqlx/pulls?page=1&q=is%3Apr+merged%3A2021-10-02..2021-12-31+sort%3Acreated-asc ## 0.5.9 - 2021-10-01 A hotfix release to address the issue of the `sqlx` crate itself still depending on older versions of `sqlx-core` and `sqlx-macros`. No other changes from `0.5.8`. ## 0.5.8 - 2021-10-01 (Yanked; use 0.5.9) [A total of 24 pull requests][0.5.8-prs] were merged this release cycle! Some highlights: * [[#1289]] Support the `immutable` option on SQLite connections [[@djmarcin]] * [[#1295]] Support custom initial options for SQLite [[@ghassmo]] * Allows specifying custom `PRAGMA`s and overriding those set by SQLx. * [[#1345]] Initial support for Postgres `COPY FROM/TO`[[@montanalow], [@abonander]] * [[#1439]] Handle multiple waiting results correctly in MySQL [[@eagletmt]] [#1289]: https://github.com/launchbadge/sqlx/pull/1289 [#1295]: https://github.com/launchbadge/sqlx/pull/1295 [#1345]: https://github.com/launchbadge/sqlx/pull/1345 [#1439]: https://github.com/launchbadge/sqlx/pull/1439 [0.5.8-prs]: https://github.com/launchbadge/sqlx/pulls?q=is%3Apr+is%3Amerged+merged%3A2021-08-21..2021-10-01 ## 0.5.7 - 2021-08-20 * [[#1392]] use `resolve_path` when getting path for `include_str!()` [[@abonander]] * Fixes a regression introduced by [[#1332]]. * [[#1393]] avoid recursively spawning tasks in `PgListener::drop()` [[@abonander]] * Fixes a panic that occurs when `PgListener` is dropped in `async fn main()`. [#1392]: https://github.com/launchbadge/sqlx/pull/1392 [#1393]: https://github.com/launchbadge/sqlx/pull/1393 ## 0.5.6 - 2021-08-16 A large bugfix release, including but not limited to: * [[#1329]] Implement `MACADDR` type for Postgres [[@nomick]] * [[#1363]] Fix `PortalSuspended` for array of composite types in Postgres [[@AtkinsChang]] * [[#1320]] Reimplement `sqlx::Pool` internals using `futures-intrusive` [[@abonander]] * This addresses a number of deadlocks/stalls on acquiring connections from the pool. * [[#1332]] Macros: tell the compiler about external files/env vars to watch [[@abonander]] * Includes `sqlx build-script` to create a `build.rs` to watch `migrations/` for changes. * Nightly users can try `RUSTFLAGS=--cfg sqlx_macros_unstable` to tell the compiler to watch `migrations/` for changes instead of using a build script. * See the new section in the docs for `sqlx::migrate!()` for details. * [[#1351]] Fix a few sources of segfaults/errors in SQLite driver [[@abonander]] * Includes contributions from [[@link2ext]] and [[@madadam]]. * [[#1323]] Keep track of column typing in SQLite EXPLAIN parsing [[@marshoepial]] * This fixes errors in the macros when using `INSERT/UPDATE/DELETE ... RETURNING ...` in SQLite. [A total of 25 pull requests][0.5.6-prs] were merged this release cycle! [#1329]: https://github.com/launchbadge/sqlx/pull/1329 [#1363]: https://github.com/launchbadge/sqlx/pull/1363 [#1320]: https://github.com/launchbadge/sqlx/pull/1320 [#1332]: https://github.com/launchbadge/sqlx/pull/1332 [#1351]: https://github.com/launchbadge/sqlx/pull/1351 [#1323]: https://github.com/launchbadge/sqlx/pull/1323 [0.5.6-prs]: https://github.com/launchbadge/sqlx/pulls?q=is%3Apr+is%3Amerged+merged%3A2021-05-24..2021-08-17 ## 0.5.5 - 2021-05-24 - [[#1242]] Fix infinite loop at compile time when using query macros [[@toshokan]] [#1242]: https://github.com/launchbadge/sqlx/pull/1242 ## 0.5.4 - 2021-05-22 - [[#1235]] Fix compilation with rustls from an eager update to webpki [[@ETCaton]] [#1235]: https://github.com/launchbadge/sqlx/pull/1235 ## 0.5.3 - 2021-05-21 - [[#1211]] Even more tweaks and fixes to the Pool internals [[@abonander]] - [[#1213]] Add support for bytes and `chrono::NaiveDateTime` to `Any` [[@guylapid]] - [[#1224]] Add support for `chrono::DateTime` to `Any` with `MySQL` [[@NatPRoach]] - [[#1216]] Skip empty lines and comments in pgpass files [[@feikesteenbergen]] - [[#1218]] Add support for `PgMoney` to the compile-time type-checking [[@iamsiddhant05]] [#1211]: https://github.com/launchbadge/sqlx/pull/1211 [#1213]: https://github.com/launchbadge/sqlx/pull/1213 [#1216]: https://github.com/launchbadge/sqlx/pull/1216 [#1218]: https://github.com/launchbadge/sqlx/pull/1218 [#1224]: https://github.com/launchbadge/sqlx/pull/1224 ## 0.5.2 - 2021-04-15 - [[#1149]] Tweak and optimize Pool internals [[@abonander]] - [[#1132]] Remove `'static` bound on `Connection::transaction` [[@argv-minus-one]] - [[#1128]] Fix `-y` flag for `sqlx db reset -y` [[@qqwa]] - [[#1099]] [[#1097]] Truncate buffer when `BufStream` is dropped [[@Diggsey]] [#1132]: https://github.com/launchbadge/sqlx/pull/1132 [#1149]: https://github.com/launchbadge/sqlx/pull/1149 [#1128]: https://github.com/launchbadge/sqlx/pull/1128 [#1099]: https://github.com/launchbadge/sqlx/pull/1099 [#1097]: https://github.com/launchbadge/sqlx/issues/1097 ### PostgreSQL - [[#1170]] Remove `Self: Type` bounds in `Encode` / `Decode` implementations for arrays [[@jplatte]] Enables working around the lack of support for user-defined array types: ```rust #[derive(sqlx::Encode)] struct Foos<'a>(&'a [Foo]); impl sqlx::Type for Foos<'_> { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("_foo") } } query_as!( Whatever, "", Foos(&foo_vec) as _, ) ``` - [[#1141]] Use `u16::MAX` instead of `i16::MAX` for a check against the largest number of parameters in a query [[@crajcan]] - [[#1112]] Add support for `DOMAIN` types [[@demurgos]] - [[#1100]] Explicitly `UNLISTEN` before returning connections to the pool in `PgListener` [[@Diggsey]] [#1170]: https://github.com/launchbadge/sqlx/pull/1170 [#1141]: https://github.com/launchbadge/sqlx/pull/1141 [#1112]: https://github.com/launchbadge/sqlx/pull/1112 [#1100]: https://github.com/launchbadge/sqlx/pull/1100 ### SQLite - [[#1161]] Catch `SQLITE_MISUSE` on connection close and panic [[@link2xt]] - [[#1160]] Do not cast pointers to `i32` (cast to `usize`) [[@link2xt]] - [[#1156]] Reset the statement when `fetch_many` stream is dropped [[@link2xt]] [#1161]: https://github.com/launchbadge/sqlx/pull/1161 [#1160]: https://github.com/launchbadge/sqlx/pull/1160 [#1156]: https://github.com/launchbadge/sqlx/pull/1156 ## 0.5.1 - 2021-02-04 - Update sqlx-rt to 0.3. ## 0.5.0 - 2021-02-04 ### Changes - [[#983]] [[#1022]] Upgrade async runtime dependencies [[@seryl], [@ant32], [@jplatte], [@robjtede]] - tokio 1.0 - actix-rt 2.0 - [[#854]] Allow chaining `map` and `try_map` [[@jplatte]] Additionally enables calling these combinators with the macros: ```rust let ones: Vec = query!("SELECT 1 as foo") .map(|row| row.foo) .fetch_all(&mut conn).await?; ``` - [[#940]] Rename the `#[sqlx(rename)]` attribute used to specify the type name on the database side to `#[sqlx(type_name)]` [[@jplatte]]. - [[#976]] Rename the `DbDone` types to `DbQueryResult`. [[@jplatte]] - [[#976]] Remove the `Done` trait. The `.rows_affected()` method is now available as an inherent method on `PgQueryResult`, `MySqlQueryResult` and so on. [[@jplatte]] - [[#1007]] Remove `any::AnyType` (and replace with directly implementing `Type`) [[@jplatte]] ### Added - [[#998]] [[#821]] Add `.constraint()` to `DatabaseError` [[@fl9]] - [[#919]] For SQLite, add support for unsigned integers [[@dignifiedquire]] ### Fixes - [[#1002]] For SQLite, `GROUP BY` in `query!` caused an infinite loop at compile time. [[@pymongo]] - [[#979]] For MySQL, fix support for non-default authentication. [[@sile]] - [[#918]] Recover from dropping `wait_for_conn` inside Pool. [[@antialize]] [#821]: https://github.com/launchbadge/sqlx/issues/821 [#918]: https://github.com/launchbadge/sqlx/pull/918 [#919]: https://github.com/launchbadge/sqlx/pull/919 [#983]: https://github.com/launchbadge/sqlx/pull/983 [#940]: https://github.com/launchbadge/sqlx/pull/940 [#976]: https://github.com/launchbadge/sqlx/pull/976 [#979]: https://github.com/launchbadge/sqlx/pull/979 [#998]: https://github.com/launchbadge/sqlx/pull/998 [#983]: https://github.com/launchbadge/sqlx/pull/983 [#1002]: https://github.com/launchbadge/sqlx/pull/1002 [#1007]: https://github.com/launchbadge/sqlx/pull/1007 [#1022]: https://github.com/launchbadge/sqlx/pull/1022 ## 0.4.2 - 2020-12-19 - [[#908]] Fix `whoami` crash on FreeBSD platform [[@fundon]] [[@AldaronLau]] - [[#895]] Decrement pool size when connection is released [[@andrewwhitehead]] - [[#878]] Fix `conn.transaction` wrapper [[@hamza1311]] ```rust conn.transaction(|transaction: &mut Transaction | { // ... }); ``` - [[#874]] Recognize `1` as `true` for `SQLX_OFFLINE [[@Pleto]] - [[#747]] [[#867]] Replace `lru-cache` with `hashlink` [[@chertov]] - [[#860]] Add `rename_all` to `FromRow` and add `camelCase` and `PascalCase` [[@framp]] - [[#839]] Add (optional) support for `bstr::BStr`, `bstr::BString`, and `git2::Oid` [[@joshtriplett]] #### SQLite - [[#893]] Fix memory leak if `create_collation` fails [[@slumber]] - [[#852]] Fix potential 100% CPU usage in `fetch_one` / `fetch_optional` [[@markazmierczak]] - [[#850]] Add `synchronous` option to `SqliteConnectOptions` [[@markazmierczak]] #### PostgreSQL - [[#889]] Fix decimals (one more time) [[@slumber]] - [[#876]] Add support for `BYTEA[]` to compile-time type-checking [[@augustocdias]] - [[#845]] Fix path for `&[NaiveTime]` in `query!` macros [[@msrd0]] #### MySQL - [[#880]] Consider `utf8mb4_general_ci` as a string [[@mcronce]] [#908]: https://github.com/launchbadge/sqlx/pull/908 [#895]: https://github.com/launchbadge/sqlx/pull/895 [#893]: https://github.com/launchbadge/sqlx/pull/893 [#889]: https://github.com/launchbadge/sqlx/pull/889 [#880]: https://github.com/launchbadge/sqlx/pull/880 [#878]: https://github.com/launchbadge/sqlx/pull/878 [#876]: https://github.com/launchbadge/sqlx/pull/876 [#874]: https://github.com/launchbadge/sqlx/pull/874 [#867]: https://github.com/launchbadge/sqlx/pull/867 [#860]: https://github.com/launchbadge/sqlx/pull/860 [#854]: https://github.com/launchbadge/sqlx/pull/854 [#852]: https://github.com/launchbadge/sqlx/pull/852 [#850]: https://github.com/launchbadge/sqlx/pull/850 [#845]: https://github.com/launchbadge/sqlx/pull/845 [#839]: https://github.com/launchbadge/sqlx/pull/839 [#747]: https://github.com/launchbadge/sqlx/issues/747 ## 0.4.1 – 2020-11-13 Fix docs.rs build by enabling a runtime feature in the docs.rs metadata in `Cargo.toml`. ## 0.4.0 - 2020-11-12 - [[#774]] Fix usage of SQLx derives with other derive crates [[@NyxCode]] - [[#762]] Fix `migrate!()` (with no params) [[@esemeniuc]] - [[#755]] Add `kebab-case` to `rename_all` [[@iamsiddhant05]] - [[#735]] Support `rustls` [[@jplatte]] Adds `-native-tls` or `-rustls` on each runtime feature: ```toml # previous features = [ "runtime-async-std" ] # now features = [ "runtime-async-std-native-tls" ] ``` - [[#718]] Support tuple structs with `#[derive(FromRow)]` [[@dvermd]] #### SQLite - [[#789]] Support `$NNN` parameters [[@nitsky]] - [[#784]] Use `futures_channel::oneshot` in worker for big perf win [[@markazmierczak]] #### PostgreSQL - [[#781]] Fix decimal conversions handling of `0.01` [[@pimeys]] - [[#745]] Always prefer parsing of the non-localized notice severity field [[@dstoeckel]] - [[#742]] Enable `Vec>` with chrono [[@mrcd]] #### MySQL - [[#743]] Consider `utf8mb4_bin` as a string [[@digorithm]] - [[#739]] Fix minor protocol detail with `iteration-count` that was blocking Vitess [[@mcronce]] [#774]: https://github.com/launchbadge/sqlx/pull/774 [#789]: https://github.com/launchbadge/sqlx/pull/789 [#784]: https://github.com/launchbadge/sqlx/pull/784 [#781]: https://github.com/launchbadge/sqlx/pull/781 [#762]: https://github.com/launchbadge/sqlx/pull/762 [#755]: https://github.com/launchbadge/sqlx/pull/755 [#745]: https://github.com/launchbadge/sqlx/pull/745 [#743]: https://github.com/launchbadge/sqlx/pull/743 [#742]: https://github.com/launchbadge/sqlx/pull/742 [#735]: https://github.com/launchbadge/sqlx/pull/735 [#739]: https://github.com/launchbadge/sqlx/pull/739 [#718]: https://github.com/launchbadge/sqlx/pull/718 ## 0.4.0-beta.1 - 2020-07-27 ### Highlights - Enable compile-time type checking from cached metadata to enable building in an environment without access to a development database (e.g., Docker, CI). - Initial support for **Microsoft SQL Server**. If there is something missing that you need, open an issue. We are happy to help. - SQL migrations, both with a CLI tool and programmatically loading migrations at runtime. - Runtime-determined database driver, `Any`, to support compile-once and run with a database driver selected at runtime. - Support for user-defined types and more generally overriding the inferred Rust type from SQL with compile-time SQL verification. ### Fixed #### MySQL - [[#418]] Support zero dates and times [[@blackwolf12333]] ### Added - [[#174]] Inroduce a builder to construct connections to bypass the URL parsing ```rust // MSSQL let conn = MssqlConnectOptions::new() .host("localhost") .database("master") .username("sa") .password("Password") .connect().await?; // SQLite let conn = SqliteConnectOptions::from_str("sqlite://a.db")? .foreign_keys(false) .connect().await?; ``` - [[#127]] Get the last ID or Row ID inserted for MySQL or SQLite ```rust // MySQL let id: u64 = query!("INSERT INTO table ( col ) VALUES ( ? )", val) .execute(&mut conn).await? .last_insert_id(); // LAST_INSERT_ID() // SQLite let id: i64 = query!("INSERT INTO table ( col ) VALUES ( ?1 )", val) .execute(&mut conn).await? .last_insert_rowid(); // sqlite3_last_insert_rowid() ``` - [[#263]] Add hooks to the Pool: `after_connect`, `before_release`, and `after_acquire` ```rust // PostgreSQL let pool = PgPoolOptions::new() .after_connect(|conn| Box::pin(async move { conn.execute("SET application_name = 'your_app';").await?; conn.execute("SET search_path = 'my_schema';").await?; Ok(()) })) .connect("postgres:// …").await? ``` - [[#308]] [[#495]] Extend `derive(FromRow)` with support for `#[sqlx(default)]` on fields to allow reading in a partial query [[@OriolMunoz]] - [[#454]] [[#456]] Support `rust_decimal::Decimal` as an alternative to `bigdecimal::BigDecimal` for `NUMERIC` columns in MySQL and PostgreSQL [[@pimeys]] - [[#181]] Column names and type information is now accessible from `Row` via `Row::columns()` or `Row::column(name)` #### PostgreSQL - [[#197]] [[#271]] Add initial support for `INTERVAL` (full support pending a `time::Period` type) [[@dimtion]] #### MySQL - [[#449]] [[#450]] Support Unix Domain Sockets (UDS) for MySQL [[@pimeys]] #### SQLite - Types are now inferred for expressions. This means its now possible to use `query!` and `query_as!` for: ```rust let row = query!("SELECT 10 as _1, x + 5 as _2 FROM table").fetch_one(&mut conn).await?; assert_eq!(row._1, 10); assert_eq!(row._2, 5); // 5 + x? ``` - [[#167]] Support `foreign_keys` explicitly with a `foreign_keys(true)` method available on `SqliteConnectOptions` which is a builder for new SQLite connections (and can be passed into `PoolOptions` to build a pool). ```rust let conn = SqliteConnectOptions::new() .foreign_keys(true) // on by default .connect().await?; ``` - [[#430]] [[#438]] Add method to get the raw SQLite connection handle [[@agentsim]] ```rust // conn is `SqliteConnection` // this is not unsafe, but what you do with the handle will be let ptr: *mut libsqlite3::sqlite3 = conn.as_raw_handle(); ``` - [[#164]] Support `TIMESTAMP`, `DATETIME`, `DATE`, and `TIME` via `chrono` in SQLite [[@felipesere]] [[@meteficha]] ### Changed - `Transaction` now mutably borrows a connection instead of owning it. This enables a new (or nested) transaction to be started from `&mut conn`. - [[#145]] [[#444]] Use a least-recently-used (LRU) cache to limit the growth of the prepared statement cache for SQLite, MySQL, and PostgreSQL [[@pimeys]] #### SQLite - [[#499]] `INTEGER` now resolves to `i64` instead of `i32`, `INT4` will still resolve to `i32` ### Removed [#127]: https://github.com/launchbadge/sqlx/issues/127 [#174]: https://github.com/launchbadge/sqlx/issues/174 [#145]: https://github.com/launchbadge/sqlx/issues/145 [#164]: https://github.com/launchbadge/sqlx/issues/164 [#167]: https://github.com/launchbadge/sqlx/issues/167 [#181]: https://github.com/launchbadge/sqlx/issues/181 [#197]: https://github.com/launchbadge/sqlx/issues/197 [#263]: https://github.com/launchbadge/sqlx/issues/263 [#308]: https://github.com/launchbadge/sqlx/issues/308 [#418]: https://github.com/launchbadge/sqlx/issues/418 [#430]: https://github.com/launchbadge/sqlx/issues/430 [#449]: https://github.com/launchbadge/sqlx/issues/449 [#499]: https://github.com/launchbadge/sqlx/issues/499 [#454]: https://github.com/launchbadge/sqlx/issues/454 [#271]: https://github.com/launchbadge/sqlx/pull/271 [#444]: https://github.com/launchbadge/sqlx/pull/444 [#438]: https://github.com/launchbadge/sqlx/pull/438 [#495]: https://github.com/launchbadge/sqlx/pull/495 [#495]: https://github.com/launchbadge/sqlx/pull/495 ## 0.3.5 - 2020-05-06 ### Fixed - [[#259]] Handle percent-encoded paths for SQLite [[@g-s-k]] - [[#281]] Deallocate SQLite statements before closing the SQLite connection [[@hasali19]] - [[#284]] Fix handling of `0` for `BigDecimal` in PostgreSQL and MySQL [[@abonander]] ### Added - [[#256]] Add `query_unchecked!` and `query_file_unchecked!` with similar semantics to `query_as_unchecked!` [[@meh]] - [[#252]] [[#297]] Derive several traits for the `Json` wrapper type [[@meh]] - [[#261]] Add support for `#[sqlx(rename_all = "snake_case")]` to `#[derive(Type)]` [[@shssoichiro]] - [[#253]] Add support for UNIX domain sockets to PostgreSQL [[@Nilix007]] - [[#251]] Add support for textual JSON on MySQL [[@blackwolf12333]] - [[#275]] [[#268]] Optionally log formatted SQL queries on execution [[@shssoichiro]] - [[#267]] Support Cargo.toml relative `.env` files; allows for each crate in a workspace to use their own `.env` file and thus their own `DATABASE_URL` [[@xyzd]] [#252]: https://github.com/launchbadge/sqlx/pull/252 [#261]: https://github.com/launchbadge/sqlx/pull/261 [#256]: https://github.com/launchbadge/sqlx/pull/256 [#259]: https://github.com/launchbadge/sqlx/pull/259 [#253]: https://github.com/launchbadge/sqlx/pull/253 [#297]: https://github.com/launchbadge/sqlx/pull/297 [#251]: https://github.com/launchbadge/sqlx/pull/251 [#275]: https://github.com/launchbadge/sqlx/pull/275 [#267]: https://github.com/launchbadge/sqlx/pull/267 [#268]: https://github.com/launchbadge/sqlx/pull/268 [#281]: https://github.com/launchbadge/sqlx/pull/281 [#284]: https://github.com/launchbadge/sqlx/pull/284 ## 0.3.4 - 2020-04-10 ### Fixed - [[#241]] Type name for custom enum is not always attached to TypeInfo in PostgreSQL - [[#237]] [[#238]] User-defined type name matching is now case-insensitive in PostgreSQL [[@qtbeee]] - [[#231]] Handle empty queries (and those with comments) in SQLite - [[#228]] Provide `MapRow` implementations for functions (enables `.map(|row| ...)` over `.try_map(|row| ...)`) ### Added - [[#234]] Add support for `NUMERIC` in MySQL with the `bigdecimal` crate [[@xiaopengli89]] - [[#227]] Support `#[sqlx(rename = "new_name")]` on struct fields within a `FromRow` derive [[@sidred]] [#228]: https://github.com/launchbadge/sqlx/issues/228 [#231]: https://github.com/launchbadge/sqlx/issues/231 [#237]: https://github.com/launchbadge/sqlx/issues/237 [#241]: https://github.com/launchbadge/sqlx/issues/241 [#227]: https://github.com/launchbadge/sqlx/pull/227 [#234]: https://github.com/launchbadge/sqlx/pull/234 [#238]: https://github.com/launchbadge/sqlx/pull/238 ## 0.3.3 - 2020-04-01 ### Fixed - [[#214]] Handle percent-encoded usernames in a database URL [[@jamwaffles]] ### Changed - [[#216]] Mark `Cursor`, `Query`, `QueryAs`, `query::Map`, and `Transaction` as `#[must_use]` [[@Ace4896]] - [[#213]] Remove matches dependency and use matches macro from std [[@nrjais]] [#216]: https://github.com/launchbadge/sqlx/pull/216 [#214]: https://github.com/launchbadge/sqlx/pull/214 [#213]: https://github.com/launchbadge/sqlx/pull/213 ## 0.3.2 - 2020-03-31 ### Fixed - [[#212]] Removed sneaky `println!` in `MySqlCursor` [#212]: https://github.com/launchbadge/sqlx/issues/212 ## 0.3.1 - 2020-03-30 ### Fixed - [[#203]] Allow an empty password for MySQL - [[#204]] Regression in error reporting for invalid SQL statements on PostgreSQL - [[#200]] Fixes the incorrect handling of raw (`r#...`) fields of a struct in the `FromRow` derive [[@sidred]] [#200]: https://github.com/launchbadge/sqlx/pull/200 [#203]: https://github.com/launchbadge/sqlx/issues/203 [#204]: https://github.com/launchbadge/sqlx/issues/204 ## 0.3.0 - 2020-03-29 ### Breaking Changes - `sqlx::Row` now has a lifetime (`'c`) tied to the database connection. In effect, this means that you cannot store `Row`s or collect them into a collection. `Query` (returned from `sqlx::query()`) has `map()` which takes a function to map from the `Row` to another type to make this transition easier. In 0.2.x ```rust let rows = sqlx::query("SELECT 1") .fetch_all(&mut conn).await?; ``` In 0.3.x ```rust let values: Vec = sqlx::query("SELECT 1") .map(|row: PgRow| row.get(0)) .fetch_all(&mut conn).await?; ``` To assist with the above, `sqlx::query_as()` now supports querying directly into tuples (up to 9 elements) or struct types with a `#[derive(FromRow)]`. ```rust // This extension trait is needed until a rust bug is fixed use sqlx::postgres::PgQueryAs; let values: Vec<(i32, bool)> = sqlx::query_as("SELECT 1, false") .fetch_all(&mut conn).await?; ``` - `HasSqlType: Database` is now `T: Type` to mirror `Encode` and `Decode` - `Query::fetch` (returned from `query()`) now returns a new `Cursor` type. `Cursor` is a Stream-like type where the item type borrows into the stream (which itself borrows from connection). This means that using `query().fetch()` you can now stream directly from the database with **zero-copy** and **zero-allocation**. - Remove `PgTypeInfo::with_oid` and replace with `PgTypeInfo::with_name` ### Added - Results from the database are now zero-copy and no allocation beyond a shared read buffer for the TCP stream ( in other words, almost no per-query allocation ). Bind arguments still do allocate a buffer per query. - [[#129]] Add support for [SQLite](https://sqlite.org/index.html). Generated code should be very close to normal use of the C API. - Adds `Sqlite`, `SqliteConnection`, `SqlitePool`, and other supporting types - [[#97]] [[#134]] Add support for user-defined types. [[@Freax13]] - Rust-only domain types or transparent wrappers around SQL types. These may be used _transparently_ inplace of the SQL type. ```rust #[derive(sqlx::Type)] #[repr(transparent)] struct Meters(i32); ``` - Enumerations may be defined in Rust and can match SQL by integer discriminant or variant name. ```rust #[derive(sqlx::Type)] #[repr(i32)] // Expects a INT in SQL enum Color { Red = 1, Green = 2, Blue = 3 } ``` ```rust #[derive(sqlx::Type)] #[sqlx(rename = "TEXT")] // May also be the name of a user defined enum type #[sqlx(rename_all = "lowercase")] // similar to serde rename_all enum Color { Red, Green, Blue } // expects 'red', 'green', or 'blue' ``` - **Postgres** further supports user-defined composite types. ```rust #[derive(sqlx::Type)] #[sqlx(rename = "interface_type")] struct InterfaceType { name: String, supplier_id: i32, price: f64 } ``` - [[#98]] [[#131]] Add support for asynchronous notifications in Postgres (`LISTEN` / `NOTIFY`). [[@thedodd]] - Supports automatic reconnection on connection failure. - `PgListener` implements `Executor` and may be used to execute queries. Be careful however as if the intent is to handle and process messages rapidly you don't want to be tying up the connection for too long. Messages received during queries are buffered and will be delivered on the next call to `recv()`. ```rust let mut listener = PgListener::new(DATABASE_URL).await?; listener.listen("topic").await?; loop { let message = listener.recv().await?; println!("payload = {}", message.payload); } ``` - Add _unchecked_ variants of the query macros. These will still verify the SQL for syntactic and semantic correctness with the current database but they will not check the input or output types. This is intended as a temporary solution until `query_as!` is able to support user defined types. - `query_as_unchecked!` - `query_file_as_unchecked!` - Add support for many more types in Postgres - `JSON`, `JSONB` [[@oeb25]] - `INET`, `CIDR` [[@PoiScript]] - Arrays [[@oeb25]] - Composites ( Rust tuples or structs with a `#[derive(Type)]` ) - `NUMERIC` [[@abonander]] - `OID` (`u32`) - `"CHAR"` (`i8`) - `TIMESTAMP`, `TIMESTAMPTZ`, etc. with the `time` crate [[@utter-step]] - Enumerations ( Rust enums with a `#[derive(Type)]` ) [[@Freax13]] ### Changed - `Query` (and `QueryAs`; returned from `query()`, `query_as()`, `query!()`, and `query_as!()`) now will accept both `&mut Connection` or `&Pool` where as in 0.2.x they required `&mut &Pool`. - `Executor` now takes any value that implements `Execute` as a query. `Execute` is implemented for `Query` and `QueryAs` to mean exactly what they've meant so far, a prepared SQL query. However, `Execute` is also implemented for just `&str` which now performs a raw or unprepared SQL query. You can further use this to fetch `Row`s from the database though it is not as efficient as the prepared API (notably Postgres and MySQL send data back in TEXT mode as opposed to in BINARY mode). ```rust use sqlx::Executor; // Set the time zone parameter conn.execute("SET TIME ZONE LOCAL;").await // Demonstrate two queries at once with the raw API let mut cursor = conn.fetch("SELECT 1; SELECT 2"); let row = cursor.next().await?.unwrap(); let value: i32 = row.get(0); // 1 let row = cursor.next().await?.unwrap(); let value: i32 = row.get(0); // 2 ``` ### Removed - `Query` (returned from `query()`) no longer has `fetch_one`, `fetch_optional`, or `fetch_all`. You _must_ map the row using `map()` and then you will have a `query::Map` value that has the former methods available. ```rust let values: Vec = sqlx::query("SELECT 1") .map(|row: PgRow| row.get(0)) .fetch_all(&mut conn).await?; ``` ### Fixed - [[#62]] [[#130]] [[#135]] Remove explicit set of `IntervalStyle`. Allow usage of SQLx for CockroachDB and potentially PgBouncer. [[@bmisiak]] - [[#108]] Allow nullable and borrowed values to be used as arguments in `query!` and `query_as!`. For example, where the column would resolve to `String` in Rust (TEXT, VARCHAR, etc.), you may now use `Option`, `Option<&str>`, or `&str` instead. [[@abonander]] - [[#108]] Make unknown type errors far more informative. As an example, trying to `SELECT` a `DATE` column will now try and tell you about the `chrono` feature. [[@abonander]] ``` optional feature `chrono` required for type DATE of column #1 ("now") ``` [#62]: https://github.com/launchbadge/sqlx/issues/62 [#130]: https://github.com/launchbadge/sqlx/issues/130 [#98]: https://github.com/launchbadge/sqlx/pull/98 [#97]: https://github.com/launchbadge/sqlx/pull/97 [#134]: https://github.com/launchbadge/sqlx/pull/134 [#129]: https://github.com/launchbadge/sqlx/pull/129 [#131]: https://github.com/launchbadge/sqlx/pull/131 [#135]: https://github.com/launchbadge/sqlx/pull/135 [#108]: https://github.com/launchbadge/sqlx/pull/108 ## 0.2.6 - 2020-03-10 ### Added - [[#114]] Export `sqlx_core::Transaction` [[@thedodd]] ### Fixed - [[#125]] [[#126]] Fix statement execution in MySQL if it contains NULL statement values [[@repnop]] - [[#105]] [[#109]] Allow trailing commas in query macros [[@timmythetiny]] [#105]: https://github.com/launchbadge/sqlx/pull/105 [#109]: https://github.com/launchbadge/sqlx/pull/109 [#114]: https://github.com/launchbadge/sqlx/pull/114 [#125]: https://github.com/launchbadge/sqlx/pull/125 [#126]: https://github.com/launchbadge/sqlx/pull/126 [@timmythetiny]: https://github.com/timmythetiny [@thedodd]: https://github.com/thedodd ## 0.2.5 - 2020-02-01 ### Fixed - Fix decoding of Rows containing NULLs in Postgres [#104] - After a large review and some battle testing by [@ianthetechie](https://github.com/ianthetechie) of the `Pool`, a live leaking issue was found. This has now been fixed by [@abonander] in [#84] which included refactoring to make the pool internals less brittle (using RAII instead of manual work is one example) and to help any future contributors when changing the pool internals. - Passwords are now being percent-decoded before being presented to the server [[@repnop]] - [@100] Fix `FLOAT` and `DOUBLE` decoding in MySQL [#84]: https://github.com/launchbadge/sqlx/issues/84 [#100]: https://github.com/launchbadge/sqlx/issues/100 [#104]: https://github.com/launchbadge/sqlx/issues/104 ### Added - [[#72]] Add `PgTypeInfo::with_oid` to allow simple construction of `PgTypeInfo` which enables `HasSqlType` to be implemented by downstream consumers of SQLx [[@jplatte]] - [[#96]] Add support for returning columns from `query!` with a name of a rust keyword by using raw identifiers [[@yaahc]] - [[#71]] Implement derives for `Encode` and `Decode`. This is the first step to supporting custom types in SQLx. [[@Freax13]] [#72]: https://github.com/launchbadge/sqlx/issues/72 [#96]: https://github.com/launchbadge/sqlx/issues/96 [#71]: https://github.com/launchbadge/sqlx/issues/71 ## 0.2.4 - 2020-01-18 ### Fixed - Fix decoding of Rows containing NULLs in MySQL (and add an integration test so this doesn't break again) ## 0.2.3 - 2020-01-18 ### Fixed - Fix `query!` when used on a query that does not return results ## 0.2.2 - 2020-01-16 ### Added - [[#57]] Add support for unsigned integers and binary types in `query!` for MySQL [[@mehcode]] [#57]: https://github.com/launchbadge/sqlx/issues/57 ### Fixed - Fix stall when requesting TLS from a Postgres server that explicitly does not support TLS (such as postgres running inside docker) [[@abonander]] - [[#66]] Declare used features for `tokio` in `sqlx-macros` explicitly [#66]: https://github.com/launchbadge/sqlx/issues/66 ## 0.2.1 - 2020-01-16 ### Fixed - [[#64], [#65]] Fix decoding of Rows containing NULLs in MySQL [[@danielakhterov]] [#64]: https://github.com/launchbadge/sqlx/pull/64 [#65]: https://github.com/launchbadge/sqlx/pull/65 - [[#55]] Use a shared tokio runtime for the `query!` macro compile-time execution (under the `runtime-tokio` feature) [[@udoprog]] [#55]: https://github.com/launchbadge/sqlx/pull/55 ## 0.2.0 - 2020-01-15 ### Fixed - https://github.com/launchbadge/sqlx/issues/47 ### Added - Support Tokio through an optional `runtime-tokio` feature. - Support SQL transactions. You may now use the `begin()` function on `Pool` or `Connection` to start a new SQL transaction. This returns `sqlx::Transaction` which will `ROLLBACK` on `Drop` or can be explicitly `COMMIT` using `commit()`. - Support TLS connections. ## 0.1.4 - 2020-01-11 ### Fixed - https://github.com/launchbadge/sqlx/issues/43 - https://github.com/launchbadge/sqlx/issues/40 ### Added - Support for `SCRAM-SHA-256` authentication in Postgres [#37](https://github.com/launchbadge/sqlx/pull/37) [@danielakhterov](https://github.com/danielakhterov) - Implement `Debug` for Pool [#42](https://github.com/launchbadge/sqlx/pull/42) [@prettynatty](https://github.com/prettynatty) ## 0.1.3 - 2020-01-06 ### Fixed - https://github.com/launchbadge/sqlx/issues/30 ## 0.1.2 - 2020-01-03 ### Added - Support for Authentication in MySQL 5+ including the newer authentication schemes now default in MySQL 8: `mysql_native_password`, `sha256_password`, and `caching_sha2_password`. - [`Chrono`](https://github.com/chronotope/chrono) support for MySQL was only partially implemented (was missing `NaiveTime` and `DateTime`). - `Vec` (and `[u8]`) support for MySQL (`BLOB`) and Postgres (`BYTEA`). [@abonander]: https://github.com/abonander [@danielakhterov]: https://github.com/danielakhterov [@mehcode]: https://github.com/mehcode [@udoprog]: https://github.com/udoprog [@jplatte]: https://github.com/jplatte [@yaahc]: https://github.com/yaahc [@freax13]: https://github.com/Freax13 [@repnop]: https://github.com/repnop [@bmisiak]: https://github.com/bmisiak [@oeb25]: https://github.com/oeb25 [@poiscript]: https://github.com/PoiScript [@utter-step]: https://github.com/utter-step [@sidred]: https://github.com/sidred [@ace4896]: https://github.com/Ace4896 [@jamwaffles]: https://github.com/jamwaffles [@nrjais]: https://github.com/nrjais [@qtbeee]: https://github.com/qtbeee [@xiaopengli89]: https://github.com/xiaopengli89 [@meh]: https://github.com/meh [@shssoichiro]: https://github.com/shssoichiro [@nilix007]: https://github.com/Nilix007 [@g-s-k]: https://github.com/g-s-k [@blackwolf12333]: https://github.com/blackwolf12333 [@xyzd]: https://github.com/xyzd [@hasali19]: https://github.com/hasali19 [@oriolmunoz]: https://github.com/OriolMunoz [@pimeys]: https://github.com/pimeys [@agentsim]: https://github.com/agentsim [@meteficha]: https://github.com/meteficha [@felipesere]: https://github.com/felipesere [@dimtion]: https://github.com/dimtion [@fundon]: https://github.com/fundon [@aldaronlau]: https://github.com/AldaronLau [@andrewwhitehead]: https://github.com/andrewwhitehead [@slumber]: https://github.com/slumber [@mcronce]: https://github.com/mcronce [@hamza1311]: https://github.com/hamza1311 [@augustocdias]: https://github.com/augustocdias [@pleto]: https://github.com/Pleto [@chertov]: https://github.com/chertov [@framp]: https://github.com/framp [@markazmierczak]: https://github.com/markazmierczak [@msrd0]: https://github.com/msrd0 [@joshtriplett]: https://github.com/joshtriplett [@nyxcode]: https://github.com/NyxCode [@nitsky]: https://github.com/nitsky [@esemeniuc]: https://github.com/esemeniuc [@iamsiddhant05]: https://github.com/iamsiddhant05 [@dstoeckel]: https://github.com/dstoeckel [@mrcd]: https://github.com/mrcd [@dvermd]: https://github.com/dvermd [@seryl]: https://github.com/seryl [@ant32]: https://github.com/ant32 [@robjtede]: https://github.com/robjtede [@pymongo]: https://github.com/pymongo [@sile]: https://github.com/sile [@fl9]: https://github.com/fl9 [@antialize]: https://github.com/antialize [@dignifiedquire]: https://github.com/dignifiedquire [@argv-minus-one]: https://github.com/argv-minus-one [@qqwa]: https://github.com/qqwa [@diggsey]: https://github.com/Diggsey [@crajcan]: https://github.com/crajcan [@demurgos]: https://github.com/demurgos [@link2xt]: https://github.com/link2xt [@guylapid]: https://github.com/guylapid [@natproach]: https://github.com/NatPRoach [@feikesteenbergen]: https://github.com/feikesteenbergen [@etcaton]: https://github.com/ETCaton [@toshokan]: https://github.com/toshokan [@nomick]: https://github.com/nomick [@marshoepial]: https://github.com/marshoepial [@link2ext]: https://github.com/link2ext [@madadam]: https://github.com/madadam [@AtkinsChang]: https://github.com/AtkinsChang [@djmarcin]: https://github.com/djmarcin [@ghassmo]: https://github.com/ghassmo [@eagletmt]: https://github.com/eagletmt [@montanalow]: https://github.com/montanalow [@nitnelave]: https://github.com/nitnelave [@Drevoed]: https://github.com/Drevoed [@yuyawk]: https://github.com/yuyawk [@yerke]: https://github.com/yerke [@russweas]: https://github.com/russweas [@zbigniewzolnierowicz]: https://github.com/zbigniewzolnierowicz [@dimfeld]: https://github.com/dimfeld [@akiradeveloper]: https://github.com/akiradeveloper [@chesedo]: https://github.com/chesedo [@LLBlumire]: https://github.com/LLBlumire [@liushuyu]: https://github.com/liushuyu [@paolobarbolini]: https://github.com/paolobarbolini [@DoumanAsh]: https://github.com/DoumanAsh [@D1plo1d]: https://github.com/D1plo1d [@tkintscher]: https://github.com/tkintscher [@SonicZentropy]: https://github.com/SonicZentropy [@parazyd]: https://github.com/parazyd [@kunjee17]: https://github.com/kunjee17 [@05storm26]: https://github.com/05storm26 [@dbeckwith]: https://github.com/dbeckwith [@k-jun]: https://github.com/k-jun [@tranzystorek-io]: https://github.com/tranzystorek-io [@taladar]: https://github.com/taladar [@genusistimelord]: https://github.com/genusistimelord [@p9s]: https://github.com/p9s [@ArGGu]: https://github.com/ArGGu [@sedrik]: https://github.com/sedrik [@nappa85]: https://github.com/nappa85 [@ifn3]: https://github.com/ifn3 [@LovecraftianHorror]: https://github.com/LovecraftianHorror [@stoically]: https://github.com/stoically [@VersBinarii]: https://github.com/VersBinarii [@cemoktra]: https://github.com/cemoktra [@jdrouet]: https://github.com/jdrouet [@vbmade2000]: https://github.com/vbmade2000 [@abreis]: https://github.com/abreis [@0xdeafbeef]: https://github.com/0xdeafbeef [@Dylan-DPC]: https://github.com/Dylan-DPC [@carols10cents]: https://github.com/carols10cents [@david-mcgillicuddy-moixa]: https://github.com/david-mcgillicuddy-moixa [@ipetkov]: https://github.com/ipetkov [@pedromfedricci]: https://github.com/pedromfedricci [@tm-drtina]: https://github.com/tm-drtina [@espindola]: https://github.com/espindola [@mgrachev]: https://github.com/mgrachev [@tyrelr]: https://github.com/tyrelr [@SebastienGllmt]: https://github.com/SebastienGllmt [@e00E]: https://github.com/e00E [@sebpuetz]: https://github.com/sebpuetz [@pruthvikar]: https://github.com/pruthvikar [@tobymurray]: https://github.com/tobymurray [@djc]: https://github.com/djc [@mfreeborn]: https://github.com/mfreeborn [@scottwey]: https://github.com/scottwey [@e-rhodes]: https://github.com/e-rhodes [@OskarPersson]: https://github.com/OskarPersson [@walf443]: https://github.com/walf443 [@lovasoa]: https://github.com/lovasoa [@mdtusz]: https://github.com/mdtusz [@kianmeng]: https://github.com/kianmeng [@EthanYuan]: https://github.com/EthanYuan [@Nukesor]: https://github.com/Nukesor [@smonv]: https://github.com/smonv [@Erik1000]: https://github.com/Erik1000 [@raviqqe]: https://github.com/raviqqe [@johnbcodes]: https://github.com/johnbcodes [@sbeckeriv]: https://github.com/sbeckeriv [@RomainStorai]: https://github.com/RomainStorai [@jayy-lmao]: https://github.com/jayy-lmao [@Thomasdezeeuw]: https://github.com/Thomasdezeeuw [@kenkoooo]: https://github.com/kenkoooo [@TheoOiry]: https://github.com/TheoOiry [@JoeyMckenzie]: https://github.com/JoeyMckenzie [@ivan]: https://github.com/ivan [@crepererum]: https://github.com/crepererum [@UramnOIL]: https://github.com/UramnOIL [@liningpan]: https://github.com/liningpan [@zzhengzhuo]: https://github.com/zzhengzhuo [@crepererum]: https://github.com/crepererum [@szymek156]: https://github.com/szymek156 [@NSMustache]: https://github.com/NSMustache [@RustyYato]: https://github.com/RustyYato [@alexander-jackson]: https://github.com/alexander-jackson [@zlidner]: https://github.com/zlidner [@zlindner]: https://github.com/zlindner [@marcustut]: https://github.com/marcustut [@rakshith-ravi]: https://github.com/rakshith-ravi [@bradfier]: https://github.com/bradfier [@fuzzbuck]: https://github.com/fuzzbuck [@cycraig]: https://github.com/cycraig [@fasterthanlime]: https://github.com/fasterthanlime [@he4d]: https://github.com/he4d [@DXist]: https://github.com/DXist [@Wopple]: https://github.com/Wopple [@TravisWhitehead]: https://github.com/TravisWhitehead [@ThibsG]: https://github.com/ThibsG [@rongcuid]: https://github.com/rongcuid [@moatra]: https://github.com/moatra [@penberg]: https://github.com/penberg [@saiintbrisson]: https://github.com/saiintbrisson [@FSMaxB]: https://github.com/FSMaxB [@95ulisse]: https://github.com/95ulisse [@miles170]: https://github.com/miles170 [@ar3s3ru]: https://github.com/ar3s3ru [@cdbfoster]: https://github.com/cdbfoster [@andyquinterom]: https://github.com/andyquinterom [@CosmicHorrorDev]: https://github.com/CosmicHorrorDev [@VictorKoenders]: https://github.com/VictorKoenders [@joehillen]: https://github.com/joehillen [@OverHash]: https://github.com/OverHash [@laundmo]: https://github.com/laundmo [@nbaztec]: https://github.com/nbaztec [@bgeron]: https://github.com/bgeron [@benesch]: https://github.com/benesch [@nstinus]: https://github.com/nstinus [@grgi]: https://github.com/grgi [@sergeiivankov]: https://github.com/sergeiivankov [@jaysonsantos]: https://github.com/jaysonsantos [@dbrgn]: https://github.com/dbrgn [@grantkee]: https://github.com/grantkee [@bnoctis]: https://github.com/bnoctis [@aschey]: https://github.com/aschey [@df51d]: https://github.com/df51d [@codahale]: https://github.com/codahale [@arlyon]: https://github.com/arlyon [@SergioBenitez]: https://github.com/SergioBenitez [@tgeoghegan]: https://github.com/tgeoghegan [@vizvasrj]: https://github.com/vizvasrj [@phlip9]: https://github.com/phlip9 [@MidasLamb]: https://github.com/MidasLamb [@utkarshgupta137]: https://github.com/utkarshgupta137 [@Arcayr]: https://github.com/Arcayr [@mdecimus]: https://github.com/mdecimus [@Razican]: https://github.com/Razican [@southball]: https://github.com/southball [@alilleybrinker]: https://github.com/alilleybrinker [@titaniumtraveler]: https://github.com/titaniumtraveler [@nyurik]: https://github.com/nyurik [@stepantubanov]: https://github.com/stepantubanov [@iamquang95]: https://github.com/iamquang95 [@jnnnnn]: https://github.com/jnnnnn [@saolof]: https://github.com/saolof [@deneut]: https://github.com/deneut [@kitterion]: https://github.com/kitterion [@denschub]: https://github.com/denschub [@fd]: https://github.com/fd [@mrl5]: https://github.com/mrl5 [@Xydez]: https://github.com/Xydez [@vabka]: https://github.com/vabka [@ldanilek]: https://github.com/ldanilek [@inahga]: https://github.com/inahga [@JockeM]: https://github.com/JockeM [@vmax]: https://github.com/vmax [@sebastianv89]: https://github.com/sebastianv89 [@marcusirgens]: https://github.com/marcusirgens [@tsing]: https://github.com/tsing [@snf]: https://github.com/snf [@wyhaya]: https://github.com/wyhaya [@hakoerber]: https://github.com/hakoerber [@olback]: https://github.com/olback [@kryptan]: https://github.com/kryptan [@grant0417]: https://github.com/grant0417 [@fermanjj]: https://github.com/fermanjj [@grooverdan]: https://github.com/grooverdan [@bobozaur]: https://github.com/bobozaur [@aldur]: https://github.com/aldur [@hgranthorner]: https://github.com/hgranthorner [@ripa1995]: https://github.com/ripa1995 [@fhsgoncalves]: https://github.com/fhsgoncalves [@uttarayan21]: https://github.com/uttarayan21 [@tk2217]: https://github.com/tk2217 [@hamiltop]: https://github.com/hamiltop [@cameronbraid]: https://github.com/cameronbraid [@Baptistemontan]: https://github.com/Baptistemontan [@satwanjyu]: https://github.com/satwanjyu [@boris-lok]: https://github.com/boris-lok [@qwerty2501]: https://github.com/qwerty2501 [@Nemo157]: https://github.com/Nemo157 [@snspinn]: https://github.com/snspinn [@granddaifuku]: https://github.com/granddaifuku [@yasamoka]: https://github.com/yasamoka [@mattfbacon]: https://github.com/mattfbacon [@A248]: https://github.com/A248 [@conradludgate]: https://github.com/conradludgate [@anupj]: https://github.com/anupj [@nanoqsh]: https://github.com/nanoqsh [@brianheineman]: https://github.com/brianheineman [@jkleinknox]: https://github.com/jkleinknox [@cryeprecision]: https://github.com/cryeprecision [@Vrajs16]: https://github.com/Vrajs16 [@shiftrightonce]: https://github.com/shiftrightonce [@tamasfe]: https://github.com/tamasfe [@lily-mosquitoes]: https://github.com/lily-mosquitoes [@larsschumacher]: https://github.com/larsschumacher [@shengsheng]: https://github.com/shengsheng [@Fyko]: https://github.com/Fyko [@kshramt]: https://github.com/kshramt [@Dawsoncodes]: https://github.com/Dawsoncodes [@tadghh]: https://github.com/tadghh [@holicc]: https://github.com/holicc [@takenoko-gohan]: https://github.com/takenoko-gohan [@iangilfillan]: https://github.com/iangilfillan [@iamjpotts]: https://github.com/iamjpotts [@Icerath]: https://github.com/Icerath [@pawurb]: https://github.com/pawurb [@darkecho731]: https://github.com/darkecho731 [@mirek26]: https://github.com/mirek26 [@Ekleog]: https://github.com/Ekleog [@zoomiti]: https://github.com/zoomiti [@ciffelia]: https://github.com/ciffelia [@rafaelGuerreiro]: https://github.com/rafaelGuerreiro [@alu]: https://github.com/alu [@BadBastion]: https://github.com/BadBastion [@tylerhawkes]: https://github.com/tylerhawkes [@g-bartoszek]: https://github.com/g-bartoszek [@benluelo]: https://github.com/benluelo [@ralpha]: https://github.com/ralpha [@nitn3lav]: https://github.com/nitn3lav [@FlakM]: https://github.com/FlakM [@hoxxep]: https://github.com/hoxxep [@NfNitLoop]: https://github.com/NfNitLoop [@GnomedDev]: https://github.com/GnomedDev [@pxp9]: https://github.com/pxp9 [@RaghavRox]: https://github.com/RaghavRox [@cleverjam]: https://github.com/cleverjam [@BlackSoulHub]: https://github.com/BlackSoulHub [@levkk]: https://github.com/levkk [@danjpgriffin]: https://github.com/danjpgriffin [@toxeus]: https://github.com/toxeus [@jasonish]: https://github.com/jasonish [@AlphaKeks]: https://github.com/AlphaKeks [@Zarathustra2]: https://github.com/Zarathustra2 [@gridbox]: https://github.com/gridbox [@joelkoen]: https://github.com/joelkoen [@nk9]: https://github.com/nk9 [@etorreborre]: https://github.com/etorreborre [@LecrisUT]: https://github.com/LecrisUT [@JohannesIBK]: https://github.com/JohannesIBK [@Lachstec]: https://github.com/Lachstec [@SrGesus]: https://github.com/SrGesus [@CommanderStorm]: https://github.com/CommanderStorm [@hamirmahal]: https://github.com/hamirmahal [@DirectorX]: https://github.com/DirectorX [@KobusEllis]: https://github.com/KobusEllis [@YgorSouza]: https://github.com/YgorSouza [@Zarthus]: https://github.com/Zarthus [@ckampfe]: https://github.com/ckampfe [@tottoto]: https://github.com/tottoto [@ods]: https://github.com/ods [@soucosmo]: https://github.com/soucosmo [@kolinfluence]: https://github.com/kolinfluence [@joeydewaal]: https://github.com/joeydewaal [@pierre-wehbe]: https://github.com/pierre-wehbe [@carschandler]: https://github.com/carschandler [@kdesjard]: https://github.com/kdesjard [@luveti]: https://github.com/luveti [@dojiong]: https://github.com/dojiong [@jayvdb]: https://github.com/jayvdb [@kurtbuilds]: https://github.com/kurtbuilds [@lilydjwg]: https://github.com/lilydjwg [@M3t0r]: https://github.com/M3t0r [@vsuryamurthy]: https://github.com/vsuryamurthy [@manifest]: https://github.com/manifest [@tbar4]: https://github.com/tbar4 [@sandhose]: https://github.com/sandhose [@IlyaBizyaev]: https://github.com/IlyaBizyaev [@philipcristiano]: https://github.com/philipcristiano [@xuehaonan27]: https://github.com/xuehaonan27 [@chanks]: https://github.com/chanks [@Ddystopia]: https://github.com/Ddystopia [@veigaribo]: https://github.com/veigaribo [@Norlock]: https://github.com/Norlock [@swlynch99]: https://github.com/swlynch99 [@BenoitRanque]: https://github.com/BenoitRanque [@hsivonen]: https://github.com/hsivonen [@andreweggleston]: https://github.com/andreweggleston [@Suficio]: https://github.com/Suficio [@bonega]: https://github.com/bonega [@nico-incubiq]: https://github.com/nico-incubiq [@tisonkun]: https://github.com/tisonkun [@karambarakat]: https://github.com/karambarakat [@seanaye]: https://github.com/seanaye [@remysaissy]: https://github.com/remysaissy [@BeauGieskens]: https://github.com/BeauGieskens [@Turbo87]: https://github.com/Turbo87 [@jthacker]: https://github.com/jthacker [@benwilber]: https://github.com/benwilber [@chitoku-k]: https://github.com/chitoku-k [@chanmaoganda]: https://github.com/chanmaoganda [@dns2utf8]: https://github.com/dns2utf8 [@mattrighetti]: https://github.com/mattrighetti [@soulwa]: https://github.com/soulwa [@kildrens]: https://github.com/kildrens [@xvapx]: https://github.com/xvapx [@jonasmalacofilho]: https://github.com/jonasmalacofilho [@sulami]: https://github.com/sulami [@thriller08]: https://github.com/thriller08 [@mbj]: https://github.com/mbj [@TeCHiScy]: https://github.com/TeCHiScy [@mpyw]: https://github.com/mpyw [@bonsairobo]: https://github.com/bonsairobo [@gferon]: https://github.com/gferon [@joshka]: https://github.com/joshka [@kujeger]: https://github.com/kujeger [@dyc3]: https://github.com/dyc3 [@ThomWright]: https://github.com/ThomWright [@duhby]: https://github.com/duhby [@V02460]: https://github.com/V02460 [@nipunn1313]: https://github.com/nipunn1313 [@miniduikboot]: https://github.com/miniduikboot [@0xfourzerofour]: https://github.com/0xfourzerofour [@AlexTMjugador]: https://github.com/AlexTMjugador [@martin-kolarik]: https://github.com/martin-kolarik [@cvzx]: https://github.com/cvzx [@Dirbaio]: https://github.com/Dirbaio [@elichai]: https://github.com/elichai [@silvestrpredko]: https://github.com/silvestrpredko [@davidcornu]: https://github.com/davidcornu [@zebrapurring]: https://github.com/zebrapurring [@djarb]: https://github.com/djarb [@barskern]: https://github.com/barskern [@nhatcher-frequenz]: https://github.com/nhatcher-frequenz [@JerryQ17]: https://github.com/JerryQ17 [@jpmelos]: https://github.com/jpmelos [@psionic-k]: https://github.com/psionic-k [@Xiretza]: https://github.com/Xiretza [@2ndDerivative]: https://github.com/2ndDerivative [@kevincox]: https://github.com/kevincox [@papaj-na-wrotkach]: https://github.com/papaj-na-wrotkach [@xb284524239]: https://github.com/xb284524239 [@Dosenpfand]: https://github.com/Dosenpfand [@daviduebler]: https://github.com/daviduebler ================================================ FILE: CONTRIBUTING.md ================================================ # How to contribute So, you've decided to contribute, that's great! You can use this document to figure out how and where to start. ## Getting started - Make sure you have a [GitHub account](https://github.com/join). - Take a look at [existing issues](https://github.com/launchbadge/sqlx/issues). - If you need to create an issue: - Make sure to clearly describe it. - Including steps to reproduce when it is a bug. - Include the version of SQLx used. - Include the database driver and version. - Include the database version. ## Making changes - Fork the repository on GitHub. - Create a branch on your fork. - You can usually base it on the `main` branch. - Make sure not to commit directly to `main`. - Make commits of logical and atomic units. - Make sure you have added the necessary tests for your changes. - Push your changes to a topic branch in your fork of the repository. - Submit a pull request to the original repository. ## What to work on We try to mark issues with a suggested level of experience (in Rust/SQL/SQLx). Where possible we try to spell out how to go about implementing the feature. To start with, check out: - Issues labeled as ["good first issue"](https://github.com/launchbadge/sqlx/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). - Issues labeled as ["Easy"](https://github.com/launchbadge/sqlx/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy). Additionally, it's always good to work on improving/adding examples and documentation. ## Communication If you're unsure about your contribution or simply want to ask a question about anything, you can: - Visit the [SQLx Discord server](https://discord.gg/uuruzJ7) - Discuss something directly in the [Github issue](https://github.com/launchbadge/sqlx/issues). ================================================ FILE: Cargo.toml ================================================ [workspace] members = [ ".", "sqlx-core", "sqlx-macros", "sqlx-macros-core", "sqlx-test", "sqlx-cli", # "sqlx-bench", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "examples/mysql/todos", "examples/postgres/axum-social-with-tests", "examples/postgres/chat", "examples/postgres/files", "examples/postgres/json", "examples/postgres/listen", "examples/postgres/mockable-todos", "examples/postgres/multi-database", "examples/postgres/multi-tenant", "examples/postgres/preferred-crates", "examples/postgres/todos", "examples/postgres/transaction", "examples/sqlite/todos", "examples/sqlite/extension", ] [workspace.package] version = "0.9.0-alpha.1" license = "MIT OR Apache-2.0" # TODO: upgrade to edition 2024 (after merging all pending PRs) edition = "2021" repository = "https://github.com/launchbadge/sqlx" keywords = ["database", "async", "postgres", "mysql", "sqlite"] categories = ["database", "asynchronous"] authors = [ "Ryan Leckey ", "Austin Bonander ", "Chloe Ross ", "Daniel Akhterov ", ] rust-version = "1.86.0" [package] name = "sqlx" readme = "README.md" documentation = "https://docs.rs/sqlx" description = "🧰 The Rust SQL Toolkit. An async, pure Rust SQL crate featuring compile-time checked queries without a DSL. Supports PostgreSQL, MySQL, and SQLite." version.workspace = true license.workspace = true edition.workspace = true authors.workspace = true repository.workspace = true rust-version.workspace = true # Note: written so that it may be copy-pasted to other crates [package.metadata.docs.rs] features = ["_unstable-docs"] rustdoc-args = ["--cfg", "docsrs"] [features] default = ["any", "macros", "migrate", "json"] derive = ["sqlx-macros/derive"] macros = ["derive", "sqlx-macros/macros", "sqlx-core/offline", "sqlx-mysql?/offline", "sqlx-postgres?/offline", "sqlx-sqlite?/offline"] migrate = ["sqlx-core/migrate", "sqlx-macros?/migrate", "sqlx-mysql?/migrate", "sqlx-postgres?/migrate", "sqlx-sqlite?/migrate"] # Enable parsing of `sqlx.toml` for configuring macros and migrations. sqlx-toml = ["sqlx-core/sqlx-toml", "sqlx-macros?/sqlx-toml", "sqlx-sqlite?/sqlx-toml"] # intended mainly for CI and docs all-databases = ["mysql", "sqlite", "postgres", "any"] _unstable-all-types = [ "bigdecimal", "rust_decimal", "json", "time", "chrono", "ipnet", "ipnetwork", "mac_address", "uuid", "bit-vec", "bstr" ] # Render documentation that wouldn't otherwise be shown (e.g. `sqlx_core::config`). _unstable-docs = [ "all-databases", "_unstable-all-types", "sqlx-sqlite/_unstable-docs" ] # Base runtime features without TLS runtime-async-global-executor = ["_rt-async-global-executor", "sqlx-core/_rt-async-global-executor", "sqlx-macros?/_rt-async-global-executor"] runtime-async-std = ["_rt-async-std", "sqlx-core/_rt-async-std", "sqlx-macros?/_rt-async-std"] runtime-smol = ["_rt-smol", "sqlx-core/_rt-smol", "sqlx-macros?/_rt-smol"] runtime-tokio = ["_rt-tokio", "sqlx-core/_rt-tokio", "sqlx-macros?/_rt-tokio"] # TLS features tls-native-tls = ["sqlx-core/_tls-native-tls", "sqlx-macros?/_tls-native-tls"] tls-rustls = ["tls-rustls-ring"] # For backwards compatibility tls-rustls-aws-lc-rs = ["sqlx-core/_tls-rustls-aws-lc-rs", "sqlx-macros?/_tls-rustls-aws-lc-rs"] tls-rustls-ring = ["tls-rustls-ring-webpki"] # For backwards compatibility tls-rustls-ring-webpki = ["sqlx-core/_tls-rustls-ring-webpki", "sqlx-macros?/_tls-rustls-ring-webpki"] tls-rustls-ring-native-roots = ["sqlx-core/_tls-rustls-ring-native-roots", "sqlx-macros?/_tls-rustls-ring-native-roots"] # No-op feature used by the workflows to compile without TLS enabled. Not meant for general use. tls-none = [] # for conditional compilation _rt-async-global-executor = [] _rt-async-std = [] _rt-smol = [] _rt-tokio = [] _sqlite = [] # database any = ["sqlx-core/any", "sqlx-mysql?/any", "sqlx-postgres?/any", "sqlx-sqlite?/any"] postgres = ["sqlx-postgres", "sqlx-macros?/postgres"] mysql = ["sqlx-mysql", "sqlx-macros?/mysql"] sqlite = ["sqlite-bundled", "sqlite-deserialize", "sqlite-load-extension", "sqlite-unlock-notify"] # SQLite base features sqlite-bundled = ["_sqlite", "sqlx-sqlite/bundled", "sqlx-macros?/sqlite"] sqlite-unbundled = ["_sqlite", "sqlx-sqlite/unbundled", "sqlx-macros?/sqlite-unbundled"] # SQLite features using conditionally compiled APIs # Note: these assume `sqlite-bundled` or `sqlite-unbundled` is also enabled # # Enable `SqliteConnection::deserialize()` and `::serialize()` # Cannot be used with `-DSQLITE_OMIT_DESERIALIZE`; requires `-DSQLITE_ENABLE_DESERIALIZE` on SQLite < 3.36.0 sqlite-deserialize = ["sqlx-sqlite/deserialize"] # Enable `SqliteConnectOptions::extension()` and `::extension_with_entrypoint()`. # Also required to use `drivers.sqlite.unsafe-load-extensions` from `sqlx.toml`. # Cannot be used with `-DSQLITE_OMIT_LOAD_EXTENSION` sqlite-load-extension = ["sqlx-sqlite/load-extension", "sqlx-macros?/sqlite-load-extension"] # Enables `sqlite3_preupdate_hook` # Requires `-DSQLITE_ENABLE_PREUPDATE_HOOK` (set automatically with `sqlite-bundled`) sqlite-preupdate-hook = ["sqlx-sqlite/preupdate-hook"] # Enable internal handling of `SQLITE_LOCKED_SHAREDCACHE` # Requires `-DSQLITE_ENABLE_UNLOCK_NOTIFY` (set automatically with `sqlite-bundled`) sqlite-unlock-notify = ["sqlx-sqlite/unlock-notify"] # types json = ["sqlx-core/json", "sqlx-macros?/json", "sqlx-mysql?/json", "sqlx-postgres?/json", "sqlx-sqlite?/json"] bigdecimal = ["sqlx-core/bigdecimal", "sqlx-macros?/bigdecimal", "sqlx-mysql?/bigdecimal", "sqlx-postgres?/bigdecimal"] bit-vec = ["sqlx-core/bit-vec", "sqlx-macros?/bit-vec", "sqlx-postgres?/bit-vec"] chrono = ["sqlx-core/chrono", "sqlx-macros?/chrono", "sqlx-mysql?/chrono", "sqlx-postgres?/chrono", "sqlx-sqlite?/chrono"] ipnet = ["sqlx-core/ipnet", "sqlx-macros?/ipnet", "sqlx-postgres?/ipnet"] ipnetwork = ["sqlx-core/ipnetwork", "sqlx-macros?/ipnetwork", "sqlx-postgres?/ipnetwork"] mac_address = ["sqlx-core/mac_address", "sqlx-macros?/mac_address", "sqlx-postgres?/mac_address"] rust_decimal = ["sqlx-core/rust_decimal", "sqlx-macros?/rust_decimal", "sqlx-mysql?/rust_decimal", "sqlx-postgres?/rust_decimal"] time = ["sqlx-core/time", "sqlx-macros?/time", "sqlx-mysql?/time", "sqlx-postgres?/time", "sqlx-sqlite?/time"] uuid = ["sqlx-core/uuid", "sqlx-macros?/uuid", "sqlx-mysql?/uuid", "sqlx-postgres?/uuid", "sqlx-sqlite?/uuid"] regexp = ["sqlx-sqlite?/regexp"] bstr = ["sqlx-core/bstr"] [workspace.dependencies] # Core Crates sqlx-core = { version = "=0.9.0-alpha.1", path = "sqlx-core" } sqlx-macros-core = { version = "=0.9.0-alpha.1", path = "sqlx-macros-core" } sqlx-macros = { version = "=0.9.0-alpha.1", path = "sqlx-macros" } # Driver crates sqlx-mysql = { version = "=0.9.0-alpha.1", path = "sqlx-mysql" } sqlx-postgres = { version = "=0.9.0-alpha.1", path = "sqlx-postgres" } sqlx-sqlite = { version = "=0.9.0-alpha.1", path = "sqlx-sqlite" } # Facade crate (for reference from sqlx-cli) sqlx = { version = "=0.9.0-alpha.1", path = ".", default-features = false } # Common type integrations shared by multiple driver crates. # These are optional unless enabled in a workspace crate. bigdecimal = "0.4.0" bit-vec = "0.8" chrono = { version = "0.4.34", default-features = false, features = ["std", "clock"] } ipnet = "2.3.0" ipnetwork = "0.21.1" mac_address = "1.1.5" rust_decimal = { version = "1.26.1", default-features = false, features = ["std"] } time = { version = "0.3.36", features = ["formatting", "parsing", "macros"] } uuid = "1.1.2" # Common utility crates cfg-if = "1.0.0" dotenvy = { version = "0.15.0", default-features = false } thiserror = { version = "2.0.17", default-features = false, features = ["std"] } # Runtimes [workspace.dependencies.async-global-executor] version = "3.1" default-features = false features = ["async-io"] [workspace.dependencies.async-std] version = "1.13" [workspace.dependencies.smol] version = "2.0" default-features = false [workspace.dependencies.tokio] version = "1" features = ["time", "net", "sync", "fs", "io-util", "rt"] default-features = false [dependencies] sqlx-core = { workspace = true, features = ["migrate"] } sqlx-macros = { workspace = true, optional = true } sqlx-mysql = { workspace = true, optional = true } sqlx-postgres = { workspace = true, optional = true } sqlx-sqlite = { workspace = true, optional = true } [dev-dependencies] anyhow = "1.0.52" time_ = { version = "0.3.2", package = "time" } futures-util = { version = "0.3.19", default-features = false, features = ["alloc"] } env_logger = "0.11" async-std = { workspace = true, features = ["attributes"] } tokio = { version = "1.15.0", features = ["full"] } dotenvy = "0.15.0" trybuild = "1.0.53" sqlx-test = { path = "./sqlx-test" } paste = "1.0.6" serde = { version = "1.0.132", features = ["derive"] } serde_json = "1.0.73" url = "2.2.2" rand = "0.8.4" rand_xoshiro = "0.6.0" hex = "0.4.3" tempfile = "3.10.1" criterion = { version = "0.5.1", features = ["async_tokio"] } libsqlite3-sys = { version = "0.30.1" } # If this is an unconditional dev-dependency then Cargo will *always* try to build `libsqlite3-sys`, # even when SQLite isn't the intended test target, and fail if the build environment is not set up for compiling C code. [target.'cfg(sqlite_test_sqlcipher)'.dev-dependencies] # Enable testing with SQLCipher if specifically requested. libsqlite3-sys = { version = "0.30.1", features = ["bundled-sqlcipher"] } # Common lint settings for the workspace [workspace.lints.clippy] # https://github.com/launchbadge/sqlx/issues/3440 cast_possible_truncation = 'deny' cast_possible_wrap = 'deny' cast_sign_loss = 'deny' # See `clippy.toml` disallowed_methods = 'deny' [lints.rust.unexpected_cfgs] level = 'warn' check-cfg = [ 'cfg(mariadb, values(any()))', 'cfg(postgres, values(any()))', 'cfg(sqlite_ipaddr)', 'cfg(sqlite_test_sqlcipher)', ] # # Any # [[test]] name = "any" path = "tests/any/any.rs" required-features = ["any"] [[test]] name = "any-pool" path = "tests/any/pool.rs" required-features = ["any"] # # Migrations # [[test]] name = "migrate-macro" path = "tests/migrate/macro.rs" required-features = ["macros", "migrate"] # # SQLite # [[test]] name = "sqlite" path = "tests/sqlite/sqlite.rs" required-features = ["sqlite"] [[test]] name = "sqlite-any" path = "tests/sqlite/any.rs" required-features = ["sqlite"] [[test]] name = "sqlite-types" path = "tests/sqlite/types.rs" required-features = ["sqlite"] [[test]] name = "sqlite-describe" path = "tests/sqlite/describe.rs" required-features = ["sqlite"] [[test]] name = "sqlite-macros" path = "tests/sqlite/macros.rs" required-features = ["_sqlite", "macros"] [[test]] name = "sqlite-derives" path = "tests/sqlite/derives.rs" required-features = ["sqlite", "macros"] [[test]] name = "sqlite-error" path = "tests/sqlite/error.rs" required-features = ["sqlite"] [[test]] name = "sqlite-sqlcipher" path = "tests/sqlite/sqlcipher.rs" required-features = ["sqlite"] [[test]] name = "sqlite-test-attr" path = "tests/sqlite/test-attr.rs" required-features = ["sqlite", "macros", "migrate"] [[test]] name = "sqlite-migrate" path = "tests/sqlite/migrate.rs" required-features = ["sqlite", "macros", "migrate"] [[test]] name = "sqlite-rustsec" path = "tests/sqlite/rustsec.rs" required-features = ["sqlite"] [[bench]] name = "sqlite-describe" path = "benches/sqlite/describe.rs" harness = false required-features = ["sqlite"] # # MySQL # [[test]] name = "mysql" path = "tests/mysql/mysql.rs" required-features = ["mysql"] [[test]] name = "mysql-types" path = "tests/mysql/types.rs" required-features = ["mysql"] [[test]] name = "mysql-describe" path = "tests/mysql/describe.rs" required-features = ["mysql"] [[test]] name = "mysql-derives" path = "tests/mysql/derives.rs" required-features = ["mysql", "derive"] [[test]] name = "mysql-macros" path = "tests/mysql/macros.rs" required-features = ["mysql", "macros"] [[test]] name = "mysql-error" path = "tests/mysql/error.rs" required-features = ["mysql"] [[test]] name = "mysql-test-attr" path = "tests/mysql/test-attr.rs" required-features = ["mysql", "macros", "migrate"] [[test]] name = "mysql-migrate" path = "tests/mysql/migrate.rs" required-features = ["mysql", "macros", "migrate"] [[test]] name = "mysql-rustsec" path = "tests/mysql/rustsec.rs" required-features = ["mysql"] # # PostgreSQL # [[test]] name = "postgres" path = "tests/postgres/postgres.rs" required-features = ["postgres"] [[test]] name = "postgres-types" path = "tests/postgres/types.rs" required-features = ["postgres"] [[test]] name = "postgres-describe" path = "tests/postgres/describe.rs" required-features = ["postgres"] [[test]] name = "postgres-macros" path = "tests/postgres/macros.rs" required-features = ["postgres", "macros"] [[test]] name = "postgres-derives" path = "tests/postgres/derives.rs" required-features = ["postgres", "macros"] [[test]] name = "postgres-error" path = "tests/postgres/error.rs" required-features = ["postgres"] [[test]] name = "postgres-test-attr" path = "tests/postgres/test-attr.rs" required-features = ["postgres", "macros", "migrate"] [[test]] name = "postgres-migrate" path = "tests/postgres/migrate.rs" required-features = ["postgres", "macros", "migrate"] [[test]] name = "postgres-query-builder" path = "tests/postgres/query_builder.rs" required-features = ["postgres"] [[test]] name = "postgres-rustsec" path = "tests/postgres/rustsec.rs" required-features = ["postgres", "macros", "migrate"] ================================================ FILE: FAQ.md ================================================ SQLx Frequently Asked Questions =============================== ### What database versions does SQLx support? This is a difficult question to answer because it depends on which features of the databases are used and when those features were introduced. SQL databases tend to be very strongly backwards-compatible so it's likely that SQLx will work with some very old versions. TLS support is one of the features that ages most quickly with databases, since old SSL/TLS versions are deprecated over time as they become insecure due to weaknesses being discovered; this is especially important to consider when using RusTLS, as it only supports the latest TLS version for security reasons (see the question below mentioning RusTLS for details). As a rule, however, we only officially support the range of versions for each database that are still actively maintained, and will drop support for versions as they reach their end-of-life. * Postgres has a page to track these versions and give their end-of-life dates: https://www.postgresql.org/support/versioning/ * MariaDB has a similar list here (though it doesn't show the dates at which old versions were EOL'd): https://mariadb.com/kb/en/mariadb-server-release-dates/ * MySQL's equivalent page is more concerned with what platforms are supported by the newest and oldest maintained versions: https://www.mysql.com/support/supportedplatforms/database.html * However, its Wikipedia page helpfully tracks its versions and their announced EOL dates: https://en.wikipedia.org/wiki/MySQL#Release_history * SQLite is easy as only SQLite 3 is supported and the current version depends on the version of the `libsqlite3-sys` crate being used. For each database and where applicable, we test against the latest and oldest versions that we intend to support. You can see the current versions being tested against by looking at our CI config: https://github.com/launchbadge/sqlx/blob/main/.github/workflows/sqlx.yml#L168 ------------------------------------------------------------------- ### What versions of Rust does SQLx support? What is SQLx's MSRV\*? SQLx's MSRV is the second-to-latest stable release as of the beginning of the current release cycle (`0.x.0`). It will remain there until the next major release (`0.{x + 1}.0`). For example, as of the `0.8.0` release of SQLx, the latest stable Rust version was `1.79.0`, so the MSRV for the `0.8.x` release cycle of SQLx is `1.78.0`. This guarantees that SQLx will compile with a Rust version that is _at least_ six weeks old, which should be plenty of time for it to make it through any packaging system that is being actively kept up to date. We do _not_ recommend installing Rust through operating system packages, as they can often be a whole year or more out-of-date. \*Minimum Supported Rust Version [`rust-version`]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-rust-version-field ---------------------------------------------------------------- ### Can SQLx Add Support for New Databases? We are always open to discuss adding support for new databases, but as of writing, have no plans to in the short term. Implementing support for a new database in SQLx is a _huge_ lift. Expecting this work to be done for free is highly unrealistic. In all likelihood, the implementation would need to be written from scratch. Even if Rust bindings exist, they may not support `async`. Even if they support `async`, they may only support either Tokio or `async-std`, and not both. Even if they support Tokio and `async-std`, the API may not be flexible enough or provide sufficient information (e.g. for implementing the macros). If we have to write the implementation from scratch, is the protocol publicly documented, and stable? Even if everything is supported on the client side, how will we run tests against the database? Is it open-source, or proprietary? Will it require a paid license? For example, Oracle Database's protocol is proprietary and only supported through their own libraries, which do not support Rust, and only have blocking APIs (see: [Oracle Call Interface for C](https://docs.oracle.com/en/database/oracle/oracle-database/23/lnoci/index.html)). This makes it a poor candidate for an async-native crate like SQLx--though we support SQLite, which also only has a blocking API, that's the exception and not the rule. Wrapping blocking APIs is not very scalable. We still have plans to bring back the MSSQL driver, but this is not feasible as of writing with the current maintenance workload. Should this change, an announcement will be made on Github as well as our [Discord server](https://discord.gg/uuruzJ7). ### What If I'm Willing to Contribute the Implementation? Being willing to contribute an implementation for a new database is one thing, but there's also the ongoing maintenance burden to consider. Are you willing to provide support long-term? Will there be enough users that we can rely on outside contributions? Or is support going to fall to the current maintainer(s)? This is the kind of thing that will need to be supported in SQLx _long_ after the initial implementation, or else later need to be removed. If you don't have plans for how to support a new driver long-term, then it doesn't belong as part of SQLx itself. However, drivers don't necessarily need to live _in_ SQLx anymore. Since 0.7.0, drivers don't need to be compiled-in to be functional. Support for third-party drivers in `sqlx-cli` and the `query!()` macros is pending, as well as documenting the process of writing a driver, but contributions are welcome in this regard. For example, see [sqlx-exasol](https://crates.io/crates/sqlx-exasol). ---------------------------------------------------------------- ### Can SQLx Add Support for New Data-Type Crates (e.g. Jiff in addition to `chrono` and `time`)? This has a lot of the same considerations as adding support for new databases (see above), but with one big additional problem: Semantic Versioning. When we add trait implementations for types from an external crate, that crate then becomes part of our public API. We become beholden to its release cycle. If the crate's API is still evolving, meaning they are making breaking changes frequently, and thus releasing new major versions frequently, that then becomes a burden on us to upgrade and release a new major version as well so everyone _else_ can upgrade. We don't have the maintainer bandwidth to support multiple major versions simultaneously (we have no Long-Term Support policy), so this means that users who want to keep up-to-date are forced to make frequent manual upgrades as well. Thus, it is best that we stick to only supporting crates which have a stable API, and which are not making new major releases frequently. Conversely, adding support for SQLx _in_ these crates may not be desirable either, since SQLx is a large dependency and a higher-level crate. In this case, the SemVer problem gets pushed onto the other crate. There isn't a satisfying answer to this problem, but one option is to have an intermediate wrapper crate. For example, [`jiff-sqlx`](https://crates.io/crates/jiff-sqlx), which is maintained by the author of Jiff. API changes to SQLx are pending to make this pattern easier to use. ---------------------------------------------------------------- ### I'm getting `HandshakeFailure` or `CorruptMessage` when trying to connect to a server over TLS using RusTLS. What gives? To encourage good security practices and limit cruft, RusTLS does not support older versions of TLS or cryptographic algorithms that are considered insecure. `HandshakeFailure` is a normal error returned when RusTLS and the server cannot agree on parameters for a secure connection. Check the supported TLS versions for the database server version you're running. If it does not support TLS 1.2 or greater, then you likely will not be able to connect to it with RusTLS. The ideal solution, of course, is to upgrade your database server to a version that supports at least TLS 1.2. * MySQL: [has supported TLS 1.2 since 5.6.46](https://dev.mysql.com/doc/refman/5.6/en/encrypted-connection-protocols-ciphers.html#encrypted-connection-supported-protocols). * PostgreSQL: depends on the system OpenSSL version. * MSSQL: TLS is not supported yet. If you're running a third-party database that talks one of these protocols, consult its documentation for supported TLS versions. If you're stuck on an outdated version, which is unfortunate but tends to happen for one reason or another, try switching to the corresponding `runtime--native-tls` feature for SQLx. That will use the system APIs for TLS which tend to have much wider support. See [the `native-tls` crate docs](https://docs.rs/native-tls/latest/native_tls/) for details. The `CorruptMessage` error occurs in similar situations and many users have had success with switching to `-native-tls` to get around it. However, if you do encounter this error, please try to capture a Wireshark or `tcpdump` trace of the TLS handshake as the RusTLS folks are interested in covering cases that trigger this (as it might indicate a protocol handling bug or the server is doing something non-standard): https://github.com/rustls/rustls/issues/893 ---------------------------------------------------------------- ### How does SQLx help prevent SQL Injection? ### How do Query Parameters work? ### Why does SQLx use Prepared Statements for most queries? ### Can I Use Query Parameters to add conditional SQL to my query? ### Why can't I use DDL (e.g. `CREATE TABLE`, `ALTER TABLE`, etc.) with the `sqlx::query*()` functions or `sqlx::query*!()` macros? These questions can all be answered by a thorough explanation of prepared statements. Feel free to skip the parts you already know. Back in the day, if a web application wanted to include user input in a SQL query, a search parameter for example, it had no choice but to simply format that data into the query. PHP applications used to be full of snippets like this: ```php /* Imagine this is user input */ $city = "Munich"; /* $query = "SELECT country FROM city WHERE name='Munich'" */ $query = sprintf("SELECT country FROM city WHERE name='%s'", $city); $result = $mysqli->query($query); ``` However, this leaves the application vulnerable to [SQL injection attacks](https://en.wikipedia.org/wiki/SQL_injection), because it's trivial to craft an input string that will terminate the existing query and begin a new one, and the database won't know the difference and will execute both. As illustrated in the famous XKCD #327: Exploits of a Mom The fictional school's student database application might have contained a query that looked like this: ```php $student_name = "Robert');DROP TABLE Students;--" $query = sprintf("INSERT INTO Students (name) VALUES ('%s')", $student_name); $result = $mysqli->query($query); ``` When formatted into the middle of this query, the maliciously crafted input string closes the quotes and finishes the statement (`Robert');`), then starts another one with the nefarious payload (`DROP TABLE Students;`), and causes the rest of the original query to be ignored by starting a SQL comment (`--`). Thus, the database server sees, and executes, three separate statements like so: ```SQL INSERT INTO Students(firstname) VALUES ('Robert'); DROP TABLE Students; --'); ``` And thus the school has lost this year's student records (at least they had last years' backed up?). The original mitigation for this attack was to make sure that any untrustworthy user input was properly escaped (or "sanitized"), and many frameworks provided utility functions for this, such as PHP's [`mysqli::real_escape_string()`](https://www.php.net/manual/en/mysqli.real-escape-string.php) (not to be confused with the obsolete [`mysql_real_escape_string()`](https://www.php.net/manual/en/function.mysql-real-escape-string) or [`mysql_escape_string()`](https://www.php.net/manual/en/function.mysql-escape-string.php)). These would prefix any syntactically significant characters (in this case, quotation marks) with a backslash, so it's less likely to affect the database server's interpretation of the query: ```php $student_name = $mysqli->real_escape_string("Robert');DROP TABLE Students;--"); /* Everything is okay now as the dastardly single-quote has been inactivated by the backslash: "INSERT INTO Students (name) VALUES ('Robert\');DROP TABLE Students;--');" */ $query = sprintf("INSERT INTO Students (name) VALUES ('%s')", $student_name); ``` The database server sees the backslash and knows that the single-quote is part of the string content, not its terminating character. However, this was something that you still had to _remember_ to do, making it only half a solution. Additionally, properly escaping the string requires knowledge of the current character set of the connection which is why the `mysqli` object is a required parameter (or the receiver in object-oriented style). And you could always just forget to wrap the string parameter in quotes (`'%s'`) in the first place, which these wouldn't help with. Even when everything is working correctly, formatting dynamic data into a query still requires the database server to re-parse and generate a new query plan with every new variant--caching helps, but is not a silver bullet. #### Prepared Statements to the rescue! These solve both problems (injection and re-parsing) by **completely separating** the query from any dynamic input data. Instead of formatting data into the query, you use a (database-specific) token to signify a value that will be passed separately: ```SQL -- MySQL INSERT INTO Students (name) VALUES(?); -- Postgres and SQLite INSERT INTO Students (name) VALUES($1); ``` The database will substitute a given value when _executing_ the query, long after it's finished parsing it. The database will effectively treat the parameter as a variable. There is, by design, **no way** for a query parameter to modify the SQL of a query, unless you're using some `exec()`-like SQL function that lets you execute a string as a query, but then hopefully you know what you're doing. In fact, parsing and executing prepared statements are explicitly separate steps in pretty much every database's protocol, where the query string, without any values attached, is parsed first and given an identifier, then a separate execution step simply passes that identifier along with the values to substitute. The response from the initial parsing often contains useful metadata about the query, which SQLx's query macros use to great effect (see "How do the query macros work under the hood?" below). Unfortunately, query parameters do not appear to be standardized, as every database has a different syntax. Look through the project for specific examples for your database, and consult your database manual about prepared statements for more information. The syntax SQLite supports is effectively a superset of many databases' syntaxes, including MySQL and Postgres. To simplify our examples, we use the same syntax for Postgres and SQLite; though SQLite's syntax technically allows alphanumeric identifiers, that's not currently exposed in SQLx, and it's expected to be a numeric 1-based index like Postgres. Some databases, like MySQL and PostgreSQL, may have special statements that let the user explicitly create and execute prepared statements (often `PREPARE` and `EXECUTE`, respectively), but most of the time an application, or library like SQLx, will interact with prepared statements using specialized messages in the database's client/server protocol. Prepared statements created through this protocol may or may not be accessible using explicit SQL statements, depending on the database flavor. Since the dynamic data is handled separately, an application only needs to prepare a statement once, and then it can execute it as many times as it wants with all kinds of different data (at least of the same type and number). Prepared statements are generally tracked per-connection, so an application may need to re-prepare a statement several times over its lifetime as it opens new connections. If it uses a connection pool, ideally all connections will eventually have all statements already prepared (assuming a closed set of statements), so the overhead of parsing and generating a query plan is amortized. Query parameters are also usually transmitted in a compact binary format, which saves bandwidth over having to send them as human-readable strings. Because of the obvious security and performance benefits of prepared statements, the design of SQLx tries to make them as easy to use and transparent as possible. The `sqlx::query*()` family of functions, as well as the `sqlx::query*!()` macros, will always prefer prepared statements. This was an explicit goal from day one. SQLx will **never** substitute query parameters for values on the client-side, it will always let the database server handle that. We have concepts for making certain usage patterns easier, like expanding a dynamic list of parameters (e.g. `?, ?, ?, ?, ...`) since MySQL and SQLite don't really support arrays, but will never simply format data into a query implicitly. Our pervasive use of prepared statements can cause some problems with third-party database implementations, e.g. projects like CockroachDB or PGBouncer that support the Postgres protocol but have their own semantics. In this case, you might try setting [`.persistent(false)`](https://docs.rs/sqlx/latest/sqlx/query/struct.Query.html#method.persistent) before executing a query, which will cause the connection not to retain the prepared statement after executing it. Not all SQL statements are allowed in prepared statements, either. As a general rule, DML (Data Manipulation Language, i.e. `SELECT`, `INSERT`, `UPDATE`, `DELETE`) is allowed while DDL (Data Definition Language, e.g. `CREATE TABLE`, `ALTER TABLE`, etc.) is not. Consult your database manual for details. To execute DDL requires using a different API than `query*()` or `query*!()` in SQLx. Ideally, we'd like to encourage you to use SQLx's built-in support for migrations (though that could be better documented, we'll get to it). However, in the event that isn't feasible, or you have different needs, you can execute pretty much any statement, including multiple statements separated by semicolons (`;`), by directly invoking methods of the [`Executor` trait](https://docs.rs/sqlx/latest/sqlx/trait.Executor.html#method.execute) on any type that implements it, and passing your query string, e.g.: ```rust use sqlx::postgres::PgConnection; use sqlx::Executor; let mut conn: PgConnection = connect().await?; conn .execute( "CREATE TABLE IF NOT EXISTS StudentContactInfo (student_id INTEGER, person_name TEXT, relation TEXT, phone TEXT);\ INSERT INTO StudentContactInfo (student_id, person_name, relation, phone) \ SELECT student_id, guardian_name, guardian_relation, guardian_phone FROM Students;\ ALTER TABLE Students DROP guardian_name, guardian_relation, guardian_phone;" ) .await?; ``` This is also pending a redesign to make it easier to discover and utilize. ---------------------------------------------------------------- ### How can I do a `SELECT ... WHERE foo IN (...)` query? In the future SQLx will support binding arrays as a comma-separated list for every database, but unfortunately there's no general solution for that currently in SQLx itself. You would need to manually generate the query, at which point it cannot be used with the macros. However, **in Postgres** you can work around this limitation by binding the arrays directly and using `= ANY()`: ```rust let db: PgPool = /* ... */; let foo_ids: Vec = vec![/* ... */]; let foos = sqlx::query!( "SELECT * FROM foo WHERE id = ANY($1)", // a bug of the parameter typechecking code requires all array parameters to be slices &foo_ids[..] ) .fetch_all(&db) .await?; ``` Even when SQLx gains generic placeholder expansion for arrays, this will still be the optimal way to do it for Postgres, as comma-expansion means each possible length of the array generates a different query (and represents a combinatorial explosion if more than one array is used). Note that you can use any operator that returns a boolean, but beware that `!= ANY($1)` is **not equivalent** to `NOT IN (...)` as it effectively works like this: `lhs != ANY(rhs) -> false OR lhs != rhs[0] OR lhs != rhs[1] OR ... lhs != rhs[length(rhs) - 1]` The equivalent of `NOT IN (...)` would be `!= ALL($1)`: `lhs != ALL(rhs) -> true AND lhs != rhs[0] AND lhs != rhs[1] AND ... lhs != rhs[length(rhs) - 1]` Note that `ANY` using any operator and passed an empty array will return `false`, thus the leading `false OR ...`. Meanwhile, `ALL` with any operator and passed an empty array will return `true`, thus the leading `true AND ...`. See also: [Postgres Manual, Section 9.24: Row and Array Comparisons](https://www.postgresql.org/docs/current/functions-comparisons.html) ----- ### How can I bind an array to a `VALUES()` clause? How can I do bulk inserts? Like the above, SQLx currently does not support this in the general case right now but will in the future. However, **Postgres** also has a feature to save the day here! You can pass an array to `UNNEST()` and it will treat it as a temporary table: ```rust let foo_texts: Vec = vec![/* ... */]; sqlx::query!( // because `UNNEST()` is a generic function, Postgres needs the cast on the parameter here // in order to know what type to expect there when preparing the query "INSERT INTO foo(text_column) SELECT * FROM UNNEST($1::text[])", &foo_texts[..] ) .execute(&db) .await?; ``` `UNNEST()` can also take more than one array, in which case it'll treat each array as a column in the temporary table: ```rust // this solution currently requires each column to be its own vector // in the future we're aiming to allow binding iterators directly as arrays // so you can take a vector of structs and bind iterators mapping to each field let foo_texts: Vec = vec![/* ... */]; let foo_bools: Vec = vec![/* ... */]; let foo_ints: Vec = vec![/* ... */]; let foo_opt_texts: Vec> = vec![/* ... */]; let foo_opt_naive_dts: Vec> = vec![/* ... */] sqlx::query!( " INSERT INTO foo(text_column, bool_column, int_column, opt_text_column, opt_naive_dt_column) SELECT * FROM UNNEST($1::text[], $2::bool[], $3::int8[], $4::text[], $5::timestamp[]) ", &foo_texts[..], &foo_bools[..], &foo_ints[..], // Due to a limitation in how SQLx typechecks query parameters, `Vec>` is unable to be typechecked. // This demonstrates the explicit type override syntax, which tells SQLx not to typecheck these parameters. // See the documentation for `query!()` for more details. &foo_opt_texts as &[Option], &foo_opt_naive_dts as &[Option] ) .execute(&db) .await?; ``` Again, even with comma-expanded lists in the future this will likely still be the most performant way to run bulk inserts with Postgres--at least until we get around to implementing an interface for `COPY FROM STDIN`, though this solution with `UNNEST()` will still be more flexible as you can use it in queries that are more complex than just inserting into a table. Note that if some vectors are shorter than others, `UNNEST` will fill the corresponding columns with `NULL`s to match the longest vector. For example, if `foo_texts` is length 5, `foo_bools` is length 4, `foo_ints` is length 3, the resulting table will look like this: | Row # | `text_column` | `bool_column` | `int_column` | | ----- | -------------- | -------------- | ------------- | | 1 | `foo_texts[0]` | `foo_bools[0]` | `foo_ints[0]` | | 2 | `foo_texts[1]` | `foo_bools[1]` | `foo_ints[1]` | | 3 | `foo_texts[2]` | `foo_bools[2]` | `foo_ints[2]` | | 4 | `foo_texts[3]` | `foo_bools[3]` | `NULL` | | 5 | `foo_texts[4]` | `NULL` | `NULL` | See Also: * [Postgres Manual, Section 7.2.1.4: Table Functions](https://www.postgresql.org/docs/current/queries-table-expressions.html#QUERIES-TABLEFUNCTIONS) * [Postgres Manual, Section 9.19: Array Functions and Operators](https://www.postgresql.org/docs/current/functions-array.html) ---- ### How do I compile with the macros without needing a database, e.g. in CI? The macros support an offline mode which saves data for existing queries to a `.sqlx` directory, so the macros can just read those instead of talking to a database. See the following: * [the docs for `query!()`](https://docs.rs/sqlx/0.5.5/sqlx/macro.query.html#offline-mode-requires-the-offline-feature) * [the README for `sqlx-cli`](sqlx-cli/README.md#enable-building-in-offline-mode-with-query) To keep `.sqlx` up-to-date you need to run `cargo sqlx prepare` before every commit that adds or changes a query; you can do this with a Git pre-commit hook: ```shell $ echo "cargo sqlx prepare > /dev/null 2>&1; git add .sqlx > /dev/null" > .git/hooks/pre-commit ``` Note that this may make committing take some time as it'll cause your project to be recompiled, and as an ergonomic choice it does _not_ block committing if `cargo sqlx prepare` fails. We're working on a way for the macros to save their data to the filesystem automatically which should be part of SQLx 0.7, so your pre-commit hook would then just need to stage the changed files. This can be enabled by creating a directory and setting the `SQLX_OFFLINE_DIR` environment variable to it before compiling. However, this behaviour is not considered stable and it is still recommended to use `cargo sqlx prepare`. ---- ### How do the query macros work under the hood? The macros work by talking to the database at compile time. When a(n) SQL client asks to create a prepared statement from a query string, the response from the server typically includes information about the following: * the number of bind parameters, and their expected types if the database is capable of inferring that * the number, names and types of result columns, as well as the original table and columns names before aliasing In MySQL/MariaDB, we also get boolean flag signaling if a column is `NOT NULL`, however in Postgres and SQLite, we need to do a bit more work to determine whether a column can be `NULL` or not. After preparing, the Postgres driver will first look up the result columns in their source table and check if they have a `NOT NULL` constraint. Then, it will execute `EXPLAIN (VERBOSE, FORMAT JSON) ` to determine which columns come from half-open joins (LEFT and RIGHT joins), which makes a normally `NOT NULL` column nullable. Since the `EXPLAIN VERBOSE` format is not stable or completely documented, this inference isn't perfect. However, it does err on the side of producing false-positives (marking a column nullable when it's `NOT NULL`) to avoid errors at runtime. If you do encounter false-positives please feel free to open an issue; make sure to include your query, any relevant schema as well as the output of `EXPLAIN (VERBOSE, FORMAT JSON) ` which will make this easier to debug. The SQLite driver will pull the bytecode of the prepared statement and step through it to find any instructions that produce a null value for any column in the output. --- ### Why can't SQLx just look at my database schema/migrations and parse the SQL itself? Take a moment and think of the effort that would be required to do that. To implement this for a single database driver, SQLx would need to: * know how to parse SQL, and not just standard SQL but the specific dialect of that particular database * know how to analyze and typecheck SQL queries in the context of the original schema * if inferring schema from migrations it would need to simulate all the schema-changing effects of those migrations This is effectively reimplementing a good chunk of the database server's frontend, _and_ maintaining and ensuring correctness of that reimplementation, including bugs and idiosyncrasies, for the foreseeable future, for _every_ database we intend to support. Even Sisyphus would pity us. ---- ### Why does my project using sqlx query macros not build on docs.rs? Docs.rs doesn't have access to your database, so it needs to be provided prepared queries in a `.sqlx` directory and be instructed to set the `SQLX_OFFLINE` environment variable to true while compiling your project. Luckily for us, docs.rs creates a `DOCS_RS` environment variable that we can access in a custom build script to achieve this functionality. To do so, first, make sure that you have run `cargo sqlx prepare` to generate a `.sqlx` directory in your project. Next, create a file called `build.rs` in the root of your project directory (at the same level as `Cargo.toml`). Add the following code to it: ```rs fn main() { // When building in docs.rs, we want to set SQLX_OFFLINE mode to true if std::env::var_os("DOCS_RS").is_some() { println!("cargo:rustc-env=SQLX_OFFLINE=true"); } } ``` ================================================ FILE: LICENSE-APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 LaunchBadge, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: LICENSE-MIT ================================================ Copyright (c) 2020 LaunchBadge, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

SQLx

🧰 The Rust SQL Toolkit


Built with ❤️ by The LaunchBadge team

Have a question? Be sure to check the FAQ first!

SQLx is an async, pure Rust SQL crate featuring compile-time checked queries without a DSL. - **Truly Asynchronous**. Built from the ground-up using async/await for maximum concurrency. - **Compile-time checked queries** (if you want). See [SQLx is not an ORM](#sqlx-is-not-an-orm). - **Database Agnostic**. Support for [PostgreSQL], [MySQL], [MariaDB], [SQLite]. - [MSSQL] was supported prior to version 0.7, but has been removed pending a full rewrite of the driver as part of our [SQLx Pro initiative]. - **Pure Rust**. The Postgres and MySQL/MariaDB drivers are written in pure Rust using **zero** unsafe†† code. - **Runtime Agnostic**. Works on different runtimes ([`async-std`] / [`tokio`] / [`actix`]) and TLS backends ([`native-tls`], [`rustls`]). † The SQLite driver uses the libsqlite3 C library as SQLite is an embedded database (the only way we could be pure Rust for SQLite is by porting _all_ of SQLite to Rust). †† SQLx uses `#![forbid(unsafe_code)]` unless the `sqlite` feature is enabled. The SQLite driver directly invokes the SQLite3 API via `libsqlite3-sys`, which requires `unsafe`. [postgresql]: http://postgresql.org/ [sqlite]: https://sqlite.org/ [mysql]: https://www.mysql.com/ [mariadb]: https://www.mariadb.org/ [mssql]: https://www.microsoft.com/en-us/sql-server [SQLx Pro initiative]: https://github.com/launchbadge/sqlx/discussions/1616 --- - Cross-platform. Being native Rust, SQLx will compile anywhere Rust is supported. - Built-in connection pooling with `sqlx::Pool`. - Row streaming. Data is read asynchronously from the database and decoded on demand. - Automatic statement preparation and caching. When using the high-level query API (`sqlx::query`), statements are prepared and cached per connection. - Simple (unprepared) query execution including fetching results into the same `Row` types used by the high-level API. Supports batch execution and returns results from all statements. - Transport Layer Security (TLS) where supported ([MySQL], [MariaDB] and [PostgreSQL]). - Asynchronous notifications using `LISTEN` and `NOTIFY` for [PostgreSQL]. - Nested transactions with support for save points. - `Any` database driver for changing the database driver at runtime. An `AnyPool` connects to the driver indicated by the URL scheme. ## Install SQLx is compatible with the [`async-std`], [`tokio`], and [`actix`] runtimes; and, the [`native-tls`] and [`rustls`] TLS backends. When adding the dependency, you must choose a runtime feature that is `runtime` + `tls`. [`async-std`]: https://github.com/async-rs/async-std [`tokio`]: https://github.com/tokio-rs/tokio [`actix`]: https://github.com/actix/actix-net [`native-tls`]: https://crates.io/crates/native-tls [`rustls`]: https://crates.io/crates/rustls ```toml # Cargo.toml [dependencies] # PICK ONE OF THE FOLLOWING: # tokio (no TLS) sqlx = { version = "0.8", features = [ "runtime-tokio" ] } # tokio + native-tls sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-native-tls" ] } # tokio + rustls with ring and WebPKI CA certificates sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-ring-webpki" ] } # tokio + rustls with ring and platform's native CA certificates sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-ring-native-roots" ] } # tokio + rustls with aws-lc-rs sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-aws-lc-rs" ] } # async-std (no TLS) sqlx = { version = "0.8", features = [ "runtime-async-std" ] } # async-std + native-tls sqlx = { version = "0.8", features = [ "runtime-async-std", "tls-native-tls" ] } # async-std + rustls with ring and WebPKI CA certificates sqlx = { version = "0.8", features = [ "runtime-async-std", "tls-rustls-ring-webpki" ] } # async-std + rustls with ring and platform's native CA certificates sqlx = { version = "0.8", features = [ "runtime-async-std", "tls-rustls-ring-native-roots" ] } # async-std + rustls with aws-lc-rs sqlx = { version = "0.8", features = [ "runtime-async-std", "tls-rustls-aws-lc-rs" ] } ``` #### Cargo Feature Flags For backward-compatibility reasons, the runtime and TLS features can either be chosen together as a single feature, or separately. For forward compatibility, you should use the separate runtime and TLS features as the combination features may be removed in the future. - `runtime-async-std`: Use the `async-std` runtime without enabling a TLS backend. - `runtime-tokio`: Use the `tokio` runtime without enabling a TLS backend. - Actix-web is fully compatible with Tokio and so a separate runtime feature is no longer needed. - `tls-native-tls`: Use the `native-tls` TLS backend (OpenSSL on *nix, SChannel on Windows, Secure Transport on macOS). - `tls-rustls`: Use the `rustls` TLS backend (cross-platform backend, only supports TLS 1.2 and 1.3). - `postgres`: Add support for the Postgres database server. - `mysql`: Add support for the MySQL/MariaDB database server. - `mssql`: Add support for the MSSQL database server. - `sqlite`: Add support for the self-contained [SQLite](https://sqlite.org/) database engine with SQLite bundled and statically-linked. - `sqlite-unbundled`: The same as above (`sqlite`), but link SQLite from the system instead of the bundled version. * Allows updating SQLite independently of SQLx or using forked versions. * You must have SQLite installed on the system or provide a path to the library at build time. See [the `rusqlite` README](https://github.com/rusqlite/rusqlite?tab=readme-ov-file#notes-on-building-rusqlite-and-libsqlite3-sys) for details. * May result in link errors if the SQLite version is too old. Version `3.20.0` or newer is recommended. * Can increase build time due to the use of bindgen. - `sqlite-preupdate-hook`: enables SQLite's [preupdate hook](https://sqlite.org/c3ref/preupdate_count.html) API. * Exposed as a separate feature because it's generally not enabled by default. * Using this feature with `sqlite-unbundled` may cause linker failures if the system SQLite version does not support it. - `any`: Add support for the `Any` database driver, which can proxy to a database driver at runtime. - `derive`: Add support for the derive family macros, those are `FromRow`, `Type`, `Encode`, `Decode`. - `macros`: Add support for the `query*!` macros, which allows compile-time checked queries. - `migrate`: Add support for the migration management and `migrate!` macro, which allow compile-time embedded migrations. - `uuid`: Add support for UUID. - `chrono`: Add support for date and time types from `chrono`. - `time`: Add support for date and time types from `time` crate (alternative to `chrono`, which is preferred by `query!` macro, if both enabled) - `bstr`: Add support for `bstr::BString`. - `bigdecimal`: Add support for `NUMERIC` using the `bigdecimal` crate. - `rust_decimal`: Add support for `NUMERIC` using the `rust_decimal` crate. - `ipnet`: Add support for `INET` and `CIDR` (in postgres) using the `ipnet` crate. - `ipnetwork`: Add support for `INET` and `CIDR` (in postgres) using the `ipnetwork` crate. - `json`: Add support for `JSON` and `JSONB` (in postgres) using the `serde_json` crate. - Offline mode is now always enabled. See [sqlx-cli/README.md][readme-offline]. [readme-offline]: sqlx-cli/README.md#enable-building-in-offline-mode-with-query ## SQLx is not an ORM! SQLx supports **compile-time checked queries**. It does not, however, do this by providing a Rust API or DSL (domain-specific language) for building queries. Instead, it provides macros that take regular SQL as input and ensure that it is valid for your database. The way this works is that SQLx connects to your development DB at compile time to have the database itself verify (and return some info on) your SQL queries. This has some potentially surprising implications: - Since SQLx never has to parse the SQL string itself, any syntax that the development DB accepts can be used (including things added by database extensions) - Due to the different amount of information databases let you retrieve about queries, the extent of SQL verification you get from the query macros depends on the database **If you are looking for an (asynchronous) ORM,** you can check out our new [Ecosystem wiki page](https://github.com/launchbadge/sqlx/wiki/Ecosystem#orms)! [`ormx`]: https://crates.io/crates/ormx [`SeaORM`]: https://github.com/SeaQL/sea-orm ## Usage See the `examples/` folder for more in-depth usage. ### Quickstart ```rust use sqlx::postgres::PgPoolOptions; // use sqlx::mysql::MySqlPoolOptions; // etc. #[async_std::main] // Requires the `attributes` feature of `async-std` // or #[tokio::main] // or #[actix_web::main] async fn main() -> Result<(), sqlx::Error> { // Create a connection pool // for MySQL/MariaDB, use MySqlPoolOptions::new() // for SQLite, use SqlitePoolOptions::new() // etc. let pool = PgPoolOptions::new() .max_connections(5) .connect("postgres://postgres:password@localhost/test").await?; // Make a simple query to return the given parameter (use a question mark `?` instead of `$1` for MySQL/MariaDB) let row: (i64,) = sqlx::query_as("SELECT $1") .bind(150_i64) .fetch_one(&pool).await?; assert_eq!(row.0, 150); Ok(()) } ``` ### Connecting A single connection can be established using any of the database connection types and calling `connect()`. ```rust use sqlx::Connection; let conn = SqliteConnection::connect("sqlite::memory:").await?; ``` Generally, you will want to instead create a connection pool (`sqlx::Pool`) for the application to regulate how many server-side connections it's using. ```rust let pool = MySqlPool::connect("mysql://user:pass@host/database").await?; ``` ### Querying In SQL, queries can be separated into prepared (parameterized) or unprepared (simple). Prepared queries have their query plan _cached_, use a binary mode of communication (lower bandwidth and faster decoding), and utilize parameters to avoid SQL injection. Unprepared queries are simple and intended only for use where a prepared statement will not work, such as various database commands (e.g., `PRAGMA` or `SET` or `BEGIN`). SQLx supports all operations with both types of queries. In SQLx, a `&str` is treated as an unprepared query, and a `Query` or `QueryAs` struct is treated as a prepared query. ```rust // low-level, Executor trait conn.execute("BEGIN").await?; // unprepared, simple query conn.execute(sqlx::query("DELETE FROM table")).await?; // prepared, cached query ``` We should prefer to use the high-level `query` interface whenever possible. To make this easier, there are finalizers on the type to avoid the need to wrap with an executor. ```rust sqlx::query("DELETE FROM table").execute(&mut conn).await?; sqlx::query("DELETE FROM table").execute(&pool).await?; ``` The `execute` query finalizer returns the number of affected rows, if any, and drops all received results. In addition, there are `fetch`, `fetch_one`, `fetch_optional`, and `fetch_all` to receive results. The `Query` type returned from `sqlx::query` will return `Row<'conn>` from the database. Column values can be accessed by ordinal or by name with `row.get()`. As the `Row` retains an immutable borrow on the connection, only one `Row` may exist at a time. The `fetch` query finalizer returns a stream-like type that iterates through the rows in the result sets. ```rust // provides `try_next` use futures_util::TryStreamExt; // provides `try_get` use sqlx::Row; let mut rows = sqlx::query("SELECT * FROM users WHERE email = ?") .bind(email) .fetch(&mut conn); while let Some(row) = rows.try_next().await? { // map the row into a user-defined domain type let email: &str = row.try_get("email")?; } ``` To assist with mapping the row into a domain type, one of two idioms may be used: ```rust let mut stream = sqlx::query("SELECT * FROM users") .map(|row: PgRow| { // map the row into a user-defined domain type }) .fetch(&mut conn); ``` ```rust #[derive(sqlx::FromRow)] struct User { name: String, id: i64 } let mut stream = sqlx::query_as::<_, User>("SELECT * FROM users WHERE email = ? OR name = ?") .bind(user_email) .bind(user_name) .fetch(&mut conn); ``` Instead of a stream of results, we can use `fetch_one` or `fetch_optional` to request one required or optional result from the database. ### Compile-time verification We can use the macro, `sqlx::query!` to achieve compile-time syntactic and semantic verification of the SQL, with an output to an anonymous record type where each SQL column is a Rust field (using raw identifiers where needed). ```rust let countries = sqlx::query!( " SELECT country, COUNT(*) as count FROM users GROUP BY country WHERE organization = ? ", organization ) .fetch_all(&pool) // -> Vec<{ country: String, count: i64 }> .await?; // countries[0].country // countries[0].count ``` Differences from `query()`: - The input (or bind) parameters must be given all at once (and they are compile-time validated to be the right number and the right type). - The output type is an anonymous record. In the above example the type would be similar to: ```rust { country: String, count: i64 } ``` - The `DATABASE_URL` environment variable must be set at build time to a database which it can prepare queries against; the database does not have to contain any data but must be the same kind (MySQL, Postgres, etc.) and have the same schema as the database you will be connecting to at runtime. For convenience, you can use [a `.env` file][dotenv]1 to set DATABASE_URL so that you don't have to pass it every time: ``` DATABASE_URL=mysql://localhost/my_database ``` [dotenv]: https://github.com/dotenv-rs/dotenv#examples The biggest downside to `query!()` is that the output type cannot be named (due to Rust not officially supporting anonymous records). To address that, there is a `query_as!()` macro that is mostly identical except that you can name the output type. ```rust // no traits are needed struct Country { country: String, count: i64 } let countries = sqlx::query_as!(Country, " SELECT country, COUNT(*) as count FROM users GROUP BY country WHERE organization = ? ", organization ) .fetch_all(&pool) // -> Vec .await?; // countries[0].country // countries[0].count ``` To avoid the need of having a development database around to compile the project even when no modifications (to the database-accessing parts of the code) are done, you can enable "offline mode" to cache the results of the SQL query analysis using the `sqlx` command-line tool. See [sqlx-cli/README.md](./sqlx-cli/README.md#enable-building-in-offline-mode-with-query). Compile-time verified queries do quite a bit of work at compile time. Incremental actions like `cargo check` and `cargo build` can be significantly faster when using an optimized build by putting the following in your `Cargo.toml` (More information in the [Profiles section](https://doc.rust-lang.org/cargo/reference/profiles.html) of The Cargo Book) ```toml [profile.dev.package.sqlx-macros] opt-level = 3 ``` 1 The `dotenv` crate itself appears abandoned as of [December 2021](https://github.com/dotenv-rs/dotenv/issues/74) so we now use the `dotenvy` crate instead. The file format is the same. ## Safety This crate uses `#![forbid(unsafe_code)]` to ensure everything is implemented in 100% Safe Rust. If the `sqlite` feature is enabled, this is downgraded to `#![deny(unsafe_code)]` with `#![allow(unsafe_code)]` on the `sqlx::sqlite` module. There are several places where we interact with the C SQLite API. We try to document each call for the invariants we're assuming. We absolutely welcome auditing of, and feedback on, our unsafe code usage. ## License Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ## Contribution Unless you explicitly state otherwise, any Contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. ================================================ FILE: benches/sqlite/describe.rs ================================================ use criterion::BenchmarkId; use criterion::Criterion; use criterion::{criterion_group, criterion_main}; use sqlx::sqlite::{Sqlite, SqliteConnection}; use sqlx::Executor; use sqlx_test::new; // Here we have an async function to benchmark async fn do_describe_trivial(db: &std::cell::RefCell) { db.borrow_mut().describe("select 1").await.unwrap(); } async fn do_describe_recursive(db: &std::cell::RefCell) { db.borrow_mut() .describe( r#" WITH RECURSIVE schedule(begin_date) AS MATERIALIZED ( SELECT datetime('2022-10-01') WHERE datetime('2022-10-01') < datetime('2022-11-03') UNION ALL SELECT datetime(begin_date,'+1 day') FROM schedule WHERE datetime(begin_date) < datetime(?2) ) SELECT begin_date FROM schedule GROUP BY begin_date "#, ) .await .unwrap(); } async fn do_describe_insert(db: &std::cell::RefCell) { db.borrow_mut() .describe("INSERT INTO tweet (id, text) VALUES (2, 'Hello') RETURNING *") .await .unwrap(); } async fn do_describe_insert_fks(db: &std::cell::RefCell) { db.borrow_mut() .describe("insert into statements (text) values ('a') returning id") .await .unwrap(); } async fn init_connection() -> SqliteConnection { let mut conn = new::().await.unwrap(); conn.execute( r#" CREATE TEMPORARY TABLE statements ( id integer not null primary key, text text not null ); CREATE TEMPORARY TABLE votes1 (statement_id integer not null references statements(id)); CREATE TEMPORARY TABLE votes2 (statement_id integer not null references statements(id)); CREATE TEMPORARY TABLE votes3 (statement_id integer not null references statements(id)); CREATE TEMPORARY TABLE votes4 (statement_id integer not null references statements(id)); CREATE TEMPORARY TABLE votes5 (statement_id integer not null references statements(id)); CREATE TEMPORARY TABLE votes6 (statement_id integer not null references statements(id)); --CREATE TEMPORARY TABLE votes7 (statement_id integer not null references statements(id)); --CREATE TEMPORARY TABLE votes8 (statement_id integer not null references statements(id)); --CREATE TEMPORARY TABLE votes9 (statement_id integer not null references statements(id)); --CREATE TEMPORARY TABLE votes10 (statement_id integer not null references statements(id)); --CREATE TEMPORARY TABLE votes11 (statement_id integer not null references statements(id)); "#, ) .await .unwrap(); conn } fn describe_trivial(c: &mut Criterion) { let runtime = tokio::runtime::Runtime::new().unwrap(); let db = std::cell::RefCell::new(runtime.block_on(init_connection())); c.bench_with_input( BenchmarkId::new("select", "trivial"), &db, move |b, db_ref| { // Insert a call to `to_async` to convert the bencher to async mode. // The timing loops are the same as with the normal bencher. b.to_async(&runtime).iter(|| do_describe_trivial(db_ref)); }, ); } fn describe_recursive(c: &mut Criterion) { let runtime = tokio::runtime::Runtime::new().unwrap(); let db = std::cell::RefCell::new(runtime.block_on(init_connection())); c.bench_with_input( BenchmarkId::new("select", "recursive"), &db, move |b, db_ref| { // Insert a call to `to_async` to convert the bencher to async mode. // The timing loops are the same as with the normal bencher. b.to_async(&runtime).iter(|| do_describe_recursive(db_ref)); }, ); } fn describe_insert(c: &mut Criterion) { let runtime = tokio::runtime::Runtime::new().unwrap(); let db = std::cell::RefCell::new(runtime.block_on(init_connection())); c.bench_with_input( BenchmarkId::new("insert", "returning"), &db, move |b, db_ref| { // Insert a call to `to_async` to convert the bencher to async mode. // The timing loops are the same as with the normal bencher. b.to_async(&runtime).iter(|| do_describe_insert(db_ref)); }, ); } fn describe_insert_fks(c: &mut Criterion) { let runtime = tokio::runtime::Runtime::new().unwrap(); let db = std::cell::RefCell::new(runtime.block_on(init_connection())); c.bench_with_input(BenchmarkId::new("insert", "fks"), &db, move |b, db_ref| { // Insert a call to `to_async` to convert the bencher to async mode. // The timing loops are the same as with the normal bencher. b.to_async(&runtime).iter(|| do_describe_insert_fks(db_ref)); }); } criterion_group!( benches, describe_trivial, describe_recursive, describe_insert, describe_insert_fks ); criterion_main!(benches); ================================================ FILE: clippy.toml ================================================ [[disallowed-methods]] path = "core::cmp::Ord::min" reason = ''' too easy to misread `x.min(y)` as "let the minimum value of `x` be `y`" when it actually means the exact opposite; use `std::cmp::min` instead. ''' [[disallowed-methods]] path = "core::cmp::Ord::max" reason = ''' too easy to misread `x.max(y)` as "let the maximum value of `x` be `y`" when it actually means the exact opposite; use `std::cmp::max` instead. ''' ================================================ FILE: contrib/ide/vscode/settings.json ================================================ { "rust-analyzer.assist.importMergeBehaviour": "last" } ================================================ FILE: examples/.gitignore ================================================ sqlite/todos/todos.db ================================================ FILE: examples/mysql/todos/Cargo.toml ================================================ [package] name = "sqlx-example-mysql-todos" version = "0.1.0" edition = "2021" workspace = "../../../" [dependencies] anyhow = "1.0" sqlx = { path = "../../../", features = [ "mysql", "runtime-tokio", "tls-native-tls" ] } clap = { version = "4", features = ["derive"] } tokio = { version = "1.20.0", features = ["rt", "macros"]} ================================================ FILE: examples/mysql/todos/README.md ================================================ # TODOs Example ## Setup 1. Declare the database URL ``` export DATABASE_URL="mysql://root:password@localhost/todos" ``` 2. Create the database. ``` $ sqlx db create ``` 3. Run sql migrations ``` $ sqlx migrate run ``` ## Usage Add a todo ``` cargo run -- add "todo description" ``` Complete a todo. ``` cargo run -- done ``` List all todos ``` cargo run ``` ================================================ FILE: examples/mysql/todos/migrations/20200718111257_todos.sql ================================================ CREATE TABLE IF NOT EXISTS todos ( id BIGINT UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT, description TEXT NOT NULL, done BOOLEAN NOT NULL DEFAULT FALSE ); ================================================ FILE: examples/mysql/todos/src/main.rs ================================================ use clap::{Parser, Subcommand}; use sqlx::mysql::MySqlPool; use std::env; #[derive(Parser)] struct Args { #[command(subcommand)] cmd: Option, } #[derive(Subcommand)] enum Command { Add { description: String }, Done { id: u64 }, } #[tokio::main(flavor = "current_thread")] async fn main() -> anyhow::Result<()> { let args = Args::parse(); let pool = MySqlPool::connect(&env::var("DATABASE_URL")?).await?; match args.cmd { Some(Command::Add { description }) => { println!("Adding new todo with description '{description}'"); let todo_id = add_todo(&pool, description).await?; println!("Added new todo with id {todo_id}"); } Some(Command::Done { id }) => { println!("Marking todo {id} as done"); if complete_todo(&pool, id).await? { println!("Todo {id} is marked as done"); } else { println!("Invalid id {id}"); } } None => { println!("Printing list of all todos"); list_todos(&pool).await?; } } Ok(()) } async fn add_todo(pool: &MySqlPool, description: String) -> anyhow::Result { // Insert the task, then obtain the ID of this row let todo_id = sqlx::query!( r#" INSERT INTO todos ( description ) VALUES ( ? ) "#, description ) .execute(pool) .await? .last_insert_id(); Ok(todo_id) } async fn complete_todo(pool: &MySqlPool, id: u64) -> anyhow::Result { let rows_affected = sqlx::query!( r#" UPDATE todos SET done = TRUE WHERE id = ? "#, id ) .execute(pool) .await? .rows_affected(); Ok(rows_affected > 0) } async fn list_todos(pool: &MySqlPool) -> anyhow::Result<()> { let recs = sqlx::query!( r#" SELECT id, description, done FROM todos ORDER BY id "# ) .fetch_all(pool) .await?; // NOTE: Booleans in MySQL are stored as `TINYINT(1)` / `i8` // 0 = false, non-0 = true for rec in recs { println!( "- [{}] {}: {}", if rec.done != 0 { "x" } else { " " }, rec.id, &rec.description, ); } Ok(()) } ================================================ FILE: examples/postgres/axum-social-with-tests/Cargo.toml ================================================ [package] name = "sqlx-example-postgres-axum-social" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] # Primary crates axum = { version = "0.5.13", features = ["macros"] } sqlx = { path = "../../../", features = [ "runtime-tokio", "tls-rustls-ring", "postgres", "time", "uuid" ] } tokio = { version = "1.20.1", features = ["rt-multi-thread", "macros"] } # Important secondary crates argon2 = "0.4.1" rand = "0.8.5" regex = "1.6.0" serde = "1.0.140" serde_with = { version = "2.0.0", features = ["time_0_3"] } time = "0.3.11" uuid = { version = "1.1.2", features = ["serde"] } validator = { version = "0.16.0", features = ["derive"] } # Auxilliary crates anyhow = "1.0.58" dotenvy = "0.15.1" thiserror = "2.0.0" tracing = "0.1.35" [dev-dependencies] serde_json = "1.0.82" tower = "0.4.13" ================================================ FILE: examples/postgres/axum-social-with-tests/README.md ================================================ This example demonstrates how to write integration tests for an API build with [Axum] and SQLx using `#[sqlx::test]`. See also: https://github.com/tokio-rs/axum/blob/main/examples/testing # Warning For the sake of brevity, this project omits numerous critical security precautions. You can use it as a starting point, but deploy to production at your own risk! [Axum]: https://github.com/tokio-rs/axum ================================================ FILE: examples/postgres/axum-social-with-tests/migrations/1_user.sql ================================================ create table "user" ( user_id uuid primary key default gen_random_uuid(), username text unique not null, password_hash text not null ); ================================================ FILE: examples/postgres/axum-social-with-tests/migrations/2_post.sql ================================================ create table post ( post_id uuid primary key default gen_random_uuid(), user_id uuid not null references "user"(user_id), content text not null, created_at timestamptz not null default now() ); create index on post(created_at desc); ================================================ FILE: examples/postgres/axum-social-with-tests/migrations/3_comment.sql ================================================ create table comment ( comment_id uuid primary key default gen_random_uuid(), post_id uuid not null references post(post_id), user_id uuid not null references "user"(user_id), content text not null, created_at timestamptz not null default now() ); create index on comment(post_id, created_at); ================================================ FILE: examples/postgres/axum-social-with-tests/src/http/error.rs ================================================ use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use axum::Json; use serde_with::DisplayFromStr; use validator::ValidationErrors; /// An API-friendly error type. #[derive(thiserror::Error, Debug)] pub enum Error { /// A SQLx call returned an error. /// /// The exact error contents are not reported to the user in order to avoid leaking /// information about database internals. #[error("an internal database error occurred")] Sqlx(#[from] sqlx::Error), /// Similarly, we don't want to report random `anyhow` errors to the user. #[error("an internal server error occurred")] Anyhow(#[from] anyhow::Error), #[error("validation error in request body")] InvalidEntity(#[from] ValidationErrors), #[error("{0}")] UnprocessableEntity(String), #[error("{0}")] Conflict(String), } impl IntoResponse for Error { fn into_response(self) -> Response { #[serde_with::serde_as] #[serde_with::skip_serializing_none] #[derive(serde::Serialize)] struct ErrorResponse<'a> { // Serialize the `Display` output as the error message #[serde_as(as = "DisplayFromStr")] message: &'a Error, errors: Option<&'a ValidationErrors>, } let errors = match &self { Error::InvalidEntity(errors) => Some(errors), _ => None, }; // Normally you wouldn't just print this, but it's useful for debugging without // using a logging framework. println!("API error: {self:?}"); ( self.status_code(), Json(ErrorResponse { message: &self, errors, }), ) .into_response() } } impl Error { fn status_code(&self) -> StatusCode { use Error::*; match self { Sqlx(_) | Anyhow(_) => StatusCode::INTERNAL_SERVER_ERROR, InvalidEntity(_) | UnprocessableEntity(_) => StatusCode::UNPROCESSABLE_ENTITY, Conflict(_) => StatusCode::CONFLICT, } } } ================================================ FILE: examples/postgres/axum-social-with-tests/src/http/mod.rs ================================================ use anyhow::Context; use axum::{Extension, Router}; use sqlx::PgPool; mod error; mod post; mod user; pub use self::error::Error; pub type Result = ::std::result::Result; pub fn app(db: PgPool) -> Router { Router::new() .merge(user::router()) .merge(post::router()) .layer(Extension(db)) } pub async fn serve(db: PgPool) -> anyhow::Result<()> { axum::Server::bind(&"0.0.0.0:8080".parse().unwrap()) .serve(app(db).into_make_service()) .await .context("failed to serve API") } ================================================ FILE: examples/postgres/axum-social-with-tests/src/http/post/comment.rs ================================================ use axum::extract::Path; use axum::{Extension, Json, Router}; use axum::routing::get; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use crate::http::user::UserAuth; use sqlx::PgPool; use validator::Validate; use crate::http::Result; use time::format_description::well_known::Rfc3339; use uuid::Uuid; pub fn router() -> Router { Router::new().route( "/v1/post/:postId/comment", get(get_post_comments).post(create_post_comment), ) } #[derive(Deserialize, Validate)] #[serde(rename_all = "camelCase")] struct CreateCommentRequest { auth: UserAuth, #[validate(length(min = 1, max = 1000))] content: String, } #[serde_with::serde_as] #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct Comment { comment_id: Uuid, username: String, content: String, // `OffsetDateTime`'s default serialization format is not standard. #[serde_as(as = "Rfc3339")] created_at: OffsetDateTime, } // #[axum::debug_handler] // very useful! async fn create_post_comment( db: Extension, Path(post_id): Path, Json(req): Json, ) -> Result> { req.validate()?; let user_id = req.auth.verify(&*db).await?; let comment = sqlx::query_as!( Comment, // language=PostgreSQL r#" with inserted_comment as ( insert into comment(user_id, post_id, content) values ($1, $2, $3) returning comment_id, user_id, content, created_at ) select comment_id, username, content, created_at from inserted_comment inner join "user" using (user_id) "#, user_id, post_id, req.content ) .fetch_one(&*db) .await?; Ok(Json(comment)) } /// Returns comments in ascending chronological order. async fn get_post_comments( db: Extension, Path(post_id): Path, ) -> Result>> { // Note: normally you'd want to put a `LIMIT` on this as well, // though that would also necessitate implementing pagination. let comments = sqlx::query_as!( Comment, // language=PostgreSQL r#" select comment_id, username, content, created_at from comment inner join "user" using (user_id) where post_id = $1 order by created_at "#, post_id ) .fetch_all(&*db) .await?; Ok(Json(comments)) } ================================================ FILE: examples/postgres/axum-social-with-tests/src/http/post/mod.rs ================================================ use axum::{Extension, Json, Router}; use axum::routing::get; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use crate::http::user::UserAuth; use sqlx::PgPool; use validator::Validate; use crate::http::Result; use time::format_description::well_known::Rfc3339; use uuid::Uuid; mod comment; pub fn router() -> Router { Router::new() .route("/v1/post", get(get_posts).post(create_post)) .merge(comment::router()) } #[derive(Deserialize, Validate)] #[serde(rename_all = "camelCase")] struct CreatePostRequest { auth: UserAuth, #[validate(length(min = 1, max = 1000))] content: String, } #[serde_with::serde_as] #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct Post { post_id: Uuid, username: String, content: String, // `OffsetDateTime`'s default serialization format is not standard. #[serde_as(as = "Rfc3339")] created_at: OffsetDateTime, } // #[axum::debug_handler] // very useful! async fn create_post( db: Extension, Json(req): Json, ) -> Result> { req.validate()?; let user_id = req.auth.verify(&*db).await?; let post = sqlx::query_as!( Post, // language=PostgreSQL r#" with inserted_post as ( insert into post(user_id, content) values ($1, $2) returning post_id, user_id, content, created_at ) select post_id, username, content, created_at from inserted_post inner join "user" using (user_id) "#, user_id, req.content ) .fetch_one(&*db) .await?; Ok(Json(post)) } /// Returns posts in descending chronological order. async fn get_posts(db: Extension) -> Result>> { // Note: normally you'd want to put a `LIMIT` on this as well, // though that would also necessitate implementing pagination. let posts = sqlx::query_as!( Post, // language=PostgreSQL r#" select post_id, username, content, created_at from post inner join "user" using (user_id) order by created_at desc "# ) .fetch_all(&*db) .await?; Ok(Json(posts)) } ================================================ FILE: examples/postgres/axum-social-with-tests/src/http/user.rs ================================================ use axum::http::StatusCode; use axum::{routing::post, Extension, Json, Router}; use rand::Rng; use regex::Regex; use std::{sync::LazyLock, time::Duration}; use serde::Deserialize; use sqlx::{PgExecutor, PgPool}; use uuid::Uuid; use validator::Validate; use crate::http::{Error, Result}; pub type UserId = Uuid; pub fn router() -> Router { Router::new().route("/v1/user", post(create_user)) } static USERNAME_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"^[0-9A-Za-z_]+$").unwrap()); // CREATE USER #[derive(Deserialize, Validate)] #[serde(rename_all = "camelCase")] pub struct UserAuth { #[validate(length(min = 3, max = 16), regex = "USERNAME_REGEX")] username: String, #[validate(length(min = 8, max = 32))] password: String, } // WARNING: this API has none of the checks that a normal user signup flow implements, // such as email or phone verification. async fn create_user(db: Extension, Json(req): Json) -> Result { req.validate()?; let UserAuth { username, password } = req; // It would be irresponsible to store passwords in plaintext, however. let password_hash = crate::password::hash(password).await?; sqlx::query!( // language=PostgreSQL r#" insert into "user"(username, password_hash) values ($1, $2) "#, username, password_hash ) .execute(&*db) .await .map_err(|e| match e { sqlx::Error::Database(dbe) if dbe.constraint() == Some("user_username_key") => { Error::Conflict("username taken".into()) } _ => e.into(), })?; Ok(StatusCode::NO_CONTENT) } impl UserAuth { // NOTE: normally we wouldn't want to verify the username and password every time, // but persistent sessions would have complicated the example. pub async fn verify(self, db: impl PgExecutor<'_> + Send) -> Result { self.validate()?; let maybe_user = sqlx::query!( r#"select user_id, password_hash from "user" where username = $1"#, self.username ) .fetch_optional(db) .await?; if let Some(user) = maybe_user { let verified = crate::password::verify(self.password, user.password_hash).await?; if verified { return Ok(user.user_id); } } // Sleep a random amount of time to avoid leaking existence of a user in timing. let sleep_duration = rand::thread_rng().gen_range(Duration::from_millis(100)..=Duration::from_millis(500)); tokio::time::sleep(sleep_duration).await; Err(Error::UnprocessableEntity( "invalid username/password".into(), )) } } ================================================ FILE: examples/postgres/axum-social-with-tests/src/lib.rs ================================================ pub mod http; mod password; ================================================ FILE: examples/postgres/axum-social-with-tests/src/main.rs ================================================ use anyhow::Context; use sqlx::postgres::PgPoolOptions; #[tokio::main] async fn main() -> anyhow::Result<()> { let database_url = dotenvy::var("DATABASE_URL") // The error from `var()` doesn't mention the environment variable. .context("DATABASE_URL must be set")?; let db = PgPoolOptions::new() .max_connections(20) .connect(&database_url) .await .context("failed to connect to DATABASE_URL")?; sqlx::migrate!().run(&db).await?; sqlx_example_postgres_axum_social::http::serve(db).await } ================================================ FILE: examples/postgres/axum-social-with-tests/src/password.rs ================================================ use anyhow::{anyhow, Context}; use tokio::task; use argon2::password_hash::SaltString; use argon2::{password_hash, Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; pub async fn hash(password: String) -> anyhow::Result { task::spawn_blocking(move || { let salt = SaltString::generate(rand::thread_rng()); Ok(Argon2::default() .hash_password(password.as_bytes(), &salt) .map_err(|e| anyhow!(e).context("failed to hash password"))? .to_string()) }) .await .context("panic in hash()")? } pub async fn verify(password: String, hash: String) -> anyhow::Result { task::spawn_blocking(move || { let hash = PasswordHash::new(&hash) .map_err(|e| anyhow!(e).context("BUG: password hash invalid"))?; let res = Argon2::default().verify_password(password.as_bytes(), &hash); match res { Ok(()) => Ok(true), Err(password_hash::Error::Password) => Ok(false), Err(e) => Err(anyhow!(e).context("failed to verify password")), } }) .await .context("panic in verify()")? } ================================================ FILE: examples/postgres/axum-social-with-tests/tests/comment.rs ================================================ use sqlx::PgPool; use sqlx_example_postgres_axum_social::http; use axum::http::{Request, StatusCode}; use tower::ServiceExt; use std::borrow::BorrowMut; use common::{expect_rfc3339_timestamp, expect_uuid, response_json, RequestBuilderExt}; use serde_json::json; mod common; #[sqlx::test(fixtures("users", "posts"))] async fn test_create_comment(db: PgPool) { let mut app = http::app(db); // Happy path! let mut resp1 = app .borrow_mut() .oneshot( Request::post("/v1/post/d9ca2672-24c5-4442-b32f-cd717adffbaa/comment").json(json! { { "auth": { "username": "bob", "password": "pro gamer 1990" }, "content": "lol bet ur still bad, 1v1 me" } }), ) .await .unwrap(); assert_eq!(resp1.status(), StatusCode::OK); let resp1_json = response_json(&mut resp1).await; assert_eq!(resp1_json["username"], "bob"); assert_eq!(resp1_json["content"], "lol bet ur still bad, 1v1 me"); let _comment_id = expect_uuid(&resp1_json["commentId"]); let _created_at = expect_rfc3339_timestamp(&resp1_json["createdAt"]); // Incorrect username let mut resp2 = app .borrow_mut() .oneshot( Request::post("/v1/post/d9ca2672-24c5-4442-b32f-cd717adffbaa/comment").json(json! { { "auth": { "username": "bobbbbbb", "password": "pro gamer 1990" }, "content": "lol bet ur still bad, 1v1 me" } }), ) .await .unwrap(); assert_eq!(resp2.status(), StatusCode::UNPROCESSABLE_ENTITY); let resp2_json = response_json(&mut resp2).await; assert_eq!(resp2_json["message"], "invalid username/password"); // Incorrect password let mut resp3 = app .borrow_mut() .oneshot( Request::post("/v1/post/d9ca2672-24c5-4442-b32f-cd717adffbaa/comment").json(json! { { "auth": { "username": "bob", "password": "pro gamer 1990" }, "content": "lol bet ur still bad, 1v1 me" } }), ) .await .unwrap(); assert_eq!(resp3.status(), StatusCode::UNPROCESSABLE_ENTITY); let resp3_json = response_json(&mut resp3).await; assert_eq!(resp3_json["message"], "invalid username/password"); } #[sqlx::test(fixtures("users", "posts", "comments"))] async fn test_list_comments(db: PgPool) { let mut app = http::app(db); // This only has the happy path. let mut resp = app .borrow_mut() .oneshot(Request::get("/v1/post/d9ca2672-24c5-4442-b32f-cd717adffbaa/comment").empty_body()) .await .unwrap(); assert_eq!(resp.status(), StatusCode::OK); let resp_json = response_json(&mut resp).await; let comments = resp_json .as_array() .expect("expected request to return an array"); assert_eq!(comments.len(), 2); assert_eq!(comments[0]["username"], "bob"); assert_eq!(comments[0]["content"], "lol bet ur still bad, 1v1 me"); let _comment_id = expect_uuid(&comments[0]["commentId"]); let created_at_0 = expect_rfc3339_timestamp(&comments[0]["createdAt"]); assert_eq!(comments[1]["username"], "alice"); assert_eq!(comments[1]["content"], "you're on!"); let _comment_id = expect_uuid(&comments[1]["commentId"]); let created_at_1 = expect_rfc3339_timestamp(&comments[1]["createdAt"]); assert!( created_at_0 < created_at_1, "comments must be assorted in ascending order" ); let mut resp = app .borrow_mut() .oneshot(Request::get("/v1/post/7e3d4d16-a35e-46ba-8223-b4f1debbfbfe/comment").empty_body()) .await .unwrap(); assert_eq!(resp.status(), StatusCode::OK); let resp_json = response_json(&mut resp).await; let comments = resp_json .as_array() .expect("expected request to return an array"); assert_eq!(comments.len(), 1); assert_eq!(comments[0]["username"], "alice"); assert_eq!(comments[0]["content"], "lol you're just mad you lost :P"); let _comment_id = expect_uuid(&comments[0]["commentId"]); let _created_at = expect_rfc3339_timestamp(&comments[0]["createdAt"]); } ================================================ FILE: examples/postgres/axum-social-with-tests/tests/common.rs ================================================ // This is imported by different tests that use different functions. #![allow(dead_code)] use axum::body::{Body, BoxBody, HttpBody}; use axum::http::header::CONTENT_TYPE; use axum::http::{request, Request}; use axum::response::Response; use time::format_description::well_known::Rfc3339; use time::OffsetDateTime; use uuid::Uuid; pub trait RequestBuilderExt { fn json(self, json: serde_json::Value) -> Request; fn empty_body(self) -> Request; } impl RequestBuilderExt for request::Builder { fn json(self, json: serde_json::Value) -> Request { self.header("Content-Type", "application/json") .body(Body::from(json.to_string())) .expect("failed to build request") } fn empty_body(self) -> Request { self.body(Body::empty()).expect("failed to build request") } } pub async fn response_json(resp: &mut Response) -> serde_json::Value { assert_eq!( resp.headers() .get(CONTENT_TYPE) .expect("expected Content-Type"), "application/json" ); let body = resp.body_mut(); let mut bytes = Vec::new(); while let Some(res) = body.data().await { let chunk = res.expect("error reading response body"); bytes.extend_from_slice(&chunk[..]); } serde_json::from_slice(&bytes).expect("failed to read response body as json") } #[track_caller] pub fn expect_string(value: &serde_json::Value) -> &str { value .as_str() .unwrap_or_else(|| panic!("expected string, got {value:?}")) } #[track_caller] pub fn expect_uuid(value: &serde_json::Value) -> Uuid { expect_string(value) .parse::() .unwrap_or_else(|e| panic!("failed to parse UUID from {value:?}: {e}")) } #[track_caller] pub fn expect_rfc3339_timestamp(value: &serde_json::Value) -> OffsetDateTime { let s = expect_string(value); OffsetDateTime::parse(s, &Rfc3339) .unwrap_or_else(|e| panic!("failed to parse RFC-3339 timestamp from {value:?}: {e}")) } ================================================ FILE: examples/postgres/axum-social-with-tests/tests/fixtures/comments.sql ================================================ INSERT INTO public.comment (comment_id, post_id, user_id, content, created_at) VALUES -- from: bob ('3a86b8f8-827b-4f14-94a2-34517b4b5bde', 'd9ca2672-24c5-4442-b32f-cd717adffbaa', 'c994b839-84f4-4509-ad49-59119133d6f5', 'lol bet ur still bad, 1v1 me', '2022-07-29 01:52:31.167673'), -- from: alice ('d6f862b5-2b87-4af4-b15e-6b3398729e6d', 'd9ca2672-24c5-4442-b32f-cd717adffbaa', '51b374f1-93ae-4c5c-89dd-611bda8412ce', 'you''re on!', '2022-07-29 01:53:53.115782'), -- from: alice ('1eed85ae-adae-473c-8d05-b1dae0a1df63', '7e3d4d16-a35e-46ba-8223-b4f1debbfbfe', '51b374f1-93ae-4c5c-89dd-611bda8412ce', 'lol you''re just mad you lost :P', '2022-07-29 01:55:50.116119'); ================================================ FILE: examples/postgres/axum-social-with-tests/tests/fixtures/posts.sql ================================================ INSERT INTO public.post (post_id, user_id, content, created_at) VALUES -- from: alice ('d9ca2672-24c5-4442-b32f-cd717adffbaa', '51b374f1-93ae-4c5c-89dd-611bda8412ce', 'This new computer is blazing fast!', '2022-07-29 01:36:24.679082'), -- from: bob ('7e3d4d16-a35e-46ba-8223-b4f1debbfbfe', 'c994b839-84f4-4509-ad49-59119133d6f5', '@alice is a haxxor', '2022-07-29 01:54:45.823523'); ================================================ FILE: examples/postgres/axum-social-with-tests/tests/fixtures/users.sql ================================================ INSERT INTO public."user" (user_id, username, password_hash) VALUES -- username: "alice"; password: "rustacean since 2015" ('51b374f1-93ae-4c5c-89dd-611bda8412ce', 'alice', '$argon2id$v=19$m=4096,t=3,p=1$3v3ats/tYTXAYs3q9RycDw$ZltwjS3oQwPuNmL9f6DNb+sH5N81dTVZhVNbUQzmmVU'), -- username: "bob"; password: "pro gamer 1990" ('c994b839-84f4-4509-ad49-59119133d6f5', 'bob', '$argon2id$v=19$m=4096,t=3,p=1$1zbkRinUH9WHzkyu8C1Vlg$70pu5Cca/s3d0nh5BYQGkN7+s9cqlNxTE7rFZaUaP4c'); ================================================ FILE: examples/postgres/axum-social-with-tests/tests/post.rs ================================================ use sqlx::PgPool; use sqlx_example_postgres_axum_social::http; use axum::http::{Request, StatusCode}; use tower::ServiceExt; use std::borrow::BorrowMut; use common::{expect_rfc3339_timestamp, expect_uuid, response_json, RequestBuilderExt}; use serde_json::json; mod common; #[sqlx::test(fixtures("users"))] async fn test_create_post(db: PgPool) { let mut app = http::app(db); // Happy path! let mut resp1 = app .borrow_mut() .oneshot(Request::post("/v1/post").json(json! { { "auth": { "username": "alice", "password": "rustacean since 2015" }, "content": "This new computer is blazing fast!" } })) .await .unwrap(); assert_eq!(resp1.status(), StatusCode::OK); let resp1_json = response_json(&mut resp1).await; assert_eq!(resp1_json["username"], "alice"); assert_eq!(resp1_json["content"], "This new computer is blazing fast!"); let _post_id = expect_uuid(&resp1_json["postId"]); let _created_at = expect_rfc3339_timestamp(&resp1_json["createdAt"]); // Incorrect username let mut resp2 = app .borrow_mut() .oneshot(Request::post("/v1/post").json(json! { { "auth": { "username": "aliceee", "password": "rustacean since 2015" }, "content": "This new computer is blazing fast!" } })) .await .unwrap(); assert_eq!(resp2.status(), StatusCode::UNPROCESSABLE_ENTITY); let resp2_json = response_json(&mut resp2).await; assert_eq!(resp2_json["message"], "invalid username/password"); // Incorrect password let mut resp3 = app .borrow_mut() .oneshot(Request::post("/v1/post").json(json! { { "auth": { "username": "alice", "password": "rustaceansince2015" }, "content": "This new computer is blazing fast!" } })) .await .unwrap(); assert_eq!(resp3.status(), StatusCode::UNPROCESSABLE_ENTITY); let resp3_json = response_json(&mut resp3).await; assert_eq!(resp3_json["message"], "invalid username/password"); } #[sqlx::test(fixtures("users", "posts"))] async fn test_list_posts(db: PgPool) { // This only has the happy path. let mut resp = http::app(db) .oneshot(Request::get("/v1/post").empty_body()) .await .unwrap(); assert_eq!(resp.status(), StatusCode::OK); let resp_json = response_json(&mut resp).await; let posts = resp_json .as_array() .expect("expected GET /v1/post to return an array"); assert_eq!(posts.len(), 2); assert_eq!(posts[0]["username"], "bob"); assert_eq!(posts[0]["content"], "@alice is a haxxor"); let _post_id = expect_uuid(&posts[0]["postId"]); let created_at_0 = expect_rfc3339_timestamp(&posts[0]["createdAt"]); assert_eq!(posts[1]["username"], "alice"); assert_eq!(posts[1]["content"], "This new computer is blazing fast!"); let _post_id = expect_uuid(&posts[1]["postId"]); let created_at_1 = expect_rfc3339_timestamp(&posts[1]["createdAt"]); assert!( created_at_0 > created_at_1, "posts must be sorted in descending order" ); } ================================================ FILE: examples/postgres/axum-social-with-tests/tests/user.rs ================================================ use sqlx::PgPool; use sqlx_example_postgres_axum_social::http; use axum::http::{Request, StatusCode}; use tower::ServiceExt; use std::borrow::BorrowMut; use common::{response_json, RequestBuilderExt}; use serde_json::json; mod common; #[sqlx::test] async fn test_create_user(db: PgPool) { let mut app = http::app(db); // Happy path! let resp1 = app .borrow_mut() // We handle JSON objects directly to sanity check the serialization and deserialization .oneshot(Request::post("/v1/user").json(json! {{ "username": "alice", "password": "rustacean since 2015" }})) .await .unwrap(); assert_eq!(resp1.status(), StatusCode::NO_CONTENT); // Username taken let mut resp2 = app .borrow_mut() .oneshot(Request::post("/v1/user").json(json! {{ "username": "alice", "password": "uhhh i forgot" }})) .await .unwrap(); assert_eq!(resp2.status(), StatusCode::CONFLICT); let resp2_json = response_json(&mut resp2).await; assert_eq!(resp2_json["message"], "username taken"); // Invalid username let mut resp3 = app .borrow_mut() .oneshot(Request::post("/v1/user").json(json! {{ "username": "definitely an invalid username", "password": "password" }})) .await .unwrap(); assert_eq!(resp3.status(), StatusCode::UNPROCESSABLE_ENTITY); let resp3_json = response_json(&mut resp3).await; assert_eq!(resp3_json["message"], "validation error in request body"); assert!( resp3_json["errors"]["username"].is_array(), "errors.username is not an array: {:?}", resp3_json ); // Invalid password let mut resp4 = app .borrow_mut() .oneshot(Request::post("/v1/user").json(json! {{ "username": "bobby123", "password": "" }})) .await .unwrap(); assert_eq!(resp4.status(), StatusCode::UNPROCESSABLE_ENTITY); let resp4_json = response_json(&mut resp4).await; assert_eq!(resp4_json["message"], "validation error in request body"); assert!( resp4_json["errors"]["password"].is_array(), "errors.password is not an array: {:?}", resp4_json ); } ================================================ FILE: examples/postgres/chat/Cargo.toml ================================================ [package] name = "sqlx-example-postgres-chat" version = "0.1.0" edition = "2021" workspace = "../../../" [dependencies] sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] } tokio = { version = "1.20.0", features = [ "rt-multi-thread", "macros" ] } ratatui = "0.27.0" crossterm = "0.27.0" unicode-width = "0.1" ================================================ FILE: examples/postgres/chat/README.md ================================================ # Chat Example Note: this example has an interactive TUI which is not trivial to test automatically, so our CI currently only checks whether or not it compiles. ## Description This example demonstrates how to use PostgreSQL channels to create a very simple chat application. ## Setup 1. Declare the database URL ``` export DATABASE_URL="postgres://postgres:password@localhost/files" ``` ## Usage Run the project ``` cargo run -p sqlx-examples-postgres-chat ``` ================================================ FILE: examples/postgres/chat/src/main.rs ================================================ use crossterm::{ event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use ratatui::text::Line; use ratatui::{ backend::{Backend, CrosstermBackend}, layout::{Constraint, Direction, Layout}, style::{Color, Modifier, Style}, text::{Span, Text}, widgets::{Block, Borders, List, ListItem, Paragraph}, Frame, Terminal, }; use sqlx::postgres::PgListener; use sqlx::PgPool; use std::sync::Arc; use std::{error::Error, io}; use tokio::{sync::Mutex, time::Duration}; use unicode_width::UnicodeWidthStr; struct ChatApp { input: String, messages: Arc>>, pool: PgPool, } impl ChatApp { fn new(pool: PgPool) -> Self { ChatApp { input: String::new(), messages: Arc::new(Mutex::new(Vec::new())), pool, } } async fn run( mut self, terminal: &mut Terminal, mut listener: PgListener, ) -> Result<(), Box> { // setup listener task let messages = self.messages.clone(); tokio::spawn(async move { while let Ok(msg) = listener.recv().await { messages.lock().await.push(msg.payload().to_string()); } }); loop { let messages: Vec = self .messages .lock() .await .iter() .map(|m| { let content = vec![Line::from(Span::raw(m.to_owned()))]; ListItem::new(content) }) .collect(); terminal.draw(|f| self.ui(f, messages))?; if !event::poll(Duration::from_millis(20))? { continue; } if let Event::Key(key) = event::read()? { match key.code { KeyCode::Enter => { notify(&self.pool, &self.input).await?; self.input.clear(); } KeyCode::Char(c) => { self.input.push(c); } KeyCode::Backspace => { self.input.pop(); } KeyCode::Esc => { return Ok(()); } _ => {} } } } } fn ui(&mut self, frame: &mut Frame, messages: Vec) { let chunks = Layout::default() .direction(Direction::Vertical) .margin(2) .constraints( [ Constraint::Length(1), Constraint::Length(3), Constraint::Min(1), ] .as_ref(), ) .split(frame.size()); let text = Text::from(Line::from(vec![ Span::raw("Press "), Span::styled("Enter", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to send the message, "), Span::styled("Esc", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to quit"), ])); let help_message = Paragraph::new(text); frame.render_widget(help_message, chunks[0]); let input = Paragraph::new(self.input.as_str()) .style(Style::default().fg(Color::Yellow)) .block(Block::default().borders(Borders::ALL).title("Input")); frame.render_widget(input, chunks[1]); frame.set_cursor( // Put cursor past the end of the input text chunks[1].x + self.input.width() as u16 + 1, // Move one line down, from the border to the input line chunks[1].y + 1, ); let messages = List::new(messages).block(Block::default().borders(Borders::ALL).title("Messages")); frame.render_widget(messages, chunks[2]); } } #[tokio::main] async fn main() -> Result<(), Box> { // setup postgres let conn_url = std::env::var("DATABASE_URL").expect("Env var DATABASE_URL is required for this example."); let pool = PgPool::connect(&conn_url).await?; let mut listener = PgListener::connect(&conn_url).await?; listener.listen("chan0").await?; // setup terminal enable_raw_mode()?; let mut stdout = io::stdout(); execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; // create app and run it let app = ChatApp::new(pool); let res = app.run(&mut terminal, listener).await; // restore terminal disable_raw_mode()?; execute!( terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture, )?; terminal.show_cursor()?; if let Err(err) = res { println!("{err:?}") } Ok(()) } async fn notify(pool: &PgPool, s: &str) -> Result<(), sqlx::Error> { sqlx::query( r#" SELECT pg_notify(chan, payload) FROM (VALUES ('chan0', $1)) v(chan, payload) "#, ) .bind(s) .execute(pool) .await?; Ok(()) } ================================================ FILE: examples/postgres/files/Cargo.toml ================================================ [package] name = "sqlx-example-postgres-files" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] anyhow = "1.0" sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] } tokio = { version = "1.20.0", features = ["rt", "macros"]} dotenvy = "0.15.0" ================================================ FILE: examples/postgres/files/README.md ================================================ # Query files Example ## Description This example demonstrates storing external files to use for querying data. Encapsulating your SQL queries can be helpful in several ways, assisting with intellisense, etc. ## Setup 1. Declare the database URL ``` export DATABASE_URL="postgres://postgres:password@localhost/files" ``` 2. Create the database. ``` $ sqlx db create ``` 3. Run sql migrations ``` $ sqlx migrate run ``` ## Usage Run the project ``` cargo run files ``` ================================================ FILE: examples/postgres/files/migrations/20220712221654_files.sql ================================================ CREATE TABLE IF NOT EXISTS users ( id BIGSERIAL PRIMARY KEY, username TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS posts ( id BIGSERIAL PRIMARY KEY, title TEXT NOT NULL, body TEXT NOT NULL, user_id BIGINT NOT NULL REFERENCES users (id) ON DELETE CASCADE ); ================================================ FILE: examples/postgres/files/queries/insert_seed_data.sql ================================================ -- seed some data to work with WITH inserted_users_cte AS ( INSERT INTO users (username) VALUES ('user1'), ('user2') RETURNING id as "user_id" ) INSERT INTO posts (title, body, user_id) VALUES ('user1 post1 title', 'user1 post1 body', (SELECT user_id FROM inserted_users_cte FETCH FIRST ROW ONLY)), ('user1 post2 title', 'user1 post2 body', (SELECT user_id FROM inserted_users_cte FETCH FIRST ROW ONLY)), ('user2 post1 title', 'user2 post2 body', (SELECT user_id FROM inserted_users_cte OFFSET 1 LIMIT 1)); ================================================ FILE: examples/postgres/files/queries/list_all_posts.sql ================================================ SELECT p.id as "post_id", p.title, p.body, u.id as "author_id", u.username as "author_username" FROM users u JOIN posts p on u.id = p.user_id; ================================================ FILE: examples/postgres/files/src/main.rs ================================================ use sqlx::{query_file, query_file_as, FromRow, PgPool}; use std::fmt::{Display, Formatter}; #[derive(FromRow)] struct PostWithAuthorQuery { pub post_id: i64, pub title: String, pub body: String, pub author_id: i64, pub author_username: String, } impl Display for PostWithAuthorQuery { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, r#" post_id: {}, title: {}, body: {}, author_id: {}, author_username: {} "#, self.post_id, self.title, self.body, self.author_id, self.author_username ) } } #[tokio::main(flavor = "current_thread")] async fn main() -> anyhow::Result<()> { let pool = PgPool::connect(&dotenvy::var("DATABASE_URL")?).await?; // we can use a traditional wrapper around the `query!()` macro using files query_file!("queries/insert_seed_data.sql") .execute(&pool) .await?; // we can also use `query_file_as!()` similarly to `query_as!()` to map our database models let posts_with_authors = query_file_as!(PostWithAuthorQuery, "queries/list_all_posts.sql") .fetch_all(&pool) .await?; for post_with_author in posts_with_authors { println!("{post_with_author}"); } Ok(()) } ================================================ FILE: examples/postgres/json/Cargo.toml ================================================ [package] name = "sqlx-example-postgres-json" version = "0.1.0" edition = "2021" workspace = "../../../" [dependencies] anyhow = "1.0" dotenvy = "0.15.0" serde = { version = "1", features = ["derive"] } serde_json = "1" sqlx = { path = "../../../", features = [ "runtime-tokio", "postgres", "json" ] } clap = { version = "4", features = ["derive"] } tokio = { version = "1.20.0", features = ["rt", "macros"]} ================================================ FILE: examples/postgres/json/README.md ================================================ # JSON Example ## Setup 1. Declare the database URL ``` export DATABASE_URL="postgres://postgres:password@localhost/json" ``` 2. Create the database. ``` $ sqlx db create ``` 3. Run sql migrations ``` $ sqlx migrate run ``` ## Usage Add a person ``` echo '{ "name": "John Doe", "age": 30 }' | cargo run -- add ``` or with extra keys ``` echo '{ "name": "Jane Doe", "age": 25, "array": ["string", true, 0] }' | cargo run -- add ``` List all people ``` cargo run ``` ================================================ FILE: examples/postgres/json/migrations/20200824190010_json.sql ================================================ CREATE TABLE IF NOT EXISTS people ( id BIGSERIAL PRIMARY KEY, person JSONB NOT NULL ); ================================================ FILE: examples/postgres/json/src/main.rs ================================================ use clap::{Parser, Subcommand}; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; use sqlx::postgres::PgPool; use sqlx::types::Json; use std::io::{self, Read}; use std::num::NonZeroU8; #[derive(Parser)] struct Args { #[clap(subcommand)] cmd: Option, } #[derive(Subcommand)] enum Command { Add, } #[derive(Deserialize, Serialize)] struct Person { name: String, age: NonZeroU8, #[serde(flatten)] extra: Map, } struct Row { id: i64, person: Json, } #[tokio::main(flavor = "current_thread")] async fn main() -> anyhow::Result<()> { let args = Args::parse(); let pool = PgPool::connect(&dotenvy::var("DATABASE_URL")?).await?; match args.cmd { Some(Command::Add) => { let mut json = String::new(); io::stdin().read_to_string(&mut json)?; let person: Person = serde_json::from_str(&json)?; println!( "Adding new person: {}", &serde_json::to_string_pretty(&person)? ); let person_id = add_person(&pool, person).await?; println!("Added new person with ID {person_id}"); } None => { println!("Printing all people"); list_people(&pool).await?; } } Ok(()) } async fn add_person(pool: &PgPool, person: Person) -> anyhow::Result { let rec = sqlx::query!( r#" INSERT INTO people ( person ) VALUES ( $1 ) RETURNING id "#, Json(person) as _ ) .fetch_one(pool) .await?; Ok(rec.id) } async fn list_people(pool: &PgPool) -> anyhow::Result<()> { let rows = sqlx::query_as!( Row, r#" SELECT id, person as "person: Json" FROM people ORDER BY id "# ) .fetch_all(pool) .await?; for row in rows { println!( "{}: {}", row.id, &serde_json::to_string_pretty(&row.person)? ); } Ok(()) } ================================================ FILE: examples/postgres/listen/Cargo.toml ================================================ [package] name = "sqlx-example-postgres-listen" version = "0.1.0" edition = "2021" workspace = "../../../" [dependencies] sqlx = { path = "../../../", features = [ "runtime-tokio", "postgres" ] } futures-util = { version = "0.3.1", default-features = false } tokio = { version = "1.20.0", features = ["rt-multi-thread", "macros", "time"]} ================================================ FILE: examples/postgres/listen/README.md ================================================ Postgres LISTEN/NOTIFY ====================== ## Usage Declare the database URL. This example does not include any reading or writing of data. ``` export DATABASE_URL="postgres://postgres@localhost/postgres" ``` Run. ``` cargo run ``` 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. ================================================ FILE: examples/postgres/listen/src/main.rs ================================================ use futures_util::TryStreamExt; use sqlx::postgres::PgListener; use sqlx::{Executor, PgPool}; use std::pin::pin; use std::sync::atomic::{AtomicI64, Ordering}; use std::time::Duration; /// How long to sit in the listen loop before exiting. /// /// This ensures the example eventually exits, which is required for automated testing. const LISTEN_DURATION: Duration = Duration::from_secs(5); #[tokio::main] async fn main() -> Result<(), Box> { println!("Building PG pool."); let conn_str = std::env::var("DATABASE_URL").expect("Env var DATABASE_URL is required for this example."); let pool = sqlx::PgPool::connect(&conn_str).await?; let mut listener = PgListener::connect_with(&pool).await?; let notify_pool = pool.clone(); let _t = tokio::spawn(async move { let mut interval = tokio::time::interval(Duration::from_secs(2)); while !notify_pool.is_closed() { interval.tick().await; notify(¬ify_pool).await; } }); println!("Starting LISTEN loop."); listener.listen_all(vec!["chan0", "chan1", "chan2"]).await?; let mut counter = 0usize; loop { let notification = listener.recv().await?; println!("[from recv]: {notification:?}"); counter += 1; if counter >= 3 { break; } } // Prove that we are buffering messages by waiting for 6 seconds listener.execute("SELECT pg_sleep(6)").await?; let mut stream = listener.into_stream(); // `Sleep` must be pinned let mut timeout = pin!(tokio::time::sleep(LISTEN_DURATION)); loop { tokio::select! { res = stream.try_next() => { if let Some(notification) = res? { println!("[from stream]: {notification:?}"); } else { break; } }, _ = timeout.as_mut() => { // Don't run forever break; } } } // The stream is holding one connection. It needs to be dropped to allow the connection to // return to the pool, otherwise `pool.close()` would never return. drop(stream); pool.close().await; Ok(()) } async fn notify(pool: &PgPool) { static COUNTER: AtomicI64 = AtomicI64::new(0); // There's two ways you can invoke `NOTIFY`: // // 1: `NOTIFY , ''` which cannot take bind parameters and // is an identifier which is lowercased unless double-quoted // // 2: `SELECT pg_notify('', '')` which can take bind parameters // and preserves its case // // We recommend #2 for consistency and usability. // language=PostgreSQL let res = sqlx::query( r#" -- this emits '{ "payload": N }' as the actual payload select pg_notify(chan, json_build_object('payload', payload)::text) from ( values ('chan0', $1), ('chan1', $2), ('chan2', $3) ) notifies(chan, payload) "#, ) .bind(COUNTER.fetch_add(1, Ordering::SeqCst)) .bind(COUNTER.fetch_add(1, Ordering::SeqCst)) .bind(COUNTER.fetch_add(1, Ordering::SeqCst)) .execute(pool) .await; println!("[from notify]: {res:?}"); } ================================================ FILE: examples/postgres/mockable-todos/Cargo.toml ================================================ [package] name = "sqlx-example-postgres-mockable-todos" version = "0.1.0" edition = "2021" workspace = "../../../" [dependencies] anyhow = "1.0" sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] } clap = { version = "4", features = ["derive"] } tokio = { version = "1.20.0", features = ["rt", "macros"]} dotenvy = "0.15.0" async-trait = "0.1.41" mockall = "0.11" ================================================ FILE: examples/postgres/mockable-todos/README.md ================================================ # Mockable TODOs Example ## Description 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. ## Setup 1. Run `docker-compose up -d` to run Postgres in the background. 2. Declare the database URL, either by exporting it: ``` export DATABASE_URL="postgres://postgres:password@localhost/todos" ``` or by making a `.env` file: ``` cp .env.example .env ``` 3. Create the database. ``` $ sqlx db create ``` 4. Run sql migrations ``` $ sqlx migrate run ``` ## Usage Add a todo ``` cargo run -- add "todo description" ``` Complete a todo. ``` cargo run -- done ``` List all todos ``` cargo run ``` ## Cleanup To destroy the Postgres database, run: ``` docker-compose down --volumes ``` ================================================ FILE: examples/postgres/mockable-todos/docker-compose.yml ================================================ version: "3" services: database: image: "postgres" ports: - "5432:5432" restart: always environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=password - POSTGRES_DB=todos volumes: - database_data:/var/lib/postgresql/data volumes: database_data: driver: local ================================================ FILE: examples/postgres/mockable-todos/migrations/20200718111257_todos.sql ================================================ CREATE TABLE IF NOT EXISTS todos ( id BIGSERIAL PRIMARY KEY, description TEXT NOT NULL, done BOOLEAN NOT NULL DEFAULT FALSE ); ================================================ FILE: examples/postgres/mockable-todos/src/main.rs ================================================ use async_trait::async_trait; use clap::{Parser, Subcommand}; use sqlx::postgres::PgPool; use std::{env, io::Write, sync::Arc}; #[derive(Parser)] struct Args { #[command(subcommand)] cmd: Option, } #[derive(Subcommand)] enum Command { Add { description: String }, Done { id: i64 }, } #[tokio::main(flavor = "current_thread")] async fn main() -> anyhow::Result<()> { dotenvy::dotenv().ok(); let args = Args::parse(); let pool = PgPool::connect(&env::var("DATABASE_URL")?).await?; let todo_repo = PostgresTodoRepo::new(pool); let mut writer = std::io::stdout(); handle_command(args, todo_repo, &mut writer).await } async fn handle_command( args: Args, todo_repo: impl TodoRepo, writer: &mut impl Write, ) -> anyhow::Result<()> { match args.cmd { Some(Command::Add { description }) => { writeln!( writer, "Adding new todo with description '{}'", &description )?; let todo_id = todo_repo.add_todo(description).await?; writeln!(writer, "Added new todo with id {todo_id}")?; } Some(Command::Done { id }) => { writeln!(writer, "Marking todo {id} as done")?; if todo_repo.complete_todo(id).await? { writeln!(writer, "Todo {id} is marked as done")?; } else { writeln!(writer, "Invalid id {id}")?; } } None => { writeln!(writer, "Printing list of all todos")?; todo_repo.list_todos().await?; } } Ok(()) } #[mockall::automock] #[async_trait] pub trait TodoRepo { async fn add_todo(&self, description: String) -> anyhow::Result; async fn complete_todo(&self, id: i64) -> anyhow::Result; async fn list_todos(&self) -> anyhow::Result<()>; } struct PostgresTodoRepo { pg_pool: Arc, } impl PostgresTodoRepo { fn new(pg_pool: PgPool) -> Self { Self { pg_pool: Arc::new(pg_pool), } } } #[async_trait] impl TodoRepo for PostgresTodoRepo { async fn add_todo(&self, description: String) -> anyhow::Result { let rec = sqlx::query!( r#" INSERT INTO todos ( description ) VALUES ( $1 ) RETURNING id "#, description ) .fetch_one(&*self.pg_pool) .await?; Ok(rec.id) } async fn complete_todo(&self, id: i64) -> anyhow::Result { let rows_affected = sqlx::query!( r#" UPDATE todos SET done = TRUE WHERE id = $1 "#, id ) .execute(&*self.pg_pool) .await? .rows_affected(); Ok(rows_affected > 0) } async fn list_todos(&self) -> anyhow::Result<()> { let recs = sqlx::query!( r#" SELECT id, description, done FROM todos ORDER BY id "# ) .fetch_all(&*self.pg_pool) .await?; for rec in recs { println!( "- [{}] {}: {}", if rec.done { "x" } else { " " }, rec.id, &rec.description, ); } Ok(()) } } #[cfg(test)] mod tests { use super::*; use mockall::predicate::*; #[async_std::test] async fn test_mocked_add() { let description = String::from("My todo"); let args = Args { cmd: Some(Command::Add { description: description.clone(), }), }; let mut todo_repo = MockTodoRepo::new(); todo_repo .expect_add_todo() .times(1) .with(eq(description)) .returning(|_| Ok(1)); let mut writer = Vec::new(); handle_command(args, todo_repo, &mut writer).await.unwrap(); assert_eq!( String::from_utf8_lossy(&writer), "Adding new todo with description \'My todo\'\nAdded new todo with id 1\n" ); } } ================================================ FILE: examples/postgres/multi-database/Cargo.toml ================================================ [package] name = "sqlx-example-postgres-multi-database" version.workspace = true license.workspace = true edition.workspace = true repository.workspace = true keywords.workspace = true categories.workspace = true authors.workspace = true [dependencies] tokio = { version = "1", features = ["rt-multi-thread", "macros"] } color-eyre = "0.6.3" dotenvy = "0.15.7" tracing-subscriber = "0.3.19" rust_decimal = "1.36.0" rand = "0.8.5" [dependencies.sqlx] # version = "0.9.0" workspace = true features = ["runtime-tokio", "postgres", "migrate", "sqlx-toml"] [dependencies.accounts] path = "accounts" package = "sqlx-example-postgres-multi-database-accounts" [dependencies.payments] path = "payments" package = "sqlx-example-postgres-multi-database-payments" [lints] workspace = true ================================================ FILE: examples/postgres/multi-database/README.md ================================================ # Using Multiple Databases with `sqlx.toml` This example project involves three crates, each owning a different schema in one database, with their own set of migrations. * The main crate, a simple binary simulating the action of a REST API. * Owns the `public` schema (tables are referenced unqualified). * Migrations are moved to `src/migrations` using config key `migrate.migrations-dir` to visually separate them from the subcrate folders. * `accounts`: a subcrate simulating a reusable account-management crate. * Owns schema `accounts`. * `payments`: a subcrate simulating a wrapper for a payments API. * Owns schema `payments`. ## Note: Schema-Qualified Names This example uses schema-qualified names everywhere for clarity. It can be tempting to change the `search_path` of the connection (MySQL, Postgres) to eliminate the need for schema prefixes, but this can cause some really confusing issues when names conflict. This example will generate a `_sqlx_migrations` table in three different schemas; if `search_path` is set to `public,accounts,payments` and the migrator for the main application attempts to reference the table unqualified, it would throw an error. # Setup This example requires running three different sets of migrations. Ensure `sqlx-cli` is installed with Postgres and `sqlx.toml` support: ``` cargo install sqlx-cli --features postgres,sqlx-toml ``` Start a Postgres server (shown here using Docker, `run` command also works with `podman`): ``` docker run -d -e POSTGRES_PASSWORD=password -p 5432:5432 --name postgres postgres:latest ``` Create `.env` with the various database URLs or set them in your shell environment; ``` DATABASE_URL=postgres://postgres:password@localhost/example-multi-database ACCOUNTS_DATABASE_URL=postgres://postgres:password@localhost/example-multi-database-accounts PAYMENTS_DATABASE_URL=postgres://postgres:password@localhost/example-multi-database-payments ``` Run the following commands: ``` (cd accounts && sqlx db setup) (cd payments && sqlx db setup) sqlx db setup ``` It is an open question how to make this more convenient; `sqlx-cli` could gain a `--recursive` flag that checks subdirectories for `sqlx.toml` files, but that would only work for crates within the same workspace. If the `accounts` and `payments` crates were instead crates.io dependencies, we would need Cargo's help to resolve that information. An issue has been opened for discussion: ================================================ FILE: examples/postgres/multi-database/accounts/Cargo.toml ================================================ [package] name = "sqlx-example-postgres-multi-database-accounts" version = "0.1.0" edition = "2021" [dependencies] sqlx = { workspace = true, features = ["postgres", "time", "uuid", "macros", "sqlx-toml"] } tokio = { version = "1", features = ["rt", "sync"] } argon2 = { version = "0.5.3", features = ["password-hash"] } password-hash = { version = "0.5", features = ["std"] } uuid = { version = "1", features = ["serde"] } thiserror = "1" rand = "0.8" time = { version = "0.3.37", features = ["serde"] } serde = { version = "1.0.218", features = ["derive"] } [dev-dependencies] sqlx = { workspace = true, features = ["runtime-tokio"] } ================================================ FILE: examples/postgres/multi-database/accounts/migrations/01_setup.sql ================================================ -- We try to ensure every table has `created_at` and `updated_at` columns, which can help immensely with debugging -- and auditing. -- -- While `created_at` can just be `default now()`, setting `updated_at` on update requires a trigger which -- is a lot of boilerplate. These two functions save us from writing that every time as instead we can just do -- -- select trigger_updated_at(''); -- -- after a `CREATE TABLE`. create or replace function set_updated_at() returns trigger as $$ begin NEW.updated_at = now(); return NEW; end; $$ language plpgsql; create or replace function trigger_updated_at(tablename regclass) returns void as $$ begin execute format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s FOR EACH ROW WHEN (OLD is distinct from NEW) EXECUTE FUNCTION set_updated_at();', tablename); end; $$ language plpgsql; ================================================ FILE: examples/postgres/multi-database/accounts/migrations/02_account.sql ================================================ create table account ( account_id uuid primary key default gen_random_uuid(), email text unique not null, password_hash text not null, created_at timestamptz not null default now(), updated_at timestamptz ); select trigger_updated_at('account'); ================================================ FILE: examples/postgres/multi-database/accounts/migrations/03_session.sql ================================================ create table session ( session_token text primary key, -- random alphanumeric string account_id uuid not null references account (account_id), created_at timestamptz not null default now() ); ================================================ FILE: examples/postgres/multi-database/accounts/sqlx.toml ================================================ [common] database-url-var = "ACCOUNTS_DATABASE_URL" [macros.table-overrides.'account'] 'account_id' = "crate::AccountId" 'password_hash' = "sqlx::types::Text" [macros.table-overrides.'session'] 'session_token' = "crate::SessionToken" 'account_id' = "crate::AccountId" ================================================ FILE: examples/postgres/multi-database/accounts/src/lib.rs ================================================ use argon2::{password_hash, Argon2, PasswordHasher, PasswordVerifier}; use password_hash::PasswordHashString; use rand::distributions::{Alphanumeric, DistString}; use sqlx::PgPool; use std::sync::Arc; use uuid::Uuid; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use tokio::sync::Semaphore; #[derive(sqlx::Type, Copy, Clone, Debug, serde::Deserialize, serde::Serialize)] #[sqlx(transparent)] pub struct AccountId(pub Uuid); #[derive(sqlx::Type, Clone, Debug, serde::Deserialize, serde::Serialize)] #[sqlx(transparent)] pub struct SessionToken(pub String); pub struct Session { pub account_id: AccountId, pub session_token: SessionToken, } #[derive(Clone)] pub struct AccountsManager { /// To prevent confusion, each crate manages its own database connection pool. pool: PgPool, /// Controls how many blocking tasks are allowed to run concurrently for Argon2 hashing. /// /// ### Motivation /// Tokio blocking tasks are generally not designed for CPU-bound work. /// /// If no threads are idle, Tokio will automatically spawn new ones to handle /// new blocking tasks up to a very high limit--512 by default. /// /// This is because blocking tasks are expected to spend their time *blocked*, e.g. on /// blocking I/O, and thus not consume CPU resources or require a lot of context switching. /// /// This strategy is not the most efficient way to use threads for CPU-bound work, which /// should schedule work to a fixed number of threads to minimize context switching /// and memory usage (each new thread needs significant space allocated for its stack). /// /// We can work around this by using a purpose-designed thread-pool, like Rayon, /// but we still have the problem that those APIs usually are not designed to support `async`, /// so we end up needing blocking tasks anyway, or implementing our own work queue using /// channels. Rayon also does not shut down idle worker threads. /// /// `block_in_place` is not a silver bullet, either, as it simply uses `spawn_blocking` /// internally to take over from the current thread while it is executing blocking work. /// This also prevents futures from being polled concurrently in the current task. /// /// We can lower the limit for blocking threads when creating the runtime, but this risks /// starving other blocking tasks that are being created by the application or the Tokio /// runtime itself /// (which are used for `tokio::fs`, stdio, resolving of hostnames by `ToSocketAddrs`, etc.). /// /// Instead, we can just use a Semaphore to limit how many blocking tasks are spawned at once, /// emulating the behavior of a thread pool like Rayon without needing any additional crates. hashing_semaphore: Arc, } #[derive(Debug, thiserror::Error)] pub enum CreateAccountError { #[error("error creating account: email in-use")] EmailInUse, #[error("error creating account")] General( #[source] #[from] GeneralError, ), } #[derive(Debug, thiserror::Error)] pub enum CreateSessionError { #[error("unknown email")] UnknownEmail, #[error("invalid password")] InvalidPassword, #[error("authentication error")] General( #[source] #[from] GeneralError, ), } #[derive(Debug, thiserror::Error)] pub enum GeneralError { #[error("database error")] Sqlx( #[source] #[from] sqlx::Error, ), #[error("error hashing password")] PasswordHash( #[source] #[from] password_hash::Error, ), #[error("task panicked")] Task( #[source] #[from] tokio::task::JoinError, ), } impl AccountsManager { pub async fn setup( opts: PgConnectOptions, max_hashing_threads: usize, ) -> Result { // This should be configurable by the caller, but for simplicity, it's not. let pool = PgPoolOptions::new() .max_connections(5) .connect_with(opts) .await?; sqlx::migrate!() .run(&pool) .await .map_err(sqlx::Error::from)?; Ok(AccountsManager { pool, hashing_semaphore: Semaphore::new(max_hashing_threads).into(), }) } async fn hash_password(&self, password: String) -> Result { let guard = self .hashing_semaphore .clone() .acquire_owned() .await .expect("BUG: this semaphore should not be closed"); // We transfer ownership to the blocking task and back to ensure Tokio doesn't spawn // excess threads. let (_guard, res) = tokio::task::spawn_blocking(move || { let salt = password_hash::SaltString::generate(rand::thread_rng()); ( guard, Argon2::default() .hash_password(password.as_bytes(), &salt) .map(|hash| hash.serialize()), ) }) .await?; Ok(res?) } async fn verify_password( &self, password: String, hash: PasswordHashString, ) -> Result<(), CreateSessionError> { let guard = self .hashing_semaphore .clone() .acquire_owned() .await .expect("BUG: this semaphore should not be closed"); let (_guard, res) = tokio::task::spawn_blocking(move || { ( guard, Argon2::default().verify_password(password.as_bytes(), &hash.password_hash()), ) }) .await .map_err(GeneralError::from)?; if let Err(password_hash::Error::Password) = res { return Err(CreateSessionError::InvalidPassword); } res.map_err(GeneralError::from)?; Ok(()) } pub async fn create( &self, email: &str, password: String, ) -> Result { // Hash password whether the account exists or not to make it harder // to tell the difference in the timing. let hash = self.hash_password(password).await?; // Thanks to `sqlx.toml`, `account_id` maps to `AccountId` sqlx::query_scalar!( // language=PostgreSQL "insert into account(email, password_hash) \ values ($1, $2) \ returning account_id", email, hash.as_str(), ) .fetch_one(&self.pool) .await .map_err(|e| { if e.as_database_error().and_then(|dbe| dbe.constraint()) == Some("account_account_id_key") { CreateAccountError::EmailInUse } else { GeneralError::from(e).into() } }) } pub async fn create_session( &self, email: &str, password: String, ) -> Result { let mut txn = self.pool.begin().await.map_err(GeneralError::from)?; // To save a round-trip to the database, we'll speculatively insert the session token // at the same time as we're looking up the password hash. // // This does nothing until the transaction is actually committed. let session_token = SessionToken::generate(); // Thanks to `sqlx.toml`: // * `account_id` maps to `AccountId` // * `password_hash` maps to `Text` // * `session_token` maps to `SessionToken` let maybe_account = sqlx::query!( // language=PostgreSQL "with account as ( select account_id, password_hash \ from account \ where email = $1 ), session as ( insert into session(session_token, account_id) select $2, account_id from account ) select account.account_id, account.password_hash from account", email, session_token.0 ) .fetch_optional(&mut *txn) .await .map_err(GeneralError::from)?; let Some(account) = maybe_account else { // Hash the password whether the account exists or not to hide the difference in timing. self.hash_password(password) .await .map_err(GeneralError::from)?; return Err(CreateSessionError::UnknownEmail); }; self.verify_password(password, account.password_hash.into_inner()) .await?; txn.commit().await.map_err(GeneralError::from)?; Ok(Session { account_id: account.account_id, session_token, }) } pub async fn auth_session( &self, session_token: &str, ) -> Result, GeneralError> { sqlx::query_scalar!( "select account_id from session where session_token = $1", session_token ) .fetch_optional(&self.pool) .await .map_err(GeneralError::from) } } impl SessionToken { const LEN: usize = 32; fn generate() -> Self { SessionToken(Alphanumeric.sample_string(&mut rand::thread_rng(), Self::LEN)) } } ================================================ FILE: examples/postgres/multi-database/payments/Cargo.toml ================================================ [package] name = "sqlx-example-postgres-multi-database-payments" version = "0.1.0" edition = "2021" [dependencies] sqlx = { workspace = true, features = ["postgres", "time", "uuid", "rust_decimal", "sqlx-toml"] } rust_decimal = "1.36.0" time = "0.3.37" uuid = "1.12.1" [dependencies.accounts] path = "../accounts" package = "sqlx-example-postgres-multi-database-accounts" [dev-dependencies] sqlx = { workspace = true, features = ["runtime-tokio"] } ================================================ FILE: examples/postgres/multi-database/payments/migrations/01_setup.sql ================================================ -- We try to ensure every table has `created_at` and `updated_at` columns, which can help immensely with debugging -- and auditing. -- -- While `created_at` can just be `default now()`, setting `updated_at` on update requires a trigger which -- is a lot of boilerplate. These two functions save us from writing that every time as instead we can just do -- -- select trigger_updated_at('
'); -- -- after a `CREATE TABLE`. create or replace function set_updated_at() returns trigger as $$ begin NEW.updated_at = now(); return NEW; end; $$ language plpgsql; create or replace function trigger_updated_at(tablename regclass) returns void as $$ begin execute format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s FOR EACH ROW WHEN (OLD is distinct from NEW) EXECUTE FUNCTION set_updated_at();', tablename); end; $$ language plpgsql; ================================================ FILE: examples/postgres/multi-database/payments/migrations/02_payment.sql ================================================ -- `payments::PaymentStatus` -- -- Historically at LaunchBadge we preferred not to define enums on the database side because it can be annoying -- and error-prone to keep them in-sync with the application. -- Instead, we let the application define the enum and just have the database store a compact representation of it. -- This is mostly a matter of taste, however. -- -- For the purposes of this example, we're using an in-database enum because this is a common use-case -- for needing type overrides. create type payment_status as enum ( 'pending', 'created', 'success', 'failed' ); create table payment ( payment_id uuid primary key default gen_random_uuid(), -- Since `account` is in a separate database, we can't foreign-key to it. account_id uuid not null, status payment_status not null, -- ISO 4217 currency code (https://en.wikipedia.org/wiki/ISO_4217#List_of_ISO_4217_currency_codes) -- -- This *could* be an ENUM of currency codes, but constraining this to a set of known values in the database -- would be annoying to keep up to date as support for more currencies is added. -- -- Consider also if support for cryptocurrencies is desired; those are not covered by ISO 4217. -- -- Though ISO 4217 is a three-character code, `TEXT`, `VARCHAR` and `CHAR(N)` -- all use the same storage format in Postgres. Any constraint against the length of this field -- would purely be a sanity check. currency text not null, -- There's an endless debate about what type should be used to represent currency amounts. -- -- Postgres has the `MONEY` type, but the fractional precision depends on a C locale setting and the type is mostly -- optimized for storing USD, or other currencies with a minimum fraction of 1 cent. -- -- NEVER use `FLOAT` or `DOUBLE`. IEEE-754 rounding point has round-off and precision errors that make it wholly -- unsuitable for representing real money amounts. -- -- `NUMERIC`, being an arbitrary-precision decimal format, is a safe default choice that can support any currency, -- and so is what we've chosen here. amount NUMERIC not null, -- Payments almost always take place through a third-party vendor (e.g. PayPal, Stripe, etc.), -- so imagine this is an identifier string for this payment in such a vendor's systems. -- -- For privacy and security reasons, payment and personally-identifying information -- (e.g. credit card numbers, bank account numbers, billing addresses) should only be stored with the vendor -- unless there is a good reason otherwise. external_payment_id text, created_at timestamptz not null default now(), updated_at timestamptz ); select trigger_updated_at('payment'); ================================================ FILE: examples/postgres/multi-database/payments/sqlx.toml ================================================ [common] database-url-var = "PAYMENTS_DATABASE_URL" [macros.table-overrides.'payment'] 'payment_id' = "crate::PaymentId" 'account_id' = "accounts::AccountId" [macros.type-overrides] 'payment_status' = "crate::PaymentStatus" ================================================ FILE: examples/postgres/multi-database/payments/src/lib.rs ================================================ use accounts::{AccountId, AccountsManager}; use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; use sqlx::{Acquire, PgConnection, PgPool, Postgres}; use time::OffsetDateTime; use uuid::Uuid; #[derive(sqlx::Type, Copy, Clone, Debug)] #[sqlx(transparent)] pub struct PaymentId(pub Uuid); #[derive(sqlx::Type, Copy, Clone, Debug)] #[sqlx(type_name = "payment_status")] #[sqlx(rename_all = "snake_case")] pub enum PaymentStatus { Pending, Created, Success, Failed, } // Users often assume that they need `#[derive(FromRow)]` to use `query_as!()`, // then are surprised when the derive's control attributes have no effect. // The macros currently do *not* use the `FromRow` trait at all. // Support for `FromRow` is planned, but would require significant changes to the macros. // See https://github.com/launchbadge/sqlx/issues/514 for details. #[derive(Clone, Debug)] pub struct Payment { pub payment_id: PaymentId, pub account_id: AccountId, pub status: PaymentStatus, pub currency: String, // `rust_decimal::Decimal` has more than enough precision for any real-world amount of money. pub amount: rust_decimal::Decimal, pub external_payment_id: Option, pub created_at: OffsetDateTime, pub updated_at: Option, } pub struct PaymentsManager { pool: PgPool, } impl PaymentsManager { pub async fn setup(opts: PgConnectOptions) -> sqlx::Result { let pool = PgPoolOptions::new() .max_connections(5) .connect_with(opts) .await?; sqlx::migrate!().run(&pool).await?; Ok(Self { pool }) } /// # Note /// For simplicity, this does not ensure that `account_id` actually exists. pub async fn create( &self, account_id: AccountId, currency: &str, amount: rust_decimal::Decimal, ) -> sqlx::Result { // Check-out a connection to avoid paying the overhead of acquiring one for each call. let mut conn = self.pool.acquire().await?; // Imagine this method does more than just create a record in the database; // maybe it actually initiates the payment with a third-party vendor, like Stripe. // // We need to ensure that we can link the payment in the vendor's systems back to a record // in ours, even if any of the following happens: // * The application dies before storing the external payment ID in the database // * We lose the connection to the database while trying to commit a transaction // * The database server dies while committing the transaction // // Thus, we create the payment in three atomic phases: // * We create the payment record in our system and commit it. // * We create the payment in the vendor's system with our payment ID attached. // * We update our payment record with the vendor's payment ID. let payment_id = sqlx::query_scalar!( "insert into payment(account_id, status, currency, amount) \ values ($1, $2, $3, $4) \ returning payment_id", // The database doesn't give us enough information to correctly typecheck `AccountId` here. // We have to insert the UUID directly. account_id.0, PaymentStatus::Pending, currency, amount, ) .fetch_one(&mut *conn) .await?; // We then create the record with the payment vendor... let external_payment_id = "foobar1234"; // Then we store the external payment ID and update the payment status. // // NOTE: use caution with `select *` or `returning *`; // the order of columns gets baked into the binary, so if it changes between compile time and // run-time, you may run into errors. let payment = sqlx::query_as!( Payment, "update payment \ set status = $1, external_payment_id = $2 \ where payment_id = $3 \ returning *", PaymentStatus::Created, external_payment_id, payment_id.0, ) .fetch_one(&mut *conn) .await?; Ok(payment) } pub async fn get(&self, payment_id: PaymentId) -> sqlx::Result> { sqlx::query_as!( Payment, // see note above about `select *` "select * from payment where payment_id = $1", payment_id.0 ) .fetch_optional(&self.pool) .await } } ================================================ FILE: examples/postgres/multi-database/sqlx.toml ================================================ [migrate] # Move `migrations/` to under `src/` to separate it from subcrates. migrations-dir = "src/migrations" ================================================ FILE: examples/postgres/multi-database/src/main.rs ================================================ use accounts::AccountsManager; use color_eyre::eyre; use color_eyre::eyre::{Context, OptionExt}; use payments::PaymentsManager; use rand::distributions::{Alphanumeric, DistString}; use sqlx::Connection; #[tokio::main] async fn main() -> eyre::Result<()> { color_eyre::install()?; let _ = dotenvy::dotenv(); tracing_subscriber::fmt::init(); let mut conn = sqlx::PgConnection::connect( // `env::var()` doesn't include the variable name in the error. &dotenvy::var("DATABASE_URL").wrap_err("DATABASE_URL must be set")?, ) .await .wrap_err("could not connect to database")?; let accounts = AccountsManager::setup( dotenvy::var("ACCOUNTS_DATABASE_URL") .wrap_err("ACCOUNTS_DATABASE_URL must be set")? .parse() .wrap_err("error parsing ACCOUNTS_DATABASE_URL")?, 1, ) .await .wrap_err("error initializing AccountsManager")?; let payments = PaymentsManager::setup( dotenvy::var("PAYMENTS_DATABASE_URL") .wrap_err("PAYMENTS_DATABASE_URL must be set")? .parse() .wrap_err("error parsing PAYMENTS_DATABASE_URL")?, ) .await .wrap_err("error initializing PaymentsManager")?; // For simplicity's sake, imagine each of these might be invoked by different request routes // in a web application. // POST /account let user_email = format!("user{}@example.com", rand::random::()); let user_password = Alphanumeric.sample_string(&mut rand::thread_rng(), 16); // Requires an externally managed transaction in case any application-specific records // should be created after the actual account record. let mut txn = conn.begin().await?; let account_id = accounts // Takes ownership of the password string because it's sent to another thread for hashing. .create(&user_email, user_password.clone()) .await .wrap_err("error creating account")?; txn.commit().await?; println!( "created account ID: {}, email: {user_email:?}, password: {user_password:?}", account_id.0 ); // POST /session // Log the user in. let session = accounts .create_session(&user_email, user_password.clone()) .await .wrap_err("error creating session")?; // After this, session.session_token should then be returned to the client, // either in the response body or a `Set-Cookie` header. println!("created session token: {}", session.session_token.0); // POST /purchase // The client would then pass the session token to authenticated routes. // In this route, they're making some kind of purchase. // First, we need to ensure the session is valid. // `session.session_token` would be passed by the client in whatever way is appropriate. // // For a pure REST API, consider an `Authorization: Bearer` header instead of the request body. // With Axum, you can create a reusable extractor that reads the header and validates the session // by implementing `FromRequestParts`. // // For APIs where the browser is intended to be the primary client, using a session cookie // may be easier for the frontend. By setting the cookie with `HttpOnly: true`, // it's impossible for malicious Javascript on the client to access and steal the session token. let account_id = accounts .auth_session(&session.session_token.0) .await .wrap_err("error authenticating session")? .ok_or_eyre("session does not exist")?; let purchase_amount: rust_decimal::Decimal = "12.34".parse().unwrap(); // Then, because the user is making a purchase, we record a payment. let payment = payments .create(account_id, "USD", purchase_amount) .await .wrap_err("error creating payment")?; println!("created payment: {payment:?}"); let purchase_id = sqlx::query_scalar!( "insert into purchase(account_id, payment_id, amount) values ($1, $2, $3) returning purchase_id", account_id.0, payment.payment_id.0, purchase_amount ) .fetch_one(&mut conn) .await .wrap_err("error creating purchase")?; println!("created purchase: {purchase_id}"); conn.close().await?; Ok(()) } ================================================ FILE: examples/postgres/multi-database/src/migrations/01_setup.sql ================================================ -- We try to ensure every table has `created_at` and `updated_at` columns, which can help immensely with debugging -- and auditing. -- -- While `created_at` can just be `default now()`, setting `updated_at` on update requires a trigger which -- is a lot of boilerplate. These two functions save us from writing that every time as instead we can just do -- -- select trigger_updated_at('
'); -- -- after a `CREATE TABLE`. create or replace function set_updated_at() returns trigger as $$ begin NEW.updated_at = now(); return NEW; end; $$ language plpgsql; create or replace function trigger_updated_at(tablename regclass) returns void as $$ begin execute format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s FOR EACH ROW WHEN (OLD is distinct from NEW) EXECUTE FUNCTION set_updated_at();', tablename); end; $$ language plpgsql; ================================================ FILE: examples/postgres/multi-database/src/migrations/02_purchase.sql ================================================ create table purchase ( purchase_id uuid primary key default gen_random_uuid(), account_id uuid not null, payment_id uuid not null, amount numeric not null, created_at timestamptz not null default now(), updated_at timestamptz ); select trigger_updated_at('purchase'); ================================================ FILE: examples/postgres/multi-tenant/Cargo.toml ================================================ [package] name = "sqlx-example-postgres-multi-tenant" version.workspace = true license.workspace = true edition.workspace = true repository.workspace = true keywords.workspace = true categories.workspace = true authors.workspace = true [dependencies] tokio = { version = "1", features = ["rt-multi-thread", "macros"] } color-eyre = "0.6.3" dotenvy = "0.15.7" tracing-subscriber = "0.3.19" rust_decimal = "1.36.0" rand = "0.8.5" [dependencies.sqlx] # version = "0.9.0" workspace = true features = ["runtime-tokio", "postgres", "migrate", "sqlx-toml"] [dependencies.accounts] path = "accounts" package = "sqlx-example-postgres-multi-tenant-accounts" [dependencies.payments] path = "payments" package = "sqlx-example-postgres-multi-tenant-payments" [lints] workspace = true ================================================ FILE: examples/postgres/multi-tenant/README.md ================================================ # Multi-tenant Databases with `sqlx.toml` This example project involves three crates, each owning a different schema in one database, with their own set of migrations. * The main crate, a simple binary simulating the action of a REST API. * Owns the `public` schema (tables are referenced unqualified). * Migrations are moved to `src/migrations` using config key `migrate.migrations-dir` to visually separate them from the subcrate folders. * `accounts`: a subcrate simulating a reusable account-management crate. * Owns schema `accounts`. * `payments`: a subcrate simulating a wrapper for a payments API. * Owns schema `payments`. ## Note: Schema-Qualified Names This example uses schema-qualified names everywhere for clarity. It can be tempting to change the `search_path` of the connection (MySQL, Postgres) to eliminate the need for schema prefixes, but this can cause some really confusing issues when names conflict. This example will generate a `_sqlx_migrations` table in three different schemas; if `search_path` is set to `public,accounts,payments` and the migrator for the main application attempts to reference the table unqualified, it would throw an error. # Setup This example requires running three different sets of migrations. Ensure `sqlx-cli` is installed with Postgres and `sqlx.toml` support: ``` cargo install sqlx-cli --features postgres,sqlx-toml ``` Start a Postgres server (shown here using Docker, `run` command also works with `podman`): ``` docker run -d -e POSTGRES_PASSWORD=password -p 5432:5432 --name postgres postgres:latest ``` Create `.env` with `DATABASE_URL` or set the variable in your shell environment; ``` DATABASE_URL=postgres://postgres:password@localhost/example-multi-tenant ``` Run the following commands: ``` (cd accounts && sqlx db setup) (cd payments && sqlx migrate run) sqlx migrate run ``` It is an open question how to make this more convenient; `sqlx-cli` could gain a `--recursive` flag that checks subdirectories for `sqlx.toml` files, but that would only work for crates within the same workspace. If the `accounts` and `payments` crates were instead crates.io dependencies, we would need Cargo's help to resolve that information. An issue has been opened for discussion: ================================================ FILE: examples/postgres/multi-tenant/accounts/Cargo.toml ================================================ [package] name = "sqlx-example-postgres-multi-tenant-accounts" version = "0.1.0" edition = "2021" [dependencies] tokio = { version = "1", features = ["rt", "sync"] } argon2 = { version = "0.5.3", features = ["password-hash"] } password-hash = { version = "0.5", features = ["std"] } uuid = { version = "1", features = ["serde"] } thiserror = "1" rand = "0.8" time = { version = "0.3.37", features = ["serde"] } serde = { version = "1.0.218", features = ["derive"] } [dependencies.sqlx] # version = "0.9.0" workspace = true features = ["postgres", "time", "uuid", "macros", "sqlx-toml", "migrate"] [dev-dependencies] sqlx = { workspace = true, features = ["runtime-tokio"] } ================================================ FILE: examples/postgres/multi-tenant/accounts/migrations/01_setup.sql ================================================ -- We try to ensure every table has `created_at` and `updated_at` columns, which can help immensely with debugging -- and auditing. -- -- While `created_at` can just be `default now()`, setting `updated_at` on update requires a trigger which -- is a lot of boilerplate. These two functions save us from writing that every time as instead we can just do -- -- select accounts.trigger_updated_at('
'); -- -- after a `CREATE TABLE`. create or replace function accounts.set_updated_at() returns trigger as $$ begin NEW.updated_at = now(); return NEW; end; $$ language plpgsql; create or replace function accounts.trigger_updated_at(tablename regclass) returns void as $$ begin execute format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s FOR EACH ROW WHEN (OLD is distinct from NEW) EXECUTE FUNCTION accounts.set_updated_at();', tablename); end; $$ language plpgsql; ================================================ FILE: examples/postgres/multi-tenant/accounts/migrations/02_account.sql ================================================ create table accounts.account ( account_id uuid primary key default gen_random_uuid(), email text unique not null, password_hash text not null, created_at timestamptz not null default now(), updated_at timestamptz ); select accounts.trigger_updated_at('accounts.account'); ================================================ FILE: examples/postgres/multi-tenant/accounts/migrations/03_session.sql ================================================ create table accounts.session ( session_token text primary key, -- random alphanumeric string account_id uuid not null references accounts.account (account_id), created_at timestamptz not null default now() ); ================================================ FILE: examples/postgres/multi-tenant/accounts/sqlx.toml ================================================ [migrate] create-schemas = ["accounts"] table-name = "accounts._sqlx_migrations" [macros.table-overrides.'accounts.account'] 'account_id' = "crate::AccountId" 'password_hash' = "sqlx::types::Text" [macros.table-overrides.'accounts.session'] 'session_token' = "crate::SessionToken" 'account_id' = "crate::AccountId" ================================================ FILE: examples/postgres/multi-tenant/accounts/src/lib.rs ================================================ use argon2::{password_hash, Argon2, PasswordHasher, PasswordVerifier}; use password_hash::PasswordHashString; use rand::distributions::{Alphanumeric, DistString}; use sqlx::{Acquire, Executor, PgTransaction, Postgres}; use std::sync::Arc; use uuid::Uuid; use tokio::sync::Semaphore; #[derive(sqlx::Type, Copy, Clone, Debug, serde::Deserialize, serde::Serialize)] #[sqlx(transparent)] pub struct AccountId(pub Uuid); #[derive(sqlx::Type, Clone, Debug, serde::Deserialize, serde::Serialize)] #[sqlx(transparent)] pub struct SessionToken(pub String); pub struct Session { pub account_id: AccountId, pub session_token: SessionToken, } pub struct AccountsManager { /// Controls how many blocking tasks are allowed to run concurrently for Argon2 hashing. /// /// ### Motivation /// Tokio blocking tasks are generally not designed for CPU-bound work. /// /// If no threads are idle, Tokio will automatically spawn new ones to handle /// new blocking tasks up to a very high limit--512 by default. /// /// This is because blocking tasks are expected to spend their time *blocked*, e.g. on /// blocking I/O, and thus not consume CPU resources or require a lot of context switching. /// /// This strategy is not the most efficient way to use threads for CPU-bound work, which /// should schedule work to a fixed number of threads to minimize context switching /// and memory usage (each new thread needs significant space allocated for its stack). /// /// We can work around this by using a purpose-designed thread-pool, like Rayon, /// but we still have the problem that those APIs usually are not designed to support `async`, /// so we end up needing blocking tasks anyway, or implementing our own work queue using /// channels. Rayon also does not shut down idle worker threads. /// /// `block_in_place` is not a silver bullet, either, as it simply uses `spawn_blocking` /// internally to take over from the current thread while it is executing blocking work. /// This also prevents futures from being polled concurrently in the current task. /// /// We can lower the limit for blocking threads when creating the runtime, but this risks /// starving other blocking tasks that are being created by the application or the Tokio /// runtime itself /// (which are used for `tokio::fs`, stdio, resolving of hostnames by `ToSocketAddrs`, etc.). /// /// Instead, we can just use a Semaphore to limit how many blocking tasks are spawned at once, /// emulating the behavior of a thread pool like Rayon without needing any additional crates. hashing_semaphore: Arc, } #[derive(Debug, thiserror::Error)] pub enum CreateAccountError { #[error("error creating account: email in-use")] EmailInUse, #[error("error creating account")] General( #[source] #[from] GeneralError, ), } #[derive(Debug, thiserror::Error)] pub enum CreateSessionError { #[error("unknown email")] UnknownEmail, #[error("invalid password")] InvalidPassword, #[error("authentication error")] General( #[source] #[from] GeneralError, ), } #[derive(Debug, thiserror::Error)] pub enum GeneralError { #[error("database error")] Sqlx( #[source] #[from] sqlx::Error, ), #[error("error hashing password")] PasswordHash( #[source] #[from] password_hash::Error, ), #[error("task panicked")] Task( #[source] #[from] tokio::task::JoinError, ), } impl AccountsManager { pub async fn setup( pool: impl Acquire<'_, Database = Postgres>, max_hashing_threads: usize, ) -> Result { sqlx::migrate!() .run(pool) .await .map_err(sqlx::Error::from)?; Ok(AccountsManager { hashing_semaphore: Semaphore::new(max_hashing_threads).into(), }) } async fn hash_password(&self, password: String) -> Result { let guard = self .hashing_semaphore .clone() .acquire_owned() .await .expect("BUG: this semaphore should not be closed"); // We transfer ownership to the blocking task and back to ensure Tokio doesn't spawn // excess threads. let (_guard, res) = tokio::task::spawn_blocking(move || { let salt = password_hash::SaltString::generate(rand::thread_rng()); ( guard, Argon2::default() .hash_password(password.as_bytes(), &salt) .map(|hash| hash.serialize()), ) }) .await?; Ok(res?) } async fn verify_password( &self, password: String, hash: PasswordHashString, ) -> Result<(), CreateSessionError> { let guard = self .hashing_semaphore .clone() .acquire_owned() .await .expect("BUG: this semaphore should not be closed"); let (_guard, res) = tokio::task::spawn_blocking(move || { ( guard, Argon2::default().verify_password(password.as_bytes(), &hash.password_hash()), ) }) .await .map_err(GeneralError::from)?; if let Err(password_hash::Error::Password) = res { return Err(CreateSessionError::InvalidPassword); } res.map_err(GeneralError::from)?; Ok(()) } pub async fn create( &self, txn: &mut PgTransaction<'_>, email: &str, password: String, ) -> Result { // Hash password whether the account exists or not to make it harder // to tell the difference in the timing. let hash = self.hash_password(password).await?; // Thanks to `sqlx.toml`, `account_id` maps to `AccountId` sqlx::query_scalar!( // language=PostgreSQL "insert into accounts.account(email, password_hash) \ values ($1, $2) \ returning account_id", email, hash.as_str(), ) .fetch_one(&mut **txn) .await .map_err(|e| { if e.as_database_error().and_then(|dbe| dbe.constraint()) == Some("account_account_id_key") { CreateAccountError::EmailInUse } else { GeneralError::from(e).into() } }) } pub async fn create_session( &self, db: impl Acquire<'_, Database = Postgres>, email: &str, password: String, ) -> Result { let mut txn = db.begin().await.map_err(GeneralError::from)?; // To save a round-trip to the database, we'll speculatively insert the session token // at the same time as we're looking up the password hash. // // This does nothing until the transaction is actually committed. let session_token = SessionToken::generate(); // Thanks to `sqlx.toml`: // * `account_id` maps to `AccountId` // * `password_hash` maps to `Text` // * `session_token` maps to `SessionToken` let maybe_account = sqlx::query!( // language=PostgreSQL "with account as ( select account_id, password_hash \ from accounts.account \ where email = $1 ), session as ( insert into accounts.session(session_token, account_id) select $2, account_id from account ) select account.account_id, account.password_hash from account", email, session_token.0 ) .fetch_optional(&mut *txn) .await .map_err(GeneralError::from)?; let Some(account) = maybe_account else { // Hash the password whether the account exists or not to hide the difference in timing. self.hash_password(password) .await .map_err(GeneralError::from)?; return Err(CreateSessionError::UnknownEmail); }; self.verify_password(password, account.password_hash.into_inner()) .await?; txn.commit().await.map_err(GeneralError::from)?; Ok(Session { account_id: account.account_id, session_token, }) } pub async fn auth_session( &self, db: impl Executor<'_, Database = Postgres>, session_token: &str, ) -> Result, GeneralError> { sqlx::query_scalar!( "select account_id from accounts.session where session_token = $1", session_token ) .fetch_optional(db) .await .map_err(GeneralError::from) } } impl SessionToken { const LEN: usize = 32; fn generate() -> Self { SessionToken(Alphanumeric.sample_string(&mut rand::thread_rng(), Self::LEN)) } } ================================================ FILE: examples/postgres/multi-tenant/payments/Cargo.toml ================================================ [package] name = "sqlx-example-postgres-multi-tenant-payments" version = "0.1.0" edition = "2021" [dependencies] rust_decimal = "1.36.0" time = "0.3.37" uuid = "1.12.1" [dependencies.sqlx] # version = "0.9.0" workspace = true features = ["postgres", "time", "uuid", "rust_decimal", "sqlx-toml", "migrate"] [dependencies.accounts] path = "../accounts" package = "sqlx-example-postgres-multi-tenant-accounts" [dev-dependencies] sqlx = { workspace = true, features = ["runtime-tokio"] } ================================================ FILE: examples/postgres/multi-tenant/payments/migrations/01_setup.sql ================================================ -- We try to ensure every table has `created_at` and `updated_at` columns, which can help immensely with debugging -- and auditing. -- -- While `created_at` can just be `default now()`, setting `updated_at` on update requires a trigger which -- is a lot of boilerplate. These two functions save us from writing that every time as instead we can just do -- -- select payments.trigger_updated_at('
'); -- -- after a `CREATE TABLE`. create or replace function payments.set_updated_at() returns trigger as $$ begin NEW.updated_at = now(); return NEW; end; $$ language plpgsql; create or replace function payments.trigger_updated_at(tablename regclass) returns void as $$ begin execute format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s FOR EACH ROW WHEN (OLD is distinct from NEW) EXECUTE FUNCTION payments.set_updated_at();', tablename); end; $$ language plpgsql; ================================================ FILE: examples/postgres/multi-tenant/payments/migrations/02_payment.sql ================================================ -- `payments::PaymentStatus` -- -- Historically at LaunchBadge we preferred not to define enums on the database side because it can be annoying -- and error-prone to keep them in-sync with the application. -- Instead, we let the application define the enum and just have the database store a compact representation of it. -- This is mostly a matter of taste, however. -- -- For the purposes of this example, we're using an in-database enum because this is a common use-case -- for needing type overrides. create type payments.payment_status as enum ( 'pending', 'created', 'success', 'failed' ); create table payments.payment ( payment_id uuid primary key default gen_random_uuid(), -- This cross-schema reference means migrations for the `accounts` crate should be run first. account_id uuid not null references accounts.account (account_id), status payments.payment_status not null, -- ISO 4217 currency code (https://en.wikipedia.org/wiki/ISO_4217#List_of_ISO_4217_currency_codes) -- -- This *could* be an ENUM of currency codes, but constraining this to a set of known values in the database -- would be annoying to keep up to date as support for more currencies is added. -- -- Consider also if support for cryptocurrencies is desired; those are not covered by ISO 4217. -- -- Though ISO 4217 is a three-character code, `TEXT`, `VARCHAR` and `CHAR(N)` -- all use the same storage format in Postgres. Any constraint against the length of this field -- would purely be a sanity check. currency text not null, -- There's an endless debate about what type should be used to represent currency amounts. -- -- Postgres has the `MONEY` type, but the fractional precision depends on a C locale setting and the type is mostly -- optimized for storing USD, or other currencies with a minimum fraction of 1 cent. -- -- NEVER use `FLOAT` or `DOUBLE`. IEEE-754 rounding point has round-off and precision errors that make it wholly -- unsuitable for representing real money amounts. -- -- `NUMERIC`, being an arbitrary-precision decimal format, is a safe default choice that can support any currency, -- and so is what we've chosen here. amount NUMERIC not null, -- Payments almost always take place through a third-party vendor (e.g. PayPal, Stripe, etc.), -- so imagine this is an identifier string for this payment in such a vendor's systems. -- -- For privacy and security reasons, payment and personally-identifying information -- (e.g. credit card numbers, bank account numbers, billing addresses) should only be stored with the vendor -- unless there is a good reason otherwise. external_payment_id text, created_at timestamptz not null default now(), updated_at timestamptz ); select payments.trigger_updated_at('payments.payment'); ================================================ FILE: examples/postgres/multi-tenant/payments/sqlx.toml ================================================ [migrate] create-schemas = ["payments"] table-name = "payments._sqlx_migrations" [macros.table-overrides.'payments.payment'] 'payment_id' = "crate::PaymentId" 'account_id' = "accounts::AccountId" [macros.type-overrides] 'payments.payment_status' = "crate::PaymentStatus" ================================================ FILE: examples/postgres/multi-tenant/payments/src/lib.rs ================================================ use accounts::AccountId; use sqlx::{Acquire, PgConnection, Postgres}; use time::OffsetDateTime; use uuid::Uuid; #[derive(sqlx::Type, Copy, Clone, Debug)] #[sqlx(transparent)] pub struct PaymentId(pub Uuid); #[derive(sqlx::Type, Copy, Clone, Debug)] #[sqlx(type_name = "payments.payment_status")] #[sqlx(rename_all = "snake_case")] pub enum PaymentStatus { Pending, Created, Success, Failed, } // Users often assume that they need `#[derive(FromRow)]` to use `query_as!()`, // then are surprised when the derive's control attributes have no effect. // The macros currently do *not* use the `FromRow` trait at all. // Support for `FromRow` is planned, but would require significant changes to the macros. // See https://github.com/launchbadge/sqlx/issues/514 for details. #[derive(Clone, Debug)] pub struct Payment { pub payment_id: PaymentId, pub account_id: AccountId, pub status: PaymentStatus, pub currency: String, // `rust_decimal::Decimal` has more than enough precision for any real-world amount of money. pub amount: rust_decimal::Decimal, pub external_payment_id: Option, pub created_at: OffsetDateTime, pub updated_at: Option, } // Accepting `impl Acquire` allows this function to be generic over `Pool`, `Connection` and `Transaction`. pub async fn migrate(db: impl Acquire<'_, Database = Postgres>) -> sqlx::Result<()> { sqlx::migrate!().run(db).await?; Ok(()) } pub async fn create( conn: &mut PgConnection, account_id: AccountId, currency: &str, amount: rust_decimal::Decimal, ) -> sqlx::Result { // Imagine this method does more than just create a record in the database; // maybe it actually initiates the payment with a third-party vendor, like Stripe. // // We need to ensure that we can link the payment in the vendor's systems back to a record // in ours, even if any of the following happens: // * The application dies before storing the external payment ID in the database // * We lose the connection to the database while trying to commit a transaction // * The database server dies while committing the transaction // // Thus, we create the payment in three atomic phases: // * We create the payment record in our system and commit it. // * We create the payment in the vendor's system with our payment ID attached. // * We update our payment record with the vendor's payment ID. let payment_id = sqlx::query_scalar!( "insert into payments.payment(account_id, status, currency, amount) \ values ($1, $2, $3, $4) \ returning payment_id", // The database doesn't give us enough information to correctly typecheck `AccountId` here. // We have to insert the UUID directly. account_id.0, PaymentStatus::Pending, currency, amount, ) .fetch_one(&mut *conn) .await?; // We then create the record with the payment vendor... let external_payment_id = "foobar1234"; // Then we store the external payment ID and update the payment status. // // NOTE: use caution with `select *` or `returning *`; // the order of columns gets baked into the binary, so if it changes between compile time and // run-time, you may run into errors. let payment = sqlx::query_as!( Payment, "update payments.payment \ set status = $1, external_payment_id = $2 \ where payment_id = $3 \ returning *", PaymentStatus::Created, external_payment_id, payment_id.0, ) .fetch_one(&mut *conn) .await?; Ok(payment) } pub async fn get(db: &mut PgConnection, payment_id: PaymentId) -> sqlx::Result> { sqlx::query_as!( Payment, // see note above about `select *` "select * from payments.payment where payment_id = $1", payment_id.0 ) .fetch_optional(db) .await } ================================================ FILE: examples/postgres/multi-tenant/sqlx.toml ================================================ [migrate] # Move `migrations/` to under `src/` to separate it from subcrates. migrations-dir = "src/migrations" ================================================ FILE: examples/postgres/multi-tenant/src/main.rs ================================================ use accounts::AccountsManager; use color_eyre::eyre; use color_eyre::eyre::{Context, OptionExt}; use rand::distributions::{Alphanumeric, DistString}; use sqlx::Connection; #[tokio::main] async fn main() -> eyre::Result<()> { color_eyre::install()?; let _ = dotenvy::dotenv(); tracing_subscriber::fmt::init(); let mut conn = sqlx::PgConnection::connect( // `env::var()` doesn't include the variable name in the error. &dotenvy::var("DATABASE_URL").wrap_err("DATABASE_URL must be set")?, ) .await .wrap_err("could not connect to database")?; // Runs migration for `accounts` internally. let accounts = AccountsManager::setup(&mut conn, 1) .await .wrap_err("error initializing AccountsManager")?; payments::migrate(&mut conn) .await .wrap_err("error running payments migrations")?; // For simplicity's sake, imagine each of these might be invoked by different request routes // in a web application. // POST /account let user_email = format!("user{}@example.com", rand::random::()); let user_password = Alphanumeric.sample_string(&mut rand::thread_rng(), 16); // Requires an externally managed transaction in case any application-specific records // should be created after the actual account record. let mut txn = conn.begin().await?; let account_id = accounts // Takes ownership of the password string because it's sent to another thread for hashing. .create(&mut txn, &user_email, user_password.clone()) .await .wrap_err("error creating account")?; txn.commit().await?; println!( "created account ID: {}, email: {user_email:?}, password: {user_password:?}", account_id.0 ); // POST /session // Log the user in. let session = accounts .create_session(&mut conn, &user_email, user_password.clone()) .await .wrap_err("error creating session")?; // After this, session.session_token should then be returned to the client, // either in the response body or a `Set-Cookie` header. println!("created session token: {}", session.session_token.0); // POST /purchase // The client would then pass the session token to authenticated routes. // In this route, they're making some kind of purchase. // First, we need to ensure the session is valid. // `session.session_token` would be passed by the client in whatever way is appropriate. // // For a pure REST API, consider an `Authorization: Bearer` header instead of the request body. // With Axum, you can create a reusable extractor that reads the header and validates the session // by implementing `FromRequestParts`. // // For APIs where the browser is intended to be the primary client, using a session cookie // may be easier for the frontend. By setting the cookie with `HttpOnly: true`, // it's impossible for malicious Javascript on the client to access and steal the session token. let account_id = accounts .auth_session(&mut conn, &session.session_token.0) .await .wrap_err("error authenticating session")? .ok_or_eyre("session does not exist")?; let purchase_amount: rust_decimal::Decimal = "12.34".parse().unwrap(); // Then, because the user is making a purchase, we record a payment. let payment = payments::create(&mut conn, account_id, "USD", purchase_amount) .await .wrap_err("error creating payment")?; println!("created payment: {payment:?}"); let purchase_id = sqlx::query_scalar!( "insert into purchase(account_id, payment_id, amount) values ($1, $2, $3) returning purchase_id", account_id.0, payment.payment_id.0, purchase_amount ) .fetch_one(&mut conn) .await .wrap_err("error creating purchase")?; println!("created purchase: {purchase_id}"); conn.close().await?; Ok(()) } ================================================ FILE: examples/postgres/multi-tenant/src/migrations/01_setup.sql ================================================ -- We try to ensure every table has `created_at` and `updated_at` columns, which can help immensely with debugging -- and auditing. -- -- While `created_at` can just be `default now()`, setting `updated_at` on update requires a trigger which -- is a lot of boilerplate. These two functions save us from writing that every time as instead we can just do -- -- select trigger_updated_at('
'); -- -- after a `CREATE TABLE`. create or replace function set_updated_at() returns trigger as $$ begin NEW.updated_at = now(); return NEW; end; $$ language plpgsql; create or replace function trigger_updated_at(tablename regclass) returns void as $$ begin execute format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s FOR EACH ROW WHEN (OLD is distinct from NEW) EXECUTE FUNCTION set_updated_at();', tablename); end; $$ language plpgsql; ================================================ FILE: examples/postgres/multi-tenant/src/migrations/02_purchase.sql ================================================ create table purchase ( purchase_id uuid primary key default gen_random_uuid(), account_id uuid not null references accounts.account (account_id), payment_id uuid not null references payments.payment (payment_id), amount numeric not null, created_at timestamptz not null default now(), updated_at timestamptz ); select trigger_updated_at('purchase'); ================================================ FILE: examples/postgres/preferred-crates/Cargo.toml ================================================ [package] name = "sqlx-example-postgres-preferred-crates" version.workspace = true license.workspace = true edition.workspace = true repository.workspace = true keywords.workspace = true categories.workspace = true authors.workspace = true [dependencies] dotenvy.workspace = true anyhow = "1" chrono = "0.4" serde = { version = "1", features = ["derive"] } uuid = { version = "1", features = ["serde"] } [dependencies.tokio] workspace = true features = ["rt-multi-thread", "macros"] [dependencies.sqlx] # version = "0.9.0" workspace = true features = ["runtime-tokio", "postgres", "bigdecimal", "chrono", "derive", "macros", "migrate", "sqlx-toml"] [dependencies.uses-rust-decimal] path = "uses-rust-decimal" package = "sqlx-example-postgres-preferred-crates-uses-rust-decimal" [dependencies.uses-time] path = "uses-time" package = "sqlx-example-postgres-preferred-crates-uses-time" [lints] workspace = true ================================================ FILE: examples/postgres/preferred-crates/README.md ================================================ # Usage of `macros.preferred-crates` in `sqlx.toml` ## The Problem SQLx has many optional features that enable integrations for external crates to map from/to SQL types. In some cases, more than one optional feature applies to the same set of types: * The `chrono` and `time` features enable mapping SQL date/time types to those in these crates. * Similarly, `bigdecimal` and `rust_decimal` enable mapping for the SQL `NUMERIC` type. Throughout its existence, the `query!()` family of macros has inferred which crate to use based on which optional feature was enabled. If multiple features are enabled, one takes precedent over the other: `time` over `chrono`, `rust_decimal` over `bigdecimal`, etc. The ordering is purely the result of historical happenstance and does not indicate any specific preference for one crate over another. They each have their tradeoffs. This works fine when only one crate in the dependency graph depends on SQLx, but can break down if another crate in the dependency graph also depends on SQLx. Because of Cargo's [feature unification], any features enabled by this other crate are also forced on for all other crates that depend on the same version of SQLx in the same project. This is intentional design on Cargo's part; features are meant to be purely additive, so it can build each transitive dependency just once no matter how many crates depend on it. Otherwise, this could result in combinatorial explosion. Unfortunately for us, this means that if your project depends on SQLx and enables the `chrono` feature, but also depends on another crate that enables the `time` feature, the `query!()` macros will end up thinking that _you_ want to use the `time` crate, because they don't know any better. Fixing this has historically required patching the dependency, which is annoying to maintain long-term. [feature unification]: https://doc.rust-lang.org/cargo/reference/features.html#feature-unification ## The Solution However, as of 0.9.0, SQLx has gained the ability to configure the macros through the use of a `sqlx.toml` file. This includes the ability to tell the macros which crate you prefer, overriding the inference. See the [`sqlx.toml`](./sqlx.toml) file in this directory for details. A full reference `sqlx.toml` is also available as `sqlx-core/src/config/reference.toml`. ## This Example This example exists both to showcase the macro configuration and also serve as a test for the functionality. It consists of three crates: * The root crate, which depends on SQLx and enables the `chrono` and `bigdecimal` features, * `uses-rust-decimal`, a dependency which also depends on SQLx and enables the `rust_decimal` feature, * and `uses-time`, a dependency which also depends on SQLx and enables the `time` feature. * This serves as a stand-in for `tower-sessions-sqlx-store`, which is [one of the culprits for this issue](https://github.com/launchbadge/sqlx/issues/3412#issuecomment-2277377597). Given that both dependencies enable features with higher precedence, they would historically have interfered with the usage in the root crate. (Pretend that they're published to crates.io and cannot be easily changed.) However, because the root crate uses a `sqlx.toml`, the macros know exactly which crates it wants to use and everyone's happy. ================================================ FILE: examples/postgres/preferred-crates/sqlx.toml ================================================ [migrate] # Move `migrations/` to under `src/` to separate it from subcrates. migrations-dir = "src/migrations" [macros.preferred-crates] # Keeps `time` from taking precedent even though it's enabled by a dependency. date-time = "chrono" # Same thing with `rust_decimal` numeric = "bigdecimal" ================================================ FILE: examples/postgres/preferred-crates/src/main.rs ================================================ use anyhow::Context; use chrono::{DateTime, Utc}; use sqlx::{Connection, PgConnection}; use std::time::Duration; use uuid::Uuid; #[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Debug)] struct SessionData { user_id: Uuid, } #[derive(sqlx::FromRow, Debug)] struct User { id: Uuid, username: String, password_hash: String, // Because `time` is enabled by a transitive dependency, we previously would have needed // a type override in the query to get types from `chrono`. created_at: DateTime, updated_at: Option>, } const SESSION_DURATION: Duration = Duration::from_secs(60 * 60); // 1 hour #[tokio::main] async fn main() -> anyhow::Result<()> { let mut conn = PgConnection::connect(&dotenvy::var("DATABASE_URL").context("DATABASE_URL must be set")?) .await .context("failed to connect to DATABASE_URL")?; sqlx::migrate!("./src/migrations").run(&mut conn).await?; uses_rust_decimal::create_table(&mut conn).await?; uses_time::create_table(&mut conn).await?; let user_id = sqlx::query_scalar!( "insert into users(username, password_hash) values($1, $2) returning id", "user_foo", "", ) .fetch_one(&mut conn) .await?; let user = sqlx::query_as!(User, "select * from users where id = $1", user_id) .fetch_one(&mut conn) .await?; println!("Created user: {user:?}"); let session = uses_time::create_session(&mut conn, SessionData { user_id }, SESSION_DURATION).await?; let session_from_id = uses_time::get_session::(&mut conn, session.id) .await? .expect("expected session"); assert_eq!(session, session_from_id); let purchase_id = uses_rust_decimal::create_purchase(&mut conn, user_id, 1234u32.into(), "Rent").await?; let purchase = uses_rust_decimal::get_purchase(&mut conn, purchase_id) .await? .expect("expected purchase"); println!("Created purchase: {purchase:?}"); Ok(()) } ================================================ FILE: examples/postgres/preferred-crates/src/migrations/01_setup.sql ================================================ -- We try to ensure every table has `created_at` and `updated_at` columns, which can help immensely with debugging -- and auditing. -- -- While `created_at` can just be `default now()`, setting `updated_at` on update requires a trigger which -- is a lot of boilerplate. These two functions save us from writing that every time as instead we can just do -- -- select trigger_updated_at('
'); -- -- after a `CREATE TABLE`. create or replace function set_updated_at() returns trigger as $$ begin NEW.updated_at = now(); return NEW; end; $$ language plpgsql; create or replace function trigger_updated_at(tablename regclass) returns void as $$ begin execute format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s FOR EACH ROW WHEN (OLD is distinct from NEW) EXECUTE FUNCTION set_updated_at();', tablename); end; $$ language plpgsql; ================================================ FILE: examples/postgres/preferred-crates/src/migrations/02_users.sql ================================================ create table users( id uuid primary key default gen_random_uuid(), username text not null, password_hash text not null, created_at timestamptz not null default now(), updated_at timestamptz ); create unique index users_username_unique on users(lower(username)); select trigger_updated_at('users'); ================================================ FILE: examples/postgres/preferred-crates/uses-rust-decimal/Cargo.toml ================================================ [package] name = "sqlx-example-postgres-preferred-crates-uses-rust-decimal" version.workspace = true license.workspace = true edition.workspace = true repository.workspace = true keywords.workspace = true categories.workspace = true authors.workspace = true [dependencies] chrono = "0.4" rust_decimal = "1" uuid = "1" [dependencies.sqlx] workspace = true features = ["runtime-tokio", "postgres", "rust_decimal", "chrono", "uuid"] [lints] workspace = true ================================================ FILE: examples/postgres/preferred-crates/uses-rust-decimal/src/lib.rs ================================================ use chrono::{DateTime, Utc}; use sqlx::PgExecutor; #[derive(sqlx::FromRow, Debug)] pub struct Purchase { pub id: Uuid, pub user_id: Uuid, pub amount: Decimal, pub description: String, pub created_at: DateTime, } pub use rust_decimal::Decimal; use uuid::Uuid; pub async fn create_table(e: impl PgExecutor<'_>) -> sqlx::Result<()> { sqlx::raw_sql( // language=PostgreSQL "create table if not exists purchases( \ id uuid primary key default gen_random_uuid(), \ user_id uuid not null, \ amount numeric not null check(amount > 0), \ description text not null, \ created_at timestamptz not null default now() \ ); ", ) .execute(e) .await?; Ok(()) } pub async fn create_purchase( e: impl PgExecutor<'_>, user_id: Uuid, amount: Decimal, description: &str, ) -> sqlx::Result { sqlx::query_scalar( "insert into purchases(user_id, amount, description) values ($1, $2, $3) returning id", ) .bind(user_id) .bind(amount) .bind(description) .fetch_one(e) .await } pub async fn get_purchase(e: impl PgExecutor<'_>, id: Uuid) -> sqlx::Result> { sqlx::query_as("select * from purchases where id = $1") .bind(id) .fetch_optional(e) .await } ================================================ FILE: examples/postgres/preferred-crates/uses-time/Cargo.toml ================================================ [package] name = "sqlx-example-postgres-preferred-crates-uses-time" version.workspace = true license.workspace = true edition.workspace = true repository.workspace = true keywords.workspace = true categories.workspace = true authors.workspace = true [dependencies] serde = "1" time = "0.3" uuid = "1" [dependencies.sqlx] workspace = true features = ["runtime-tokio", "postgres", "time", "json", "uuid"] [lints] workspace = true ================================================ FILE: examples/postgres/preferred-crates/uses-time/src/lib.rs ================================================ use serde::de::DeserializeOwned; use serde::Serialize; use sqlx::PgExecutor; use std::time::Duration; use time::OffsetDateTime; use sqlx::types::Json; use uuid::Uuid; #[derive(sqlx::FromRow, PartialEq, Eq, Debug)] pub struct Session { pub id: Uuid, #[sqlx(json)] pub data: D, pub created_at: OffsetDateTime, pub expires_at: OffsetDateTime, } pub async fn create_table(e: impl PgExecutor<'_>) -> sqlx::Result<()> { sqlx::raw_sql( // language=PostgreSQL "create table if not exists sessions( \ id uuid primary key default gen_random_uuid(), \ data jsonb not null, created_at timestamptz not null default now(), expires_at timestamptz not null )", ) .execute(e) .await?; Ok(()) } pub async fn create_session( e: impl PgExecutor<'_>, data: D, valid_duration: Duration, ) -> sqlx::Result> { // Round down to the nearest second because // Postgres doesn't support precision higher than 1 microsecond anyway. let created_at = OffsetDateTime::now_utc() .replace_nanosecond(0) .expect("0 nanoseconds should be in range"); let expires_at = created_at + valid_duration; let id: Uuid = sqlx::query_scalar( "insert into sessions(data, created_at, expires_at) \ values ($1, $2, $3) \ returning id", ) .bind(Json(&data)) .bind(created_at) .bind(expires_at) .fetch_one(e) .await?; Ok(Session { id, data, created_at, expires_at, }) } pub async fn get_session( e: impl PgExecutor<'_>, id: Uuid, ) -> sqlx::Result>> { sqlx::query_as("select id, data, created_at, expires_at from sessions where id = $1") .bind(id) .fetch_optional(e) .await } ================================================ FILE: examples/postgres/todos/Cargo.toml ================================================ [package] name = "sqlx-example-postgres-todos" version = "0.1.0" edition = "2018" workspace = "../../../" [dependencies] anyhow = "1.0" sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] } clap = { version = "4", features = ["derive"] } tokio = { version = "1.20.0", features = ["rt", "macros"]} dotenvy = "0.15.0" ================================================ FILE: examples/postgres/todos/README.md ================================================ # TODOs Example ## Setup 1. Declare the database URL ``` export DATABASE_URL="postgres://postgres:password@localhost/todos" ``` 2. Create the database. ``` $ sqlx db create ``` 3. Run sql migrations ``` $ sqlx migrate run ``` ## Usage Add a todo ``` cargo run -- add "todo description" ``` Complete a todo. ``` cargo run -- done ``` List all todos ``` cargo run ``` ================================================ FILE: examples/postgres/todos/migrations/20200718111257_todos.sql ================================================ CREATE TABLE IF NOT EXISTS todos ( id BIGSERIAL PRIMARY KEY, description TEXT NOT NULL, done BOOLEAN NOT NULL DEFAULT FALSE ); ================================================ FILE: examples/postgres/todos/src/main.rs ================================================ use clap::{Parser, Subcommand}; use sqlx::postgres::PgPool; use std::env; #[derive(Parser)] struct Args { #[command(subcommand)] cmd: Option, } #[derive(Subcommand)] enum Command { Add { description: String }, Done { id: i64 }, } #[tokio::main(flavor = "current_thread")] async fn main() -> anyhow::Result<()> { let args = Args::parse(); let pool = PgPool::connect(&env::var("DATABASE_URL")?).await?; match args.cmd { Some(Command::Add { description }) => { println!("Adding new todo with description '{description}'"); let todo_id = add_todo(&pool, description).await?; println!("Added new todo with id {todo_id}"); } Some(Command::Done { id }) => { println!("Marking todo {id} as done"); if complete_todo(&pool, id).await? { println!("Todo {id} is marked as done"); } else { println!("Invalid id {id}"); } } None => { println!("Printing list of all todos"); list_todos(&pool).await?; } } Ok(()) } async fn add_todo(pool: &PgPool, description: String) -> anyhow::Result { let rec = sqlx::query!( r#" INSERT INTO todos ( description ) VALUES ( $1 ) RETURNING id "#, description ) .fetch_one(pool) .await?; Ok(rec.id) } async fn complete_todo(pool: &PgPool, id: i64) -> anyhow::Result { let rows_affected = sqlx::query!( r#" UPDATE todos SET done = TRUE WHERE id = $1 "#, id ) .execute(pool) .await? .rows_affected(); Ok(rows_affected > 0) } async fn list_todos(pool: &PgPool) -> anyhow::Result<()> { let recs = sqlx::query!( r#" SELECT id, description, done FROM todos ORDER BY id "# ) .fetch_all(pool) .await?; for rec in recs { println!( "- [{}] {}: {}", if rec.done { "x" } else { " " }, rec.id, &rec.description, ); } Ok(()) } ================================================ FILE: examples/postgres/transaction/Cargo.toml ================================================ [package] name = "sqlx-example-postgres-transaction" version = "0.1.0" edition = "2021" workspace = "../../../" [dependencies] sqlx = { path = "../../../", features = [ "postgres", "runtime-tokio", "tls-native-tls" ] } tokio = { version = "1.20.0", features = ["rt-multi-thread", "macros"]} ================================================ FILE: examples/postgres/transaction/README.md ================================================ # Postgres Transaction Example A simple example demonstrating how to obtain and roll back a transaction with postgres. ## Usage Declare the database URL. This example does not include any reading or writing of data. ``` export DATABASE_URL="postgres://postgres@localhost/postgres" ``` Run. ``` cargo run ``` ================================================ FILE: examples/postgres/transaction/migrations/20200718111257_todos.sql ================================================ CREATE TABLE IF NOT EXISTS todos ( id BIGSERIAL PRIMARY KEY, description TEXT NOT NULL, done BOOLEAN NOT NULL DEFAULT FALSE ); ================================================ FILE: examples/postgres/transaction/src/main.rs ================================================ use sqlx::query; async fn insert_and_verify( transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>, test_id: i64, ) -> Result<(), Box> { query!( r#"INSERT INTO todos (id, description) VALUES ( $1, $2 ) "#, test_id, "test todo" ) // In 0.7, `Transaction` can no longer implement `Executor` directly, // so it must be dereferenced to the internal connection type. .execute(&mut **transaction) .await?; // check that inserted todo can be fetched inside the uncommitted transaction let _ = query!(r#"SELECT FROM todos WHERE id = $1"#, test_id) .fetch_one(&mut **transaction) .await?; Ok(()) } async fn explicit_rollback_example( pool: &sqlx::PgPool, test_id: i64, ) -> Result<(), Box> { let mut transaction = pool.begin().await?; insert_and_verify(&mut transaction, test_id).await?; transaction.rollback().await?; Ok(()) } async fn implicit_rollback_example( pool: &sqlx::PgPool, test_id: i64, ) -> Result<(), Box> { let mut transaction = pool.begin().await?; insert_and_verify(&mut transaction, test_id).await?; // no explicit rollback here but the transaction object is dropped at the end of the scope Ok(()) } async fn commit_example( pool: &sqlx::PgPool, test_id: i64, ) -> Result<(), Box> { let mut transaction = pool.begin().await?; insert_and_verify(&mut transaction, test_id).await?; transaction.commit().await?; Ok(()) } #[tokio::main] async fn main() -> Result<(), Box> { let conn_str = std::env::var("DATABASE_URL").expect("Env var DATABASE_URL is required for this example."); let pool = sqlx::PgPool::connect(&conn_str).await?; let test_id = 1; // remove any old values that might be in the table already with this id from a previous run let _ = query!(r#"DELETE FROM todos WHERE id = $1"#, test_id) .execute(&pool) .await?; explicit_rollback_example(&pool, test_id).await?; // check that inserted todo is not visible outside the transaction after explicit rollback let inserted_todo = query!(r#"SELECT FROM todos WHERE id = $1"#, test_id) .fetch_one(&pool) .await; assert!(inserted_todo.is_err()); implicit_rollback_example(&pool, test_id).await?; // check that inserted todo is not visible outside the transaction after implicit rollback let inserted_todo = query!(r#"SELECT FROM todos WHERE id = $1"#, test_id) .fetch_one(&pool) .await; assert!(inserted_todo.is_err()); commit_example(&pool, test_id).await?; // check that inserted todo is visible outside the transaction after commit let inserted_todo = query!(r#"SELECT FROM todos WHERE id = $1"#, test_id) .fetch_one(&pool) .await; assert!(inserted_todo.is_ok()); Ok(()) } ================================================ FILE: examples/sqlite/extension/Cargo.toml ================================================ [package] name = "sqlx-example-sqlite-extension" version = "0.1.0" license.workspace = true edition.workspace = true repository.workspace = true keywords.workspace = true categories.workspace = true authors.workspace = true [dependencies] sqlx = { path = "../../../", features = [ "sqlite", "runtime-tokio", "tls-native-tls", "sqlx-toml"] } tokio = { version = "1.20.0", features = ["rt", "macros"]} anyhow = "1.0" [lints] workspace = true ================================================ FILE: examples/sqlite/extension/download-extension.sh ================================================ #!/bin/bash # This grabs a pre-compiled version of the extension used in this # example, and stores it in a temporary directory. That's a bit # unusual. Normally, any extensions you need will be installed into a # directory on the library search path, either by using the system # package manager or by compiling and installing it yourself. mkdir /tmp/sqlite3-lib && wget -O /tmp/sqlite3-lib/ipaddr.so https://github.com/nalgeon/sqlean/releases/download/0.15.2/ipaddr.so ================================================ FILE: examples/sqlite/extension/migrations/20250203094951_addresses.sql ================================================ create table addresses (address text, family integer); -- The `ipfamily` function is provided by the -- [ipaddr](https://github.com/nalgeon/sqlean/blob/main/docs/ipaddr.md) -- sqlite extension, and so this migration can not run if that -- extension is not loaded. insert into addresses (address, family) values ('fd04:3d29:9f41::1', ipfamily('fd04:3d29:9f41::1')), ('10.0.0.1', ipfamily('10.0.0.1')), ('10.0.0.2', ipfamily('10.0.0.2')), ('fd04:3d29:9f41::2', ipfamily('fd04:3d29:9f41::2')), ('fd04:3d29:9f41::3', ipfamily('fd04:3d29:9f41::3')), ('10.0.0.3', ipfamily('10.0.0.3')), ('fd04:3d29:9f41::4', ipfamily('fd04:3d29:9f41::4')), ('fd04:3d29:9f41::5', ipfamily('fd04:3d29:9f41::5')), ('fd04:3d29:9f41::6', ipfamily('fd04:3d29:9f41::6')), ('10.0.0.4', ipfamily('10.0.0.4')), ('10.0.0.5', ipfamily('10.0.0.5')), ('10.0.0.6', ipfamily('10.0.0.6')), ('10.0.0.7', ipfamily('10.0.0.7')), ('fd04:3d29:9f41::7', ipfamily('fd04:3d29:9f41::7')), ('fd04:3d29:9f41::8', ipfamily('fd04:3d29:9f41::8')), ('10.0.0.8', ipfamily('10.0.0.8')), ('fd04:3d29:9f41::9', ipfamily('fd04:3d29:9f41::9')), ('10.0.0.9', ipfamily('10.0.0.9')); ================================================ FILE: examples/sqlite/extension/sqlx.toml ================================================ [common.drivers.sqlite] # Including the full path to the extension is somewhat unusual, # because normally an extension will be installed in a standard # directory which is part of the library search path. If that were the # case here, the unsafe-load-extensions value could just be `["ipaddr"]` # # When the extension file is installed in a non-standard location, as # in this example, there are two options: # * Provide the full path the the extension, as seen below. # * Add the non-standard location to the library search path, which on # Linux means adding it to the LD_LIBRARY_PATH environment variable. unsafe-load-extensions = ["/tmp/sqlite3-lib/ipaddr"] ================================================ FILE: examples/sqlite/extension/src/main.rs ================================================ use std::str::FromStr; use sqlx::{ query, sqlite::{SqliteConnectOptions, SqlitePool}, }; #[tokio::main(flavor = "current_thread")] async fn main() -> anyhow::Result<()> { let opts = SqliteConnectOptions::from_str(&std::env::var("DATABASE_URL")?)? // The sqlx.toml file controls loading extensions for the CLI // and for the query checking macros, *not* for the // application while it's running. Thus, if we want the // extension to be available during program execution, we need // to load it. // // Note that while in this case the extension path is the same // when checking the program (sqlx.toml) and when running it // (here), this is not required. The runtime environment can // be entirely different from the development one. // // The extension can be described with a full path, as seen // here, but in many cases that will not be necessary. As long // as the extension is installed in a directory on the library // search path, it is sufficient to just provide the extension // name, like "ipaddr" .extension("/tmp/sqlite3-lib/ipaddr"); let db = SqlitePool::connect_with(opts).await?; // We're not running the migrations here, for the sake of brevity // and to confirm that the needed extension was loaded during the // CLI migrate operation. It would not be unusual to run the // migrations here as well, though, using the database connection // we just configured. query!( "insert into addresses (address, family) values (?1, ipfamily(?1))", "10.0.0.10" ) .execute(&db) .await?; println!("Query which requires the extension was successfully executed."); Ok(()) } ================================================ FILE: examples/sqlite/todos/Cargo.toml ================================================ [package] name = "sqlx-example-sqlite-todos" version = "0.1.0" edition = "2018" workspace = "../../../" [dependencies] anyhow = "1.0" sqlx = { path = "../../../", features = [ "sqlite", "runtime-tokio", "tls-native-tls" ] } clap = { version = "4", features = ["derive"] } tokio = { version = "1.20.0", features = ["rt", "macros"]} ================================================ FILE: examples/sqlite/todos/README.md ================================================ # TODOs Example ## Setup 1. Declare the database URL ``` export DATABASE_URL="sqlite:todos.db" ``` 2. Create the database. ``` $ sqlx db create ``` 3. Run sql migrations ``` $ sqlx migrate run ``` ## Usage Add a todo ``` cargo run -- add "todo description" ``` Complete a todo. ``` cargo run -- done ``` List all todos ``` cargo run ``` ================================================ FILE: examples/sqlite/todos/migrations/20200718111257_todos.sql ================================================ CREATE TABLE IF NOT EXISTS todos ( id INTEGER PRIMARY KEY NOT NULL, description TEXT NOT NULL, done BOOLEAN NOT NULL DEFAULT 0 ); ================================================ FILE: examples/sqlite/todos/src/main.rs ================================================ use clap::{Parser, Subcommand}; use sqlx::sqlite::SqlitePool; use std::env; #[derive(Parser)] struct Args { #[command(subcommand)] cmd: Option, } #[derive(Subcommand)] enum Command { Add { description: String }, Done { id: i64 }, } #[tokio::main(flavor = "current_thread")] async fn main() -> anyhow::Result<()> { let args = Args::parse(); let pool = SqlitePool::connect(&env::var("DATABASE_URL")?).await?; match args.cmd { Some(Command::Add { description }) => { println!("Adding new todo with description '{description}'"); let todo_id = add_todo(&pool, description).await?; println!("Added new todo with id {todo_id}"); } Some(Command::Done { id }) => { println!("Marking todo {id} as done"); if complete_todo(&pool, id).await? { println!("Todo {id} is marked as done"); } else { println!("Invalid id {id}"); } } None => { println!("Printing list of all todos"); list_todos(&pool).await?; } } Ok(()) } async fn add_todo(pool: &SqlitePool, description: String) -> anyhow::Result { let mut conn = pool.acquire().await?; // Insert the task, then obtain the ID of this row let id = sqlx::query!( r#" INSERT INTO todos ( description ) VALUES ( ?1 ) "#, description ) .execute(&mut *conn) .await? .last_insert_rowid(); Ok(id) } async fn complete_todo(pool: &SqlitePool, id: i64) -> anyhow::Result { let rows_affected = sqlx::query!( r#" UPDATE todos SET done = TRUE WHERE id = ?1 "#, id ) .execute(pool) .await? .rows_affected(); Ok(rows_affected > 0) } async fn list_todos(pool: &SqlitePool) -> anyhow::Result<()> { let recs = sqlx::query!( r#" SELECT id, description, done FROM todos ORDER BY id "# ) .fetch_all(pool) .await?; for rec in recs { println!( "- [{}] {}: {}", if rec.done { "x" } else { " " }, rec.id, &rec.description, ); } Ok(()) } ================================================ FILE: examples/x.py ================================================ #!/usr/bin/env python3 import sys import os from os import path # base dir of sqlx workspace dir_workspace = path.dirname(path.dirname(path.realpath(__file__))) # dir of tests dir_tests = path.join(dir_workspace, "tests") # extend import path to tests/ sys.path.append(dir_tests) import subprocess import time import argparse from docker import start_database parser = argparse.ArgumentParser() parser.add_argument("-p", "--project") parser.add_argument("-l", "--list-projects", action="store_true") argv, unknown = parser.parse_known_args() def run(command, env=None, cwd=None, display=None): if display: print(f"\x1b[93m $ {display}\x1b[0m") else: print(f"\x1b[93m $ {command}\x1b[0m") res = subprocess.run( command.split(" "), env=dict(**os.environ, **env), cwd=cwd, ) if res.returncode != 0: sys.exit(res.returncode) def sqlx(command, url, cwd=None): run(f"cargo --quiet run -p sqlx-cli --bin sqlx -- {command}", cwd=cwd, env={"DATABASE_URL": url}, display=f"sqlx {command}") def project(name, database=None, driver=None): if argv.list_projects: print(f"{name}") return if argv.project and name != argv.project: return print(f"\x1b[2m # {name}\x1b[0m") env = {} cwd = path.join(dir_workspace, "examples", name) if database is not None: database_url = start_database(driver, database, cwd=cwd) env["DATABASE_URL"] = database_url # show the database url print(f"\x1b[94m @ {database_url}\x1b[0m") # database drop (if exists) sqlx("db drop -y", database_url, cwd=cwd) # database create sqlx("db create", database_url, cwd=cwd) # migrate sqlx("migrate run", database_url, cwd=cwd) # check run("cargo check", cwd=cwd, env=env) # todos project("mysql/todos", driver="mysql_8", database="todos") project("postgres/todos", driver="postgres_12", database="todos") project("sqlite/todos", driver="sqlite", database="todos.db") project("sqlite/extension", driver="sqlite", database="extension.db") ================================================ FILE: gen-changelog.sh ================================================ # Requires Github CLI and `jq` # Usage: `./gen-changelog.sh YYYY-mm-dd` # Generates changelog entries for all PRs merged on or after the given date. set -e PULLS='[]' CURSOR='null' MIN_MERGED_AT=$(date --date="$1" +%s) while true do # Use the GraphQL API to paginate merged pull requests. # The REST API doesn't allow filtering only merged pull requests. # We scan all merged pull requests from the beginning because it's not unheard of to have a very old PR finally get # merged; e.g. #1081, merged a year and a half after it was opened. if [ "$CURSOR" != "null" ]; then PAGE=$(gh api graphql -f after="$CURSOR" -f query='query($after: String) { repository(owner: "launchbadge", name: "sqlx") { pullRequests(first:100,orderBy: {field:CREATED_AT, direction:ASC},states:MERGED, after: $after) { nodes { number author { login } title url mergedAt } pageInfo { hasNextPage endCursor } } } }'); else PAGE=$(gh api graphql -f query='query { repository(owner: "launchbadge", name: "sqlx") { pullRequests(first:100,orderBy: {field:CREATED_AT, direction:ASC},states:MERGED) { nodes { number author { login } title url mergedAt } pageInfo { hasNextPage endCursor } } } }'); fi CURSOR=$(echo "$PAGE" | jq -r '.data.repository.pullRequests.pageInfo.endCursor'); HAS_NEXT_PAGE=$(echo "$PAGE" | jq '.data.repository.pullRequests.pageInfo.hasNextPage'); PULLS=$(echo "$PAGE" | jq "$PULLS + (.data.repository.pullRequests.nodes | map(select(.mergedAt | fromdate >= $MIN_MERGED_AT)))"); # can't use `"$CURSOR" == 'null'` because the last page still gives a valid cursor if ! $HAS_NEXT_PAGE; then break; fi; done COUNT=$(echo "$PULLS" | jq "length"); echo "Found $COUNT pull requests merged on or after $1\n" if [ -z $COUNT ]; then exit 0; fi; echo "Entries:" echo "$PULLS" | jq -r 'map("* [[#\(.number)]]: \(.title) [[@\(.author.login)]]") | join("\n")' echo "\nLinks:" echo "$PULLS" | jq -r 'map("[#\(.number)]: \(.url)") | join("\n")' echo "\nNew Authors:" DUPE_AUTHORS='' # Generate link entries for new authors at the end of the changelog. echo "$PULLS" | jq -r '.[].author.login' | while read author; do author_url="https://github.com/$author" author_entry="[@$author]: $author_url" # Check if the entry already exists in the changelog or in our list of new authors. if grep -qF "$author_entry" CHANGELOG.md || echo "$DUPE_AUTHORS" | grep -qF "$author_entry"; then continue; fi; DUPE_AUTHORS="$DUPE_AUTHORS$author_entry\n" echo $author_entry done ================================================ FILE: rust-toolchain.toml ================================================ # Note: should NOT increase during a minor/patch release cycle [toolchain] channel = "1.86" profile = "minimal" ================================================ FILE: sqlx-cli/Cargo.toml ================================================ [package] name = "sqlx-cli" version.workspace = true description = "Command-line utility for SQLx, the Rust SQL toolkit." edition = "2021" readme = "README.md" homepage = "https://github.com/launchbadge/sqlx" repository = "https://github.com/launchbadge/sqlx" keywords = ["database", "postgres", "database-management", "migration"] categories = ["database", "command-line-utilities"] license = "MIT OR Apache-2.0" default-run = "sqlx" authors = [ "Jesper Axelsson ", "Austin Bonander ", ] rust-version.workspace = true [[bin]] name = "sqlx" path = "src/bin/sqlx.rs" # enables invocation as `cargo sqlx`; required for `prepare` subcommand [[bin]] name = "cargo-sqlx" path = "src/bin/cargo-sqlx.rs" [dependencies] dotenvy = "0.15.0" tokio = { version = "1.15.0", features = ["macros", "rt", "rt-multi-thread", "signal"] } futures-util = { version = "0.3.19", features = ["alloc"] } clap = { version = "4.3.10", features = ["derive", "env", "wrap_help"] } clap_complete = { version = "4.3.1", optional = true } chrono = { version = "0.4.19", default-features = false, features = ["clock"] } anyhow = "1.0.52" console = "0.15.0" dialoguer = { version = "0.11", default-features = false } serde_json = "1.0.73" glob = "0.3.0" openssl = { version = "0.10.46", optional = true } cargo_metadata = "0.18.1" filetime = "0.2" backoff = { version = "0.4.0", features = ["futures", "tokio"] } [dependencies.sqlx] workspace = true default-features = false features = [ "runtime-tokio", "migrate", "any", ] [features] default = ["postgres", "sqlite", "mysql", "native-tls", "completions", "sqlx-toml"] # TLS options rustls = ["sqlx/tls-rustls"] native-tls = ["sqlx/tls-native-tls"] # databases mysql = ["sqlx/mysql"] postgres = ["sqlx/postgres"] sqlite = ["sqlx/sqlite", "_sqlite"] sqlite-unbundled = ["sqlx/sqlite-unbundled", "_sqlite"] # workaround for musl + openssl issues openssl-vendored = ["openssl/vendored"] completions = ["dep:clap_complete"] sqlx-toml = ["sqlx/sqlx-toml"] # Conditional compilation only _sqlite = [] [dev-dependencies] assert_cmd = "2.1.1" tempfile = "3.10.1" [lints] workspace = true ================================================ FILE: sqlx-cli/README.md ================================================ # SQLx CLI SQLx's associated command-line utility for managing databases, migrations, and enabling "offline" mode with `sqlx::query!()` and friends. ## Install ### With Rust toolchain ```bash # supports all databases supported by SQLx $ cargo install sqlx-cli # only for postgres $ cargo install sqlx-cli --no-default-features --features native-tls,postgres # use vendored OpenSSL (build from source) $ cargo install sqlx-cli --features openssl-vendored # use Rustls rather than OpenSSL (be sure to add the features for the databases you intend to use!) $ cargo install sqlx-cli --no-default-features --features rustls # only for sqlite and use the system sqlite library $ cargo install sqlx-cli --no-default-features --features sqlite-unbundled ``` ## Usage All commands require that a database url is provided. This can be done either with the `--database-url` command line option or by setting `DATABASE_URL`, either in the environment or in a `.env` file in the current working directory. For more details, run `sqlx --help`. ```dotenv # Postgres DATABASE_URL=postgres://postgres@localhost/my_database ``` ### Create/drop the database at `DATABASE_URL` ```bash sqlx database create sqlx database drop ``` --- ### Create and run migrations ```bash sqlx migrate add ``` Creates a new file in `migrations/-.sql`. Add your database schema changes to this new file. --- ```bash sqlx migrate run ``` Compares the migration history of the running database against the `migrations/` folder and runs any scripts that are still pending. --- Users can provide the directory for the migration scripts to `sqlx migrate` subcommands with the `--source` flag. ```bash sqlx migrate info --source ../relative/migrations ``` --- ### Reverting Migrations If you would like to create _reversible_ migrations with corresponding "up" and "down" scripts, you use the `-r` flag when creating the first migration: ```bash $ sqlx migrate add -r Creating migrations/20211001154420_.up.sql Creating migrations/20211001154420_.down.sql ``` After that, you can run these as above: ```bash $ sqlx migrate run Applied migrations/20211001154420 (32.517835ms) ``` And reverts work as well: ```bash $ sqlx migrate revert Applied 20211001154420/revert ``` **Note**: All the subsequent migrations will be reversible as well. ```bash $ sqlx migrate add Creating migrations/20211001154420_.up.sql Creating migrations/20211001154420_.down.sql ``` ### Enable building in "offline mode" with `query!()` There are 2 steps to building with "offline mode": 1. Save query metadata for offline usage - `cargo sqlx prepare` 2. Build Note: Saving query metadata must be run as `cargo sqlx`. ```bash cargo sqlx prepare ``` Invoking `prepare` saves query metadata to `.sqlx` in the current directory. For workspaces where several crates are using query macros, pass the `--workspace` flag to generate a single `.sqlx` directory at the root of the workspace. ```bash cargo sqlx prepare --workspace ``` Check this directory into version control and an active database connection will no longer be needed to build your project. --- ```bash cargo sqlx prepare --check # OR cargo sqlx prepare --check --workspace ``` Exits with a nonzero exit status if the data in `.sqlx` is out of date with the current database schema or queries in the project. Intended for use in Continuous Integration. ### Force building in offline mode The presence of a `DATABASE_URL` environment variable will take precedence over the presence of `.sqlx`, meaning SQLx will default to building against a database if it can. To make sure an accidentally-present `DATABASE_URL` environment variable or `.env` file does not result in `cargo build` (trying to) access the database, you can set the `SQLX_OFFLINE` environment variable to `true`. If you want to make this the default, just add it to your `.env` file. `cargo sqlx prepare` will still do the right thing and connect to the database. ### Include queries behind feature flags (such as queries inside of tests) In order for sqlx to be able to find queries behind certain feature flags or in tests, you need to turn them on by passing arguments to `cargo`. This is how you would turn all targets and features on. ```bash cargo sqlx prepare -- --all-targets --all-features ``` ================================================ FILE: sqlx-cli/src/bin/cargo-sqlx.rs ================================================ use clap::Parser; use console::style; use sqlx_cli::Opt; use std::process; // cargo invokes this binary as `cargo-sqlx sqlx ` // so the parser below is defined with that in mind #[derive(Parser, Debug)] #[clap(bin_name = "cargo")] enum Cli { Sqlx(Opt), } #[tokio::main] async fn main() { sqlx_cli::maybe_apply_dotenv(); sqlx::any::install_default_drivers(); let Cli::Sqlx(opt) = Cli::parse(); if let Err(error) = sqlx_cli::run(opt).await { println!("{} {}", style("error:").bold().red(), error); process::exit(1); } } ================================================ FILE: sqlx-cli/src/bin/sqlx.rs ================================================ use clap::Parser; use console::style; use sqlx_cli::Opt; #[tokio::main] async fn main() { // Checks for `--no-dotenv` before parsing. sqlx_cli::maybe_apply_dotenv(); sqlx::any::install_default_drivers(); let opt = Opt::parse(); // no special handling here if let Err(error) = sqlx_cli::run(opt).await { println!("{} {}", style("error:").bold().red(), error); std::process::exit(1); } } ================================================ FILE: sqlx-cli/src/completions.rs ================================================ use std::io; use clap::CommandFactory; use clap_complete::{generate, Shell}; use crate::opt::Command; pub fn run(shell: Shell) { generate(shell, &mut Command::command(), "sqlx", &mut io::stdout()) } ================================================ FILE: sqlx-cli/src/database.rs ================================================ use crate::opt::{ConnectOpts, MigrationSourceOpt}; use crate::{migrate, Config}; use console::{style, Term}; use dialoguer::Confirm; use sqlx::any::Any; use sqlx::migrate::MigrateDatabase; use std::{io, mem}; use tokio::task; pub async fn create(connect_opts: &ConnectOpts) -> anyhow::Result<()> { // NOTE: only retry the idempotent action. // We're assuming that if this succeeds, then any following operations should also succeed. let exists = crate::retry_connect_errors(connect_opts, Any::database_exists).await?; if !exists { #[cfg(feature = "_sqlite")] sqlx::sqlite::CREATE_DB_WAL.store( connect_opts.sqlite_create_db_wal, std::sync::atomic::Ordering::Release, ); Any::create_database(connect_opts.expect_db_url()?).await?; } Ok(()) } pub async fn drop(connect_opts: &ConnectOpts, confirm: bool, force: bool) -> anyhow::Result<()> { if confirm && !ask_to_continue_drop(connect_opts.expect_db_url()?.to_owned()).await { return Ok(()); } // NOTE: only retry the idempotent action. // We're assuming that if this succeeds, then any following operations should also succeed. let exists = crate::retry_connect_errors(connect_opts, Any::database_exists).await?; if exists { if force { Any::force_drop_database(connect_opts.expect_db_url()?).await?; } else { Any::drop_database(connect_opts.expect_db_url()?).await?; } } Ok(()) } pub async fn reset( config: &Config, migration_source: &MigrationSourceOpt, connect_opts: &ConnectOpts, confirm: bool, force: bool, ) -> anyhow::Result<()> { drop(connect_opts, confirm, force).await?; setup(config, migration_source, connect_opts).await } pub async fn setup( config: &Config, migration_source: &MigrationSourceOpt, connect_opts: &ConnectOpts, ) -> anyhow::Result<()> { create(connect_opts).await?; migrate::run(config, migration_source, connect_opts, false, false, None).await } async fn ask_to_continue_drop(db_url: String) -> bool { // If the setup operation is cancelled while we are waiting for the user to decide whether // or not to drop the database, this will restore the terminal's cursor to its normal state. struct RestoreCursorGuard { disarmed: bool, } impl Drop for RestoreCursorGuard { fn drop(&mut self) { if !self.disarmed { Term::stderr().show_cursor().unwrap() } } } let mut guard = RestoreCursorGuard { disarmed: false }; let decision_result = task::spawn_blocking(move || { Confirm::new() .with_prompt(format!("Drop database at {}?", style(&db_url).cyan())) .wait_for_newline(true) .default(false) .show_default(true) .interact() }) .await .expect("Confirm thread panicked"); match decision_result { Ok(decision) => { guard.disarmed = true; decision } Err(dialoguer::Error::IO(err)) if err.kind() == io::ErrorKind::Interrupted => { // Sometimes CTRL + C causes this error to be returned mem::drop(guard); false } Err(err) => { mem::drop(guard); panic!("Confirm dialog failed with {err}") } } } ================================================ FILE: sqlx-cli/src/lib.rs ================================================ //! # SQLx CLI //! //! Command-line utility for the [SQLx](https://github.com/launchbadge/sqlx) ecosystem. //! //! This crate provides the core logic for the `sqlx` command-line interface, enabling database management, //! migrations, and offline query preparation for Rust projects using SQLx. //! //! ### Note: Semver Exempt API //! The API of this crate is not meant for general use and does *not* follow Semantic Versioning. //! The only crate that follows Semantic Versioning in the project is the `sqlx` crate itself. //! If you are building a custom SQLx driver, you should pin an exact version for `sqlx-cli` to //! avoid breakages: //! //! ```toml //! sqlx-cli = { version = "=0.9.0" } //! ``` //! //! And then make releases in lockstep with `sqlx-cli`. We recommend all driver crates, in-tree //! or otherwise, use the same version numbers as `sqlx-cli` to avoid confusion. use std::future::Future; use std::io; use std::time::Duration; use futures_util::TryFutureExt; use sqlx::AnyConnection; use tokio::{select, signal}; use crate::opt::{Command, ConnectOpts, DatabaseCommand, MigrateCommand}; pub mod database; pub mod metadata; // mod migration; // mod migrator; #[cfg(feature = "completions")] pub mod completions; pub mod migrate; pub mod opt; pub mod prepare; pub use crate::opt::Opt; pub use sqlx::_unstable::config::{self, Config}; /// Check arguments for `--no-dotenv` _before_ Clap parsing, and apply `.env` if not set. pub fn maybe_apply_dotenv() { if std::env::args().any(|arg| arg == "--no-dotenv") { return; } if let Err(e) = dotenvy::dotenv() { if !e.not_found() { eprintln!("Warning: error loading `.env` file: {e:?}"); } } } pub async fn run(opt: Opt) -> anyhow::Result<()> { // This `select!` is here so that when the process receives a `SIGINT` (CTRL + C), // the futures currently running on this task get dropped before the program exits. // This is currently necessary for the consumers of the `dialoguer` crate to restore // the user's terminal if the process is interrupted while a dialog is being displayed. let ctrlc_fut = signal::ctrl_c(); let do_run_fut = do_run(opt); select! { biased; _ = ctrlc_fut => { Ok(()) }, do_run_outcome = do_run_fut => { do_run_outcome } } } async fn do_run(opt: Opt) -> anyhow::Result<()> { match opt.command { Command::Migrate(migrate) => match migrate.command { MigrateCommand::Add(opts) => migrate::add(opts).await?, MigrateCommand::Run { source, config, dry_run, ignore_missing, mut connect_opts, target_version, } => { let config = config.load_config().await?; connect_opts.populate_db_url(&config)?; migrate::run( &config, &source, &connect_opts, dry_run, *ignore_missing, target_version, ) .await? } MigrateCommand::Revert { source, config, dry_run, ignore_missing, mut connect_opts, target_version, } => { let config = config.load_config().await?; connect_opts.populate_db_url(&config)?; migrate::revert( &config, &source, &connect_opts, dry_run, *ignore_missing, target_version, ) .await? } MigrateCommand::Info { source, config, mut connect_opts, } => { let config = config.load_config().await?; connect_opts.populate_db_url(&config)?; migrate::info(&config, &source, &connect_opts).await? } MigrateCommand::BuildScript { source, config, force, } => { let config = config.load_config().await?; migrate::build_script(&config, &source, force)? } }, Command::Database(database) => match database.command { DatabaseCommand::Create { config, mut connect_opts, } => { let config = config.load_config().await?; connect_opts.populate_db_url(&config)?; database::create(&connect_opts).await? } DatabaseCommand::Drop { confirmation, config, mut connect_opts, force, } => { let config = config.load_config().await?; connect_opts.populate_db_url(&config)?; database::drop(&connect_opts, !confirmation.yes, force).await? } DatabaseCommand::Reset { confirmation, source, config, mut connect_opts, force, } => { let config = config.load_config().await?; connect_opts.populate_db_url(&config)?; database::reset(&config, &source, &connect_opts, !confirmation.yes, force).await? } DatabaseCommand::Setup { source, config, mut connect_opts, } => { let config = config.load_config().await?; connect_opts.populate_db_url(&config)?; database::setup(&config, &source, &connect_opts).await? } }, Command::Prepare { check, all, workspace, mut connect_opts, args, config, } => { let config = config.load_config().await?; connect_opts.populate_db_url(&config)?; prepare::run(&config, check, all, workspace, connect_opts, args).await? } #[cfg(feature = "completions")] Command::Completions { shell } => completions::run(shell), }; Ok(()) } /// Attempt to connect to the database server, retrying up to `ops.connect_timeout`. async fn connect(config: &Config, opts: &ConnectOpts) -> anyhow::Result { retry_connect_errors(opts, move |url| { AnyConnection::connect_with_driver_config(url, &config.drivers) }) .await } /// Attempt an operation that may return errors like `ConnectionRefused`, /// retrying up until `ops.connect_timeout`. /// /// The closure is passed `&ops.database_url` for easy composition. async fn retry_connect_errors<'a, F, Fut, T>( opts: &'a ConnectOpts, mut connect: F, ) -> anyhow::Result where F: FnMut(&'a str) -> Fut, Fut: Future> + 'a, { let db_url = opts.expect_db_url()?; backoff::future::retry( backoff::ExponentialBackoffBuilder::new() .with_max_elapsed_time(Some(Duration::from_secs(opts.connect_timeout))) .build(), || { connect(db_url).map_err(|e| -> backoff::Error { if let sqlx::Error::Io(ref ioe) = e { match ioe.kind() { io::ErrorKind::ConnectionRefused | io::ErrorKind::ConnectionReset | io::ErrorKind::ConnectionAborted => { return backoff::Error::transient(e.into()); } _ => (), } } backoff::Error::permanent(e.into()) }) }, ) .await } ================================================ FILE: sqlx-cli/src/metadata.rs ================================================ use std::{ collections::{btree_map, BTreeMap, BTreeSet}, ffi::OsStr, path::{Path, PathBuf}, process::Command, str::FromStr, }; use anyhow::Context; use cargo_metadata::{ Metadata as CargoMetadata, Package as MetadataPackage, PackageId as MetadataId, }; /// The minimal amount of package information we care about /// /// The package's `name` is used to `cargo clean -p` specific crates while the `src_paths` are /// are used to trigger recompiles of packages within the workspace #[derive(Debug)] pub struct Package { name: String, src_paths: Vec, } impl Package { pub fn name(&self) -> &str { &self.name } pub fn src_paths(&self) -> &[PathBuf] { &self.src_paths } } impl From<&MetadataPackage> for Package { fn from(package: &MetadataPackage) -> Self { let name = package.name.clone(); let src_paths = package .targets .iter() .map(|target| target.src_path.clone().into_std_path_buf()) .collect(); Self { name, src_paths } } } /// Contains metadata for the current project pub struct Metadata { /// Maps packages metadata id to the package /// /// Currently `MetadataId` is used over `PkgId` because pkgid is not a UUID packages: BTreeMap, /// All of the crates in the current workspace workspace_members: Vec, /// Workspace root path. workspace_root: PathBuf, /// Maps each dependency to its set of dependents reverse_deps: BTreeMap>, /// The target directory of the project /// /// Typically `target` at the workspace root, but can be overridden target_directory: PathBuf, /// Crate in the current working directory, empty if run from a /// virtual workspace root. current_package: Option, } impl Metadata { /// Parse the manifest from the current working directory using `cargo metadata`. pub fn from_current_directory(cargo: &OsStr) -> anyhow::Result { let output = Command::new(cargo) .args(["metadata", "--format-version=1"]) .output() .context("Could not fetch metadata")?; std::str::from_utf8(&output.stdout) .context("Invalid `cargo metadata` output")? .parse() .context("Issue parsing `cargo metadata` output - consider manually running it to check for issues") } pub fn package(&self, id: &MetadataId) -> Option<&Package> { self.packages.get(id) } pub fn entries(&self) -> btree_map::Iter<'_, MetadataId, Package> { self.packages.iter() } pub fn workspace_members(&self) -> &[MetadataId] { &self.workspace_members } pub fn workspace_root(&self) -> &Path { &self.workspace_root } pub fn target_directory(&self) -> &Path { &self.target_directory } pub fn current_package(&self) -> Option<&Package> { self.current_package.as_ref() } /// Gets all dependents (direct and transitive) of `id` pub fn all_dependents_of(&self, id: &MetadataId) -> BTreeSet<&MetadataId> { let mut dependents = BTreeSet::new(); self.all_dependents_of_helper(id, &mut dependents); dependents } fn all_dependents_of_helper<'this>( &'this self, id: &MetadataId, dependents: &mut BTreeSet<&'this MetadataId>, ) { if let Some(immediate_dependents) = self.reverse_deps.get(id) { for immediate_dependent in immediate_dependents { if dependents.insert(immediate_dependent) { self.all_dependents_of_helper(immediate_dependent, dependents); } } } } } impl FromStr for Metadata { type Err = anyhow::Error; fn from_str(s: &str) -> Result { let cargo_metadata: CargoMetadata = serde_json::from_str(s)?; // Extract the package in the current working directory, empty if run from a // virtual workspace root. let current_package: Option = cargo_metadata.root_package().map(Package::from); let CargoMetadata { packages: metadata_packages, workspace_members, workspace_root, resolve, target_directory, .. } = cargo_metadata; let mut packages = BTreeMap::new(); for metadata_package in metadata_packages { let package = Package::from(&metadata_package); packages.insert(metadata_package.id, package); } let mut reverse_deps: BTreeMap<_, BTreeSet<_>> = BTreeMap::new(); let resolve = resolve.context("Resolving the dependency graph failed (old version of cargo)")?; for node in resolve.nodes { for dep in node.deps { let dependent = node.id.clone(); let dependency = dep.pkg; reverse_deps .entry(dependency) .or_default() .insert(dependent); } } let workspace_root = workspace_root.into_std_path_buf(); let target_directory = target_directory.into_std_path_buf(); Ok(Self { packages, workspace_members, workspace_root, reverse_deps, target_directory, current_package, }) } } /// The absolute path to the directory containing the `Cargo.toml` manifest. /// Depends on the current working directory. pub(crate) fn manifest_dir(cargo: &OsStr) -> anyhow::Result { let stdout = Command::new(cargo) .args(["locate-project", "--message-format=plain"]) .output() .context("could not locate manifest directory")? .stdout; let mut manifest_path: PathBuf = std::str::from_utf8(&stdout) .context("output of `cargo locate-project` was not valid UTF-8")? // remove trailing newline .trim() .into(); manifest_path.pop(); Ok(manifest_path) } ================================================ FILE: sqlx-cli/src/migrate.rs ================================================ use crate::config::Config; use crate::opt::{AddMigrationOpts, ConnectOpts, MigrationSourceOpt}; use anyhow::{bail, Context}; use console::style; use sqlx::migrate::{AppliedMigration, Migrate, MigrateError, MigrationType, Migrator}; use sqlx::Connection; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::fmt::Write; use std::fs::{self, File}; use std::path::Path; use std::time::Duration; pub async fn add(opts: AddMigrationOpts) -> anyhow::Result<()> { let config = opts.config.load_config().await?; let source = opts.source.resolve_path(&config); fs::create_dir_all(source).context("Unable to create migrations directory")?; let migrator = opts.source.resolve(&config).await?; let version_prefix = opts.version_prefix(&config, &migrator); if opts.reversible(&config, &migrator) { create_file( source, &version_prefix, &opts.description, MigrationType::ReversibleUp, )?; create_file( source, &version_prefix, &opts.description, MigrationType::ReversibleDown, )?; } else { create_file( source, &version_prefix, &opts.description, MigrationType::Simple, )?; } // if the migrations directory is empty let has_existing_migrations = fs::read_dir(source) .map(|mut dir| dir.next().is_some()) .unwrap_or(false); if !has_existing_migrations { let quoted_source = if opts.source.source.is_some() { format!("{source:?}") } else { "".to_string() }; // Provide a link to the current version in case the details change. // Patch version is deliberately omitted. let version = if let (Some(major), Some(minor)) = ( // Don't fail if we're not being built by Cargo option_env!("CARGO_PKG_VERSION_MAJOR"), option_env!("CARGO_PKG_VERSION_MINOR"), ) { format!("{major}.{minor}") } else { // If a version isn't available, "latest" is fine. "latest".to_string() }; print!( r#" Congratulations on creating your first migration! Did you know you can embed your migrations in your application binary? On startup, after creating your database connection or pool, add: sqlx::migrate!({quoted_source}).run(<&your_pool OR &mut your_connection>).await?; Note that the compiler won't pick up new migrations if no Rust source files have changed. You can create a Cargo build script to work around this with `sqlx migrate build-script`. See: https://docs.rs/sqlx/{version}/sqlx/macro.migrate.html "#, ); } Ok(()) } fn create_file( migration_source: &str, file_prefix: &str, description: &str, migration_type: MigrationType, ) -> anyhow::Result<()> { use std::path::PathBuf; let mut file_name = file_prefix.to_string(); file_name.push('_'); file_name.push_str(&description.replace(' ', "_")); file_name.push_str(migration_type.suffix()); let mut path = PathBuf::new(); path.push(migration_source); path.push(&file_name); println!("Creating {}", style(path.display()).cyan()); let mut file = File::create(&path).context("Failed to create migration file")?; std::io::Write::write_all(&mut file, migration_type.file_content().as_bytes())?; Ok(()) } fn short_checksum(checksum: &[u8]) -> String { let mut s = String::with_capacity(checksum.len() * 2); for b in checksum { write!(&mut s, "{b:02x?}").expect("should not fail to write to str"); } s } pub async fn info( config: &Config, migration_source: &MigrationSourceOpt, connect_opts: &ConnectOpts, ) -> anyhow::Result<()> { let migrator = migration_source.resolve(config).await?; let mut conn = crate::connect(config, connect_opts).await?; // FIXME: we shouldn't actually be creating anything here for schema_name in &config.migrate.create_schemas { conn.create_schema_if_not_exists(schema_name).await?; } conn.ensure_migrations_table(config.migrate.table_name()) .await?; let applied_migrations: HashMap<_, _> = conn .list_applied_migrations(config.migrate.table_name()) .await? .into_iter() .map(|m| (m.version, m)) .collect(); for migration in migrator.iter() { if migration.migration_type.is_down_migration() { // Skipping down migrations continue; } let applied = applied_migrations.get(&migration.version); let (status_msg, mismatched_checksum) = if let Some(applied) = applied { if applied.checksum != migration.checksum { (style("installed (different checksum)").red(), true) } else { (style("installed").green(), false) } } else { (style("pending").yellow(), false) }; println!( "{}/{} {}", style(migration.version).cyan(), status_msg, migration.description ); if mismatched_checksum { println!( "applied migration had checksum {}", short_checksum( &applied .map(|a| a.checksum.clone()) .unwrap_or_else(|| Cow::Owned(vec![])) ), ); println!( "local migration has checksum {}", short_checksum(&migration.checksum) ) } } let _ = conn.close().await; Ok(()) } fn validate_applied_migrations( applied_migrations: &[AppliedMigration], migrator: &Migrator, ignore_missing: bool, ) -> Result<(), MigrateError> { if ignore_missing { return Ok(()); } let migrations: HashSet<_> = migrator.iter().map(|m| m.version).collect(); for applied_migration in applied_migrations { if !migrations.contains(&applied_migration.version) { return Err(MigrateError::VersionMissing(applied_migration.version)); } } Ok(()) } pub async fn run( config: &Config, migration_source: &MigrationSourceOpt, connect_opts: &ConnectOpts, dry_run: bool, ignore_missing: bool, target_version: Option, ) -> anyhow::Result<()> { let migrator = migration_source.resolve(config).await?; if let Some(target_version) = target_version { if !migrator.version_exists(target_version) { bail!(MigrateError::VersionNotPresent(target_version)); } } let mut conn = crate::connect(config, connect_opts).await?; for schema_name in &config.migrate.create_schemas { conn.create_schema_if_not_exists(schema_name).await?; } conn.ensure_migrations_table(config.migrate.table_name()) .await?; let version = conn.dirty_version(config.migrate.table_name()).await?; if let Some(version) = version { bail!(MigrateError::Dirty(version)); } let applied_migrations = conn .list_applied_migrations(config.migrate.table_name()) .await?; validate_applied_migrations(&applied_migrations, &migrator, ignore_missing)?; let latest_version = applied_migrations .iter() .max_by(|x, y| x.version.cmp(&y.version)) .map(|migration| migration.version) .unwrap_or(0); if let Some(target_version) = target_version { if target_version < latest_version { bail!(MigrateError::VersionTooOld(target_version, latest_version)); } } let applied_migrations: HashMap<_, _> = applied_migrations .into_iter() .map(|m| (m.version, m)) .collect(); for migration in migrator.iter() { if migration.migration_type.is_down_migration() { // Skipping down migrations continue; } match applied_migrations.get(&migration.version) { Some(applied_migration) => { if migration.checksum != applied_migration.checksum { bail!(MigrateError::VersionMismatch(migration.version)); } } None => { let skip = target_version.is_some_and(|target_version| migration.version > target_version); let elapsed = if dry_run || skip { Duration::new(0, 0) } else { conn.apply(config.migrate.table_name(), migration).await? }; let text = if skip { "Skipped" } else if dry_run { "Can apply" } else { "Applied" }; println!( "{} {}/{} {} {}", text, style(migration.version).cyan(), style(migration.migration_type.label()).green(), migration.description, style(format!("({elapsed:?})")).dim() ); } } } // Close the connection before exiting: // * For MySQL and Postgres this should ensure timely cleanup on the server side, // including decrementing the open connection count. // * For SQLite this should checkpoint and delete the WAL file to ensure the migrations // were actually applied to the database file and aren't just sitting in the WAL file. let _ = conn.close().await; Ok(()) } pub async fn revert( config: &Config, migration_source: &MigrationSourceOpt, connect_opts: &ConnectOpts, dry_run: bool, ignore_missing: bool, target_version: Option, ) -> anyhow::Result<()> { let migrator = migration_source.resolve(config).await?; if let Some(target_version) = target_version { if target_version != 0 && !migrator.version_exists(target_version) { bail!(MigrateError::VersionNotPresent(target_version)); } } let mut conn = crate::connect(config, connect_opts).await?; // FIXME: we should not be creating anything here if it doesn't exist for schema_name in &config.migrate.create_schemas { conn.create_schema_if_not_exists(schema_name).await?; } conn.ensure_migrations_table(config.migrate.table_name()) .await?; let version = conn.dirty_version(config.migrate.table_name()).await?; if let Some(version) = version { bail!(MigrateError::Dirty(version)); } let applied_migrations = conn .list_applied_migrations(config.migrate.table_name()) .await?; validate_applied_migrations(&applied_migrations, &migrator, ignore_missing)?; let latest_version = applied_migrations .iter() .max_by(|x, y| x.version.cmp(&y.version)) .map(|migration| migration.version) .unwrap_or(0); if let Some(target_version) = target_version { if target_version > latest_version { bail!(MigrateError::VersionTooNew(target_version, latest_version)); } } let applied_migrations: HashMap<_, _> = applied_migrations .into_iter() .map(|m| (m.version, m)) .collect(); let mut is_applied = false; for migration in migrator.iter().rev() { if !migration.migration_type.is_down_migration() { // Skipping non down migration // This will skip any simple or up migration file continue; } if applied_migrations.contains_key(&migration.version) { let skip = target_version.is_some_and(|target_version| migration.version <= target_version); let elapsed = if dry_run || skip { Duration::new(0, 0) } else { conn.revert(config.migrate.table_name(), migration).await? }; let text = if skip { "Skipped" } else if dry_run { "Can apply" } else { "Applied" }; println!( "{} {}/{} {} {}", text, style(migration.version).cyan(), style(migration.migration_type.label()).green(), migration.description, style(format!("({elapsed:?})")).dim() ); is_applied = true; // Only a single migration will be reverted at a time if no target // version is supplied, so we break. if target_version.is_none() { break; } } } if !is_applied { println!("No migrations available to revert"); } let _ = conn.close().await; Ok(()) } pub fn build_script( config: &Config, migration_source: &MigrationSourceOpt, force: bool, ) -> anyhow::Result<()> { let source = migration_source.resolve_path(config); anyhow::ensure!( Path::new("Cargo.toml").exists(), "must be run in a Cargo project root" ); anyhow::ensure!( (force || !Path::new("build.rs").exists()), "build.rs already exists; use --force to overwrite" ); let contents = format!( r#"// generated by `sqlx migrate build-script` fn main() {{ // trigger recompilation when a new migration is added println!("cargo:rerun-if-changed={source}"); }} "#, ); fs::write("build.rs", contents)?; println!("Created `build.rs`; be sure to check it into version control!"); Ok(()) } ================================================ FILE: sqlx-cli/src/opt.rs ================================================ use crate::config::migrate::{DefaultMigrationType, DefaultVersioning}; use crate::config::Config; use anyhow::Context; use chrono::Utc; use clap::{ builder::{styling::AnsiColor, Styles}, Args, Parser, }; #[cfg(feature = "completions")] use clap_complete::Shell; use sqlx::migrate::{MigrateError, Migrator, ResolveWith}; use std::env; use std::ops::{Deref, Not}; use std::path::PathBuf; const HELP_STYLES: Styles = Styles::styled() .header(AnsiColor::Blue.on_default().bold()) .usage(AnsiColor::Blue.on_default().bold()) .literal(AnsiColor::White.on_default()) .placeholder(AnsiColor::Green.on_default()); #[derive(Parser, Debug)] #[clap(version, about, author, styles = HELP_STYLES)] pub struct Opt { // https://github.com/launchbadge/sqlx/pull/3724 placed this here, // but the intuitive place would be in the arguments for each subcommand. #[clap(flatten)] pub no_dotenv: NoDotenvOpt, #[clap(subcommand)] pub command: Command, } #[derive(Parser, Debug)] pub enum Command { #[clap(alias = "db")] Database(DatabaseOpt), /// Generate query metadata to support offline compile-time verification. /// /// Saves metadata for all invocations of `query!` and related macros to a `.sqlx` directory /// in the current directory (or workspace root with `--workspace`), overwriting if needed. /// /// During project compilation, the absence of the `DATABASE_URL` environment variable or /// the presence of `SQLX_OFFLINE` (with a value of `true` or `1`) will constrain the /// compile-time verification to only read from the cached query metadata. #[clap(alias = "prep")] Prepare { /// Run in 'check' mode. Exits with 0 if the query metadata is up-to-date. Exits with /// 1 if the query metadata needs updating. #[clap(long)] check: bool, /// Prepare query macros in dependencies that exist outside the current crate or workspace. #[clap(long)] all: bool, /// Generate a single workspace-level `.sqlx` folder. /// /// This option is intended for workspaces where multiple crates use SQLx. If there is only /// one, it is better to run `cargo sqlx prepare` without this option inside that crate. #[clap(long)] workspace: bool, /// Arguments to be passed to `cargo rustc ...`. #[clap(last = true)] args: Vec, #[clap(flatten)] connect_opts: ConnectOpts, #[clap(flatten)] config: ConfigOpt, }, #[clap(alias = "mig")] Migrate(MigrateOpt), #[cfg(feature = "completions")] /// Generate shell completions for the specified shell Completions { shell: Shell }, } /// Group of commands for creating and dropping your database. #[derive(Parser, Debug)] pub struct DatabaseOpt { #[clap(subcommand)] pub command: DatabaseCommand, } #[derive(Parser, Debug)] pub enum DatabaseCommand { /// Creates the database specified in your DATABASE_URL. Create { #[clap(flatten)] connect_opts: ConnectOpts, #[clap(flatten)] config: ConfigOpt, }, /// Drops the database specified in your DATABASE_URL. Drop { #[clap(flatten)] confirmation: Confirmation, #[clap(flatten)] config: ConfigOpt, #[clap(flatten)] connect_opts: ConnectOpts, /// PostgreSQL only: force drops the database. #[clap(long, short, default_value = "false")] force: bool, }, /// Drops the database specified in your DATABASE_URL, re-creates it, and runs any pending migrations. Reset { #[clap(flatten)] confirmation: Confirmation, #[clap(flatten)] source: MigrationSourceOpt, #[clap(flatten)] config: ConfigOpt, #[clap(flatten)] connect_opts: ConnectOpts, /// PostgreSQL only: force drops the database. #[clap(long, short, default_value = "false")] force: bool, }, /// Creates the database specified in your DATABASE_URL and runs any pending migrations. Setup { #[clap(flatten)] source: MigrationSourceOpt, #[clap(flatten)] config: ConfigOpt, #[clap(flatten)] connect_opts: ConnectOpts, }, } /// Group of commands for creating and running migrations. #[derive(Parser, Debug)] pub struct MigrateOpt { #[clap(subcommand)] pub command: MigrateCommand, } #[derive(Parser, Debug)] pub enum MigrateCommand { /// Create a new migration with the given description. /// /// -------------------------------- /// /// Migrations may either be simple, or reversible. /// /// Reversible migrations can be reverted with `sqlx migrate revert`, simple migrations cannot. /// /// Reversible migrations are created as a pair of two files with the same filename but /// extensions `.up.sql` and `.down.sql` for the up-migration and down-migration, respectively. /// /// The up-migration should contain the commands to be used when applying the migration, /// while the down-migration should contain the commands to reverse the changes made by the /// up-migration. /// /// When writing down-migrations, care should be taken to ensure that they /// do not leave the database in an inconsistent state. /// /// Simple migrations have just `.sql` for their extension and represent an up-migration only. /// /// Note that reverting a migration is **destructive** and will likely result in data loss. /// Reverting a migration will not restore any data discarded by commands in the up-migration. /// /// It is recommended to always back up the database before running migrations. /// /// -------------------------------- /// /// For convenience, this command attempts to detect if reversible migrations are in-use. /// /// If the latest existing migration is reversible, the new migration will also be reversible. /// /// Otherwise, a simple migration is created. /// /// This behavior can be overridden by `--simple` or `--reversible`, respectively. /// /// The default type to use can also be set in `sqlx.toml`. /// /// -------------------------------- /// /// A version number will be automatically assigned to the migration. /// /// Migrations are applied in ascending order by version number. /// Version numbers do not need to be strictly consecutive. /// /// The migration process will abort if SQLx encounters a migration with a version number /// less than _any_ previously applied migration. /// /// Migrations should only be created with increasing version number. /// /// -------------------------------- /// /// For convenience, this command will attempt to detect if sequential versioning is in use, /// and if so, continue the sequence. /// /// Sequential versioning is inferred if: /// /// * The version numbers of the last two migrations differ by exactly 1, or: /// /// * only one migration exists and its version number is either 0 or 1. /// /// Otherwise, timestamp versioning (`YYYYMMDDHHMMSS`) is assumed. /// /// This behavior can be overridden by `--timestamp` or `--sequential`, respectively. /// /// The default versioning to use can also be set in `sqlx.toml`. Add(AddMigrationOpts), /// Run all pending migrations. Run { #[clap(flatten)] source: MigrationSourceOpt, #[clap(flatten)] config: ConfigOpt, /// List all the migrations to be run without applying #[clap(long)] dry_run: bool, #[clap(flatten)] ignore_missing: IgnoreMissing, #[clap(flatten)] connect_opts: ConnectOpts, /// Apply migrations up to the specified version. If unspecified, apply all /// pending migrations. If already at the target version, then no-op. #[clap(long)] target_version: Option, }, /// Revert the latest migration with a down file. Revert { #[clap(flatten)] source: MigrationSourceOpt, #[clap(flatten)] config: ConfigOpt, /// List the migration to be reverted without applying #[clap(long)] dry_run: bool, #[clap(flatten)] ignore_missing: IgnoreMissing, #[clap(flatten)] connect_opts: ConnectOpts, /// Revert migrations down to the specified version. If unspecified, revert /// only the last migration. Set to 0 to revert all migrations. If already /// at the target version, then no-op. #[clap(long)] target_version: Option, }, /// List all available migrations. Info { #[clap(flatten)] source: MigrationSourceOpt, #[clap(flatten)] config: ConfigOpt, #[clap(flatten)] connect_opts: ConnectOpts, }, /// Generate a `build.rs` to trigger recompilation when a new migration is added. /// /// Must be run in a Cargo project root. BuildScript { #[clap(flatten)] source: MigrationSourceOpt, #[clap(flatten)] config: ConfigOpt, /// Overwrite the build script if it already exists. #[clap(long)] force: bool, }, } #[derive(Args, Debug)] pub struct AddMigrationOpts { pub description: String, #[clap(flatten)] pub source: MigrationSourceOpt, #[clap(flatten)] pub config: ConfigOpt, /// If set, create an up-migration only. Conflicts with `--reversible`. #[clap(long, conflicts_with = "reversible")] simple: bool, /// If set, create a pair of up and down migration files with same version. /// /// Conflicts with `--simple`. #[clap(short, long, conflicts_with = "simple")] reversible: bool, /// If set, use timestamp versioning for the new migration. Conflicts with `--sequential`. /// /// Timestamp format: `YYYYMMDDHHMMSS` #[clap(short, long, conflicts_with = "sequential")] timestamp: bool, /// If set, use sequential versioning for the new migration. Conflicts with `--timestamp`. #[clap(short, long, conflicts_with = "timestamp")] sequential: bool, } /// Argument for the migration scripts source. #[derive(Args, Debug)] pub struct MigrationSourceOpt { /// Path to folder containing migrations. /// /// Defaults to `migrations/` if not specified, but a different default may be set by `sqlx.toml`. #[clap(long)] pub source: Option, } impl MigrationSourceOpt { pub fn resolve_path<'a>(&'a self, config: &'a Config) -> &'a str { if let Some(source) = &self.source { return source; } config.migrate.migrations_dir() } pub async fn resolve(&self, config: &Config) -> Result { Migrator::new(ResolveWith( self.resolve_path(config), config.migrate.to_resolve_config(), )) .await } } /// Argument for the database URL. #[derive(Args, Debug)] pub struct ConnectOpts { #[clap(flatten)] pub no_dotenv: NoDotenvOpt, /// Location of the DB, by default will be read from the DATABASE_URL env var or `.env` files. #[clap(long, short = 'D')] pub database_url: Option, /// The maximum time, in seconds, to try connecting to the database server before /// returning an error. #[clap(long, default_value = "10")] pub connect_timeout: u64, /// Set whether or not to create SQLite databases in Write-Ahead Log (WAL) mode: /// https://www.sqlite.org/wal.html /// /// WAL mode is enabled by default for SQLite databases created by `sqlx-cli`. /// /// However, if your application sets a `journal_mode` on `SqliteConnectOptions` to something /// other than `Wal`, then it will have to take the database file out of WAL mode on connecting, /// which requires an exclusive lock and may return a `database is locked` (`SQLITE_BUSY`) error. #[cfg(feature = "_sqlite")] #[clap(long, action = clap::ArgAction::Set, default_value = "true")] pub sqlite_create_db_wal: bool, } #[derive(Args, Debug)] pub struct NoDotenvOpt { /// Do not automatically load `.env` files. #[clap(long)] // Parsing of this flag is actually handled _before_ calling Clap, // by `crate::maybe_apply_dotenv()`. #[allow(unused)] // TODO: switch to `#[expect]` pub no_dotenv: bool, } #[derive(Args, Debug)] pub struct ConfigOpt { /// Override the path to the config file. /// /// Defaults to `sqlx.toml` in the current directory, if it exists. /// /// Configuration file loading may be bypassed with `--config=/dev/null` on Linux, /// or `--config=NUL` on Windows. /// /// Config file loading is enabled by the `sqlx-toml` feature. #[clap(long)] pub config: Option, } impl ConnectOpts { /// Require a database URL to be provided, otherwise /// return an error. pub fn expect_db_url(&self) -> anyhow::Result<&str> { self.database_url .as_deref() .context("BUG: database_url not populated") } /// Populate `database_url` from the environment, if not set. pub fn populate_db_url(&mut self, config: &Config) -> anyhow::Result<()> { if self.database_url.is_some() { return Ok(()); } let var = config.common.database_url_var(); let context = if var != "DATABASE_URL" { " (`common.database-url-var` in `sqlx.toml`)" } else { "" }; match env::var(var) { Ok(url) => { if !context.is_empty() { eprintln!("Read database url from `{var}`{context}"); } self.database_url = Some(url) } Err(env::VarError::NotPresent) => { anyhow::bail!("`--database-url` or `{var}`{context} must be set") } Err(env::VarError::NotUnicode(_)) => { anyhow::bail!("`{var}`{context} is not valid UTF-8"); } } Ok(()) } } impl ConfigOpt { pub async fn load_config(&self) -> anyhow::Result { let path = self.config.clone(); // Tokio does file I/O on a background task anyway tokio::task::spawn_blocking(|| { if let Some(path) = path { let err_str = format!("error reading config from {path:?}"); Config::try_from_path(path).context(err_str) } else { let path = PathBuf::from("sqlx.toml"); if path.exists() { eprintln!("Found `sqlx.toml` in current directory; reading..."); Ok(Config::try_from_path(path)?) } else { Ok(Config::default()) } } }) .await .context("unexpected error loading config")? } } /// Argument for automatic confirmation. #[derive(Args, Copy, Clone, Debug)] pub struct Confirmation { /// Automatic confirmation. Without this option, you will be prompted before dropping /// your database. #[clap(short)] pub yes: bool, } /// Argument for ignoring applied migrations that were not resolved. #[derive(Args, Copy, Clone, Debug)] pub struct IgnoreMissing { /// Ignore applied migrations that are missing in the resolved migrations #[clap(long)] ignore_missing: bool, } impl Deref for IgnoreMissing { type Target = bool; fn deref(&self) -> &Self::Target { &self.ignore_missing } } impl Not for IgnoreMissing { type Output = bool; fn not(self) -> Self::Output { !self.ignore_missing } } impl AddMigrationOpts { pub fn reversible(&self, config: &Config, migrator: &Migrator) -> bool { if self.reversible { return true; } if self.simple { return false; } match config.migrate.defaults.migration_type { DefaultMigrationType::Inferred => migrator .iter() .last() .is_some_and(|m| m.migration_type.is_reversible()), DefaultMigrationType::Simple => false, DefaultMigrationType::Reversible => true, } } pub fn version_prefix(&self, config: &Config, migrator: &Migrator) -> String { let default_versioning = &config.migrate.defaults.migration_versioning; match (self.timestamp, self.sequential, default_versioning) { (true, false, _) | (false, false, DefaultVersioning::Timestamp) => next_timestamp(), (false, true, _) | (false, false, DefaultVersioning::Sequential) => fmt_sequential( migrator .migrations .last() .map_or(1, |migration| migration.version + 1), ), (false, false, DefaultVersioning::Inferred) => { migrator .migrations .rchunks(2) .next() .and_then(|migrations| { match migrations { [previous, latest] => { // If the latest two versions differ by 1, infer sequential. (latest.version - previous.version == 1) .then_some(latest.version + 1) } [latest] => { // If only one migration exists and its version is 0 or 1, infer sequential matches!(latest.version, 0 | 1).then_some(latest.version + 1) } _ => unreachable!(), } }) .map_or_else(next_timestamp, fmt_sequential) } (true, true, _) => unreachable!("BUG: Clap should have rejected this case"), } } } fn next_timestamp() -> String { Utc::now().format("%Y%m%d%H%M%S").to_string() } fn fmt_sequential(version: i64) -> String { format!("{version:04}") } ================================================ FILE: sqlx-cli/src/prepare.rs ================================================ use std::collections::{BTreeSet, HashSet}; use std::env; use std::ffi::{OsStr, OsString}; use std::fs; use std::path::{Path, PathBuf}; use std::process::Command; use crate::metadata::{manifest_dir, Metadata}; use crate::opt::ConnectOpts; use crate::Config; use anyhow::{bail, Context}; use console::style; use sqlx::Connection; pub struct PrepareCtx<'a> { pub config: &'a Config, pub workspace: bool, pub all: bool, pub cargo: OsString, pub cargo_args: Vec, pub metadata: Metadata, pub connect_opts: ConnectOpts, } impl PrepareCtx<'_> { /// Path to the directory where cached queries should be placed. fn prepare_dir(&self) -> anyhow::Result { if self.workspace { Ok(self.metadata.workspace_root().join(".sqlx")) } else { Ok(manifest_dir(&self.cargo)?.join(".sqlx")) } } } pub async fn run( config: &Config, check: bool, all: bool, workspace: bool, connect_opts: ConnectOpts, cargo_args: Vec, ) -> anyhow::Result<()> { let cargo = env::var_os("CARGO") .context("failed to get value of `CARGO`; `prepare` subcommand may only be invoked as `cargo sqlx prepare`")?; anyhow::ensure!( Path::new("Cargo.toml").exists(), r#"Failed to read `Cargo.toml`. hint: This command only works in the manifest directory of a Cargo package or workspace."# ); let metadata: Metadata = Metadata::from_current_directory(&cargo)?; let ctx = PrepareCtx { config, workspace, all, cargo, cargo_args, metadata, connect_opts, }; if check { prepare_check(&ctx).await } else { prepare(&ctx).await } } async fn prepare(ctx: &PrepareCtx<'_>) -> anyhow::Result<()> { if ctx.connect_opts.database_url.is_some() { check_backend(ctx.config, &ctx.connect_opts).await?; } let prepare_dir = ctx.prepare_dir()?; run_prepare_step(ctx, &prepare_dir)?; // Warn if no queries were generated. Glob since the directory may contain unrelated files. if glob_query_files(prepare_dir)?.is_empty() { println!("{} no queries found", style("warning:").yellow()); return Ok(()); } if ctx.workspace { println!( "query data written to .sqlx in the workspace root; \ please check this into version control" ); } else { println!( "query data written to .sqlx in the current directory; \ please check this into version control" ); } Ok(()) } async fn prepare_check(ctx: &PrepareCtx<'_>) -> anyhow::Result<()> { if ctx.connect_opts.database_url.is_some() { check_backend(ctx.config, &ctx.connect_opts).await?; } // Re-generate and store the queries in a separate directory from both the prepared // queries and the ones generated by `cargo check`, to avoid conflicts. let prepare_dir = ctx.prepare_dir()?; let cache_dir = ctx.metadata.target_directory().join("sqlx-prepare-check"); run_prepare_step(ctx, &cache_dir)?; // Compare .sqlx to cache. let prepare_filenames: HashSet = glob_query_files(&prepare_dir)? .into_iter() .filter_map(|path| path.file_name().map(|f| f.to_string_lossy().into_owned())) .collect(); let cache_filenames: HashSet = glob_query_files(&cache_dir)? .into_iter() .filter_map(|path| path.file_name().map(|f| f.to_string_lossy().into_owned())) .collect(); // Error: files in cache but not .sqlx. if cache_filenames .difference(&prepare_filenames) .next() .is_some() { bail!("prepare check failed: .sqlx is missing one or more queries; you should re-run sqlx prepare"); } // Warn: files in .sqlx but not cache. if prepare_filenames .difference(&cache_filenames) .next() .is_some() { println!( "{} potentially unused queries found in .sqlx; you may want to re-run sqlx prepare", style("warning:").yellow() ); } // Compare file contents as JSON to ignore superficial differences. // Everything in cache checked to be in .sqlx already. for filename in cache_filenames { let prepare_json = load_json_file(prepare_dir.join(&filename))?; let cache_json = load_json_file(cache_dir.join(&filename))?; if prepare_json != cache_json { bail!("prepare check failed: one or more query files differ ({}); you should re-run sqlx prepare", filename); } } Ok(()) } fn run_prepare_step(ctx: &PrepareCtx, cache_dir: &Path) -> anyhow::Result<()> { // Create and/or clean the directory. fs::create_dir_all(cache_dir).context(format!( "Failed to create query cache directory: {:?}", cache_dir ))?; // Create directory to hold temporary query files before they get persisted to SQLX_OFFLINE_DIR let tmp_dir = ctx.metadata.target_directory().join("sqlx-tmp"); fs::create_dir_all(&tmp_dir).context(format!( "Failed to create temporary query cache directory: {:?}", cache_dir ))?; // Only delete sqlx-*.json files to avoid accidentally deleting any user data. for query_file in glob_query_files(cache_dir).context("Failed to read query cache files")? { fs::remove_file(&query_file) .with_context(|| format!("Failed to delete query file: {}", query_file.display()))?; } // Try only triggering a recompile on crates that use `sqlx-macros` falling back to a full // clean on error setup_minimal_project_recompile(&ctx.cargo, &ctx.metadata, ctx.all, ctx.workspace)?; // Compile the queries. let check_status = { let mut check_command = Command::new(&ctx.cargo); check_command .arg("check") .args(&ctx.cargo_args) .env("SQLX_TMP", tmp_dir) .env("SQLX_OFFLINE", "false") .env("SQLX_OFFLINE_DIR", cache_dir); if let Some(database_url) = &ctx.connect_opts.database_url { check_command.env("DATABASE_URL", database_url); } // `cargo check` recompiles on changed rust flags which can be set either via the env var // or through the `rustflags` field in `$CARGO_HOME/config` when the env var isn't set. // Because of this we only pass in `$RUSTFLAGS` when present. if let Ok(rustflags) = env::var("RUSTFLAGS") { check_command.env("RUSTFLAGS", rustflags); } check_command.status()? }; if !check_status.success() { bail!("`cargo check` failed with status: {}", check_status); } Ok(()) } #[derive(Debug, PartialEq)] struct ProjectRecompileAction { // The names of the packages clean_packages: Vec, touch_paths: Vec, } /// Sets up recompiling only crates that depend on `sqlx-macros` /// /// This gets a listing of all crates that depend on `sqlx-macros` (direct and transitive). The /// crates within the current workspace have their source file's mtimes updated while crates /// outside the workspace are selectively `cargo clean -p`ed. In this way we can trigger a /// recompile of crates that may be using compile-time macros without forcing a full recompile. /// /// If `workspace` is false, only the current package will have its files' mtimes updated. fn setup_minimal_project_recompile( cargo: impl AsRef, metadata: &Metadata, all: bool, workspace: bool, ) -> anyhow::Result<()> { let recompile_action: ProjectRecompileAction = if workspace { minimal_project_recompile_action(metadata, all) } else { // Only touch the current crate. ProjectRecompileAction { clean_packages: Vec::new(), touch_paths: metadata.current_package() .context("failed to get package in current working directory, pass `--workspace` if running from a workspace root")? .src_paths() .to_vec(), } }; if let Err(err) = minimal_project_clean(&cargo, recompile_action) { println!( "Failed minimal recompile setup. Cleaning entire project. Err: {}", err ); let clean_status = Command::new(&cargo).arg("clean").status()?; if !clean_status.success() { bail!("`cargo clean` failed with status: {}", clean_status); } } Ok(()) } fn minimal_project_clean( cargo: impl AsRef, action: ProjectRecompileAction, ) -> anyhow::Result<()> { let ProjectRecompileAction { clean_packages, touch_paths, } = action; // Update the modified timestamp of package files to force a selective recompilation. for file in touch_paths { let now = filetime::FileTime::now(); filetime::set_file_times(&file, now, now) .with_context(|| format!("Failed to update mtime for {file:?}"))?; } // Clean entire packages. for pkg_id in &clean_packages { let clean_status = Command::new(&cargo) .args(["clean", "-p", pkg_id]) .status()?; if !clean_status.success() { bail!("`cargo clean -p {}` failed", pkg_id); } } Ok(()) } fn minimal_project_recompile_action(metadata: &Metadata, all: bool) -> ProjectRecompileAction { // Get all the packages that depend on `sqlx-macros` let mut sqlx_macros_dependents = BTreeSet::new(); let sqlx_macros_ids: BTreeSet<_> = metadata .entries() // We match just by name instead of name and url because some people may have it installed // through different means like vendoring .filter(|(_, package)| package.name() == "sqlx-macros") .map(|(id, _)| id) .collect(); for sqlx_macros_id in sqlx_macros_ids { sqlx_macros_dependents.extend(metadata.all_dependents_of(sqlx_macros_id)); } // Figure out which `sqlx-macros` dependents are in the workspace vs out let mut in_workspace_dependents = Vec::new(); let mut out_of_workspace_dependents = Vec::new(); for dependent in sqlx_macros_dependents { if metadata.workspace_members().contains(dependent) { in_workspace_dependents.push(dependent); } else { out_of_workspace_dependents.push(dependent); } } // In-workspace dependents have their source file's mtime updated. let files_to_touch: Vec<_> = in_workspace_dependents .iter() .filter_map(|id| { metadata .package(id) .map(|package| package.src_paths().to_owned()) }) .flatten() .collect(); // Out-of-workspace get `cargo clean -p `ed, only if --all is set. let packages_to_clean: Vec<_> = if all { out_of_workspace_dependents .iter() .filter_map(|id| { metadata .package(id) .map(|package| package.name().to_owned()) }) // Do not clean sqlx, it depends on sqlx-macros but has no queries to prepare itself. .filter(|name| name != "sqlx") .collect() } else { Vec::new() }; ProjectRecompileAction { clean_packages: packages_to_clean, touch_paths: files_to_touch, } } /// Find all `query-*.json` files in a directory. fn glob_query_files(path: impl AsRef) -> anyhow::Result> { let path = path.as_ref(); let pattern = path.join("query-*.json"); glob::glob( pattern .to_str() .context("query cache path is invalid UTF-8")?, ) .with_context(|| format!("failed to read query cache path: {}", path.display()))? .collect::, _>>() .context("glob failed") } /// Load the JSON contents of a query data file. fn load_json_file(path: impl AsRef) -> anyhow::Result { let path = path.as_ref(); let file_bytes = fs::read(path).with_context(|| format!("failed to load file: {}", path.display()))?; Ok(serde_json::from_slice(&file_bytes)?) } async fn check_backend(config: &Config, opts: &ConnectOpts) -> anyhow::Result<()> { crate::connect(config, opts).await?.close().await?; Ok(()) } #[cfg(test)] mod tests { use super::*; use std::assert_eq; #[test] fn minimal_project_recompile_action_works() -> anyhow::Result<()> { let sample_metadata_path = Path::new("tests") .join("assets") .join("sample_metadata.json"); let sample_metadata = std::fs::read_to_string(sample_metadata_path)?; let metadata: Metadata = sample_metadata.parse()?; let action = minimal_project_recompile_action(&metadata, false); assert_eq!( action, ProjectRecompileAction { clean_packages: vec![], touch_paths: vec![ "/home/user/problematic/workspace/b_in_workspace_lib/src/lib.rs".into(), "/home/user/problematic/workspace/c_in_workspace_bin/src/main.rs".into(), ], } ); Ok(()) } } ================================================ FILE: sqlx-cli/tests/add.rs ================================================ use anyhow::Context; use assert_cmd::cargo_bin_cmd; use std::cmp::Ordering; use std::fs::read_dir; use std::ops::Index; use std::path::{Path, PathBuf}; use tempfile::TempDir; #[derive(Debug, PartialEq, Eq)] struct FileName { id: u64, description: String, suffix: String, } impl PartialOrd for FileName { fn partial_cmp(&self, other: &Self) -> Option { if self.id != other.id { self.id.partial_cmp(&other.id) } else { self.suffix.partial_cmp(&other.suffix) } } } impl FileName { fn assert_is_timestamp(&self) { assert!( self.id > 20200101000000, "{self:?} is too low for a timestamp" ); } } impl From for FileName { fn from(path: PathBuf) -> Self { let filename = path.file_name().unwrap().to_string_lossy(); let (id, rest) = filename.split_once("_").unwrap(); let id: u64 = id.parse().unwrap(); let (description, suffix) = rest.split_once(".").unwrap(); Self { id, description: description.to_string(), suffix: suffix.to_string(), } } } struct AddMigrationsResult(Vec); impl AddMigrationsResult { fn len(&self) -> usize { self.0.len() } fn assert_is_reversible(&self) { let mut up_cnt = 0; let mut down_cnt = 0; for file in self.0.iter() { if file.suffix == "down.sql" { down_cnt += 1; } else if file.suffix == "up.sql" { up_cnt += 1; } else { panic!("unknown suffix for {file:?}"); } assert!(file.description.starts_with("hello_world")); } assert_eq!(up_cnt, down_cnt); } fn assert_is_not_reversible(&self) { for file in self.0.iter() { assert_eq!(file.suffix, "sql"); assert!(file.description.starts_with("hello_world")); } } } impl Index for AddMigrationsResult { type Output = FileName; fn index(&self, index: usize) -> &Self::Output { &self.0[index] } } struct AddMigrations { tempdir: TempDir, config_arg: Option, } impl AddMigrations { fn new() -> anyhow::Result { anyhow::Ok(Self { tempdir: TempDir::new()?, config_arg: None, }) } fn with_config(mut self, filename: &str) -> anyhow::Result { let path = format!("./tests/assets/{filename}"); let path = std::fs::canonicalize(&path) .with_context(|| format!("error canonicalizing path {path:?}"))?; let path = path .to_str() .with_context(|| format!("canonicalized version of path {path:?} is not UTF-8"))?; self.config_arg = Some(format!("--config={path}")); Ok(self) } fn run( &self, description: &str, revesible: bool, timestamp: bool, sequential: bool, expect_success: bool, ) -> anyhow::Result<&'_ Self> { let cmd_result = cargo_bin_cmd!("cargo-sqlx") .current_dir(&self.tempdir) .args( [ vec!["sqlx", "migrate", "add", description], self.config_arg.as_deref().map_or(vec![], |arg| vec![arg]), match revesible { true => vec!["-r"], false => vec![], }, match timestamp { true => vec!["--timestamp"], false => vec![], }, match sequential { true => vec!["--sequential"], false => vec![], }, ] .concat(), ) .env("RUST_BACKTRACE", "1") .assert(); if expect_success { cmd_result.success(); } else { cmd_result.failure(); } anyhow::Ok(self) } fn fs_output(&self) -> anyhow::Result { let files = recurse_files(&self.tempdir)?; let mut fs_paths = Vec::with_capacity(files.len()); for path in files { let relative_path = path.strip_prefix(self.tempdir.path())?.to_path_buf(); fs_paths.push(FileName::from(relative_path)); } Ok(AddMigrationsResult(fs_paths)) } } fn recurse_files(path: impl AsRef) -> anyhow::Result> { let mut buf = vec![]; let entries = read_dir(path)?; for entry in entries { let entry = entry?; let meta = entry.metadata()?; if meta.is_dir() { let mut subdir = recurse_files(entry.path())?; buf.append(&mut subdir); } if meta.is_file() { buf.push(entry.path()); } } buf.sort(); Ok(buf) } #[test] fn add_migration_error_ambiguous() -> anyhow::Result<()> { for reversible in [true, false] { let files = AddMigrations::new()? // Passing both `--timestamp` and `--reversible` should result in an error. .run("hello world", reversible, true, true, false)? .fs_output()?; // Assert that no files are created assert_eq!(files.0, []); } Ok(()) } #[test] fn add_migration_sequential() -> anyhow::Result<()> { { let files = AddMigrations::new()? .run("hello world", false, false, true, true)? .fs_output()?; assert_eq!(files.len(), 1); files.assert_is_not_reversible(); assert_eq!(files.0[0].id, 1); } { let files = AddMigrations::new()? .run("hello world1", false, false, true, true)? .run("hello world2", true, false, true, true)? .fs_output()?; assert_eq!(files.len(), 3); assert_eq!(files.0[0].id, 1); assert_eq!(files.0[1].id, 2); assert_eq!(files.0[1].suffix, "down.sql"); assert_eq!(files.0[2].id, 2); assert_eq!(files.0[2].suffix, "up.sql"); } Ok(()) } #[test] fn add_migration_sequential_reversible() -> anyhow::Result<()> { { let files = AddMigrations::new()? .run("hello world", true, false, true, true)? .fs_output()?; assert_eq!(files.len(), 2); files.assert_is_reversible(); assert_eq!(files.0[0].id, 1); assert_eq!(files.0[0].id, 1); } { let files = AddMigrations::new()? .run("hello world1", true, false, true, true)? .run("hello world2", true, true, false, true)? .run("hello world3", true, false, true, true)? .fs_output()?; assert_eq!(files.len(), 6); files.assert_is_reversible(); assert_eq!(files.0[0].id, 1); assert_eq!(files.0[1].id, 1); // sequential -> timestamp is one way files.0[2].assert_is_timestamp(); files.0[3].assert_is_timestamp(); files.0[4].assert_is_timestamp(); files.0[5].assert_is_timestamp(); } Ok(()) } #[test] fn add_migration_timestamp() -> anyhow::Result<()> { { let files = AddMigrations::new()? .run("hello world", false, true, false, true)? .fs_output()?; assert_eq!(files.len(), 1); files.assert_is_not_reversible(); files.0[0].assert_is_timestamp(); } { let files = AddMigrations::new()? .run("hello world1", false, true, false, true)? .run("hello world2", true, false, true, true)? .fs_output()?; assert_eq!(files.len(), 3); files.0[0].assert_is_timestamp(); // sequential -> timestamp is one way files.0[1].assert_is_timestamp(); files.0[2].assert_is_timestamp(); } Ok(()) } #[test] fn add_migration_timestamp_reversible() -> anyhow::Result<()> { { let files = AddMigrations::new()? .run("hello world", true, false, false, true)? .fs_output()?; assert_eq!(files.len(), 2); files.assert_is_reversible(); // .up.sql and .down.sql files[0].assert_is_timestamp(); assert_eq!(files[1].id, files[0].id); } { let files = AddMigrations::new()? .run("hello world", true, true, false, true)? .fs_output()?; assert_eq!(files.len(), 2); files.assert_is_reversible(); // .up.sql and .down.sql files[0].assert_is_timestamp(); assert_eq!(files[1].id, files[0].id); } { let files = AddMigrations::new()? .run("hello world1", true, true, false, true)? // Reversible should be inferred, but sequential should be forced .run("hello world2", false, false, true, true)? .fs_output()?; assert_eq!(files.len(), 4); files.assert_is_reversible(); // First pair: .up.sql and .down.sql files[0].assert_is_timestamp(); assert_eq!(files[1].id, files[0].id); // Second pair; we set `--sequential` so this version should be one higher assert_eq!(files[2].id, files[1].id + 1); assert_eq!(files[3].id, files[1].id + 1); } Ok(()) } #[test] fn add_migration_config_default_type_reversible() -> anyhow::Result<()> { let files = AddMigrations::new()? .with_config("config_default_type_reversible.toml")? // Type should default to reversible without any flags .run("hello world", false, false, false, true)? .run("hello world2", false, false, false, true)? .run("hello world3", false, false, false, true)? .fs_output()?; assert_eq!(files.len(), 6); files.assert_is_reversible(); files[0].assert_is_timestamp(); assert_eq!(files[1].id, files[0].id); files[2].assert_is_timestamp(); assert_eq!(files[3].id, files[2].id); files[4].assert_is_timestamp(); assert_eq!(files[5].id, files[4].id); Ok(()) } #[test] fn add_migration_config_default_versioning_sequential() -> anyhow::Result<()> { let files = AddMigrations::new()? .with_config("config_default_versioning_sequential.toml")? // Versioning should default to timestamp without any flags .run("hello world", false, false, false, true)? .run("hello world2", false, false, false, true)? .run("hello world3", false, false, false, true)? .fs_output()?; assert_eq!(files.len(), 3); files.assert_is_not_reversible(); assert_eq!(files[0].id, 1); assert_eq!(files[1].id, 2); assert_eq!(files[2].id, 3); Ok(()) } #[test] fn add_migration_config_default_versioning_timestamp() -> anyhow::Result<()> { let migrations = AddMigrations::new()?; migrations .run("hello world", false, false, true, true)? // Default config should infer sequential even without passing `--sequential` .run("hello world2", false, false, false, true)? .run("hello world3", false, false, false, true)?; let files = migrations.fs_output()?; assert_eq!(files.len(), 3); files.assert_is_not_reversible(); assert_eq!(files[0].id, 1); assert_eq!(files[1].id, 2); assert_eq!(files[2].id, 3); // Now set a config that uses `default-versioning = "timestamp"` let migrations = migrations.with_config("config_default_versioning_timestamp.toml")?; // Now the default should be a timestamp migrations .run("hello world4", false, false, false, true)? .run("hello world5", false, false, false, true)?; let files = migrations.fs_output()?; assert_eq!(files.len(), 5); files.assert_is_not_reversible(); assert_eq!(files[0].id, 1); assert_eq!(files[1].id, 2); assert_eq!(files[2].id, 3); files[3].assert_is_timestamp(); files[4].assert_is_timestamp(); Ok(()) } ================================================ FILE: sqlx-cli/tests/assets/config_default_type_reversible.toml ================================================ [migrate.defaults] migration-type = "reversible" ================================================ FILE: sqlx-cli/tests/assets/config_default_versioning_sequential.toml ================================================ [migrate.defaults] migration-versioning = "sequential" ================================================ FILE: sqlx-cli/tests/assets/config_default_versioning_timestamp.toml ================================================ [migrate.defaults] migration-versioning = "timestamp" ================================================ FILE: sqlx-cli/tests/assets/sample_metadata.json ================================================ { "packages": [ { "name": "ahash", "version": "0.7.6", "id": "ahash 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "A non-cryptographic hash function using AES-NI for high performance", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "criterion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.2", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "fnv", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.5", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "fxhash", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "hex", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.2", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "no-panic", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.10", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.7.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "seahash", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^4.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde_json", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.59", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "version_check", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.9", "kind": "build", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "const-random", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.12", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": "cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"windows\", target_os = \"macos\", target_os = \"ios\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"dragonfly\", target_os = \"solaris\", target_os = \"illumos\", target_os = \"fuchsia\", target_os = \"redox\", target_os = \"cloudabi\", target_os = \"haiku\", target_os = \"vxworks\", target_os = \"emscripten\", target_os = \"wasi\"))", "registry": null }, { "name": "getrandom", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.3", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"windows\", target_os = \"macos\", target_os = \"ios\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"dragonfly\", target_os = \"solaris\", target_os = \"illumos\", target_os = \"fuchsia\", target_os = \"redox\", target_os = \"cloudabi\", target_os = \"haiku\", target_os = \"vxworks\", target_os = \"emscripten\", target_os = \"wasi\"))", "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.117", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": "cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"windows\", target_os = \"macos\", target_os = \"ios\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"dragonfly\", target_os = \"solaris\", target_os = \"illumos\", target_os = \"fuchsia\", target_os = \"redox\", target_os = \"cloudabi\", target_os = \"haiku\", target_os = \"vxworks\", target_os = \"emscripten\", target_os = \"wasi\"))", "registry": null }, { "name": "once_cell", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.8", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [ "alloc" ], "target": "cfg(not(all(target_arch = \"arm\", target_os = \"none\")))", "registry": null }, { "name": "const-random", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.12", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": "cfg(not(any(target_os = \"linux\", target_os = \"android\", target_os = \"windows\", target_os = \"macos\", target_os = \"ios\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"dragonfly\", target_os = \"solaris\", target_os = \"illumos\", target_os = \"fuchsia\", target_os = \"redox\", target_os = \"cloudabi\", target_os = \"haiku\", target_os = \"vxworks\", target_os = \"emscripten\", target_os = \"wasi\")))", "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.117", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": "cfg(not(any(target_os = \"linux\", target_os = \"android\", target_os = \"windows\", target_os = \"macos\", target_os = \"ios\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"dragonfly\", target_os = \"solaris\", target_os = \"illumos\", target_os = \"fuchsia\", target_os = \"redox\", target_os = \"cloudabi\", target_os = \"haiku\", target_os = \"vxworks\", target_os = \"emscripten\", target_os = \"wasi\")))", "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "ahash", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ahash-0.7.6/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "map_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ahash-0.7.6/tests/map_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "nopanic", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ahash-0.7.6/tests/nopanic.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "bench", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ahash-0.7.6/tests/bench.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "ahash", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ahash-0.7.6/tests/bench.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "map", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ahash-0.7.6/tests/map_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ahash-0.7.6/./build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "compile-time-rng": [ "const-random" ], "const-random": [ "dep:const-random" ], "default": [ "std" ], "serde": [ "dep:serde" ], "std": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ahash-0.7.6/Cargo.toml", "metadata": { "docs": { "rs": { "features": [ "std" ], "rustc-args": [ "-C", "target-feature=+aes" ], "rustdoc-args": [ "-C", "target-feature=+aes" ] } } }, "publish": null, "authors": [ "Tom Kaitchuck " ], "categories": [ "algorithms", "data-structures", "no-std" ], "keywords": [ "hash", "hasher", "hashmap", "aes", "no-std" ], "readme": "README.md", "repository": "https://github.com/tkaitchuck/ahash", "homepage": null, "documentation": "https://docs.rs/ahash", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "atoi", "version": "0.4.0", "id": "atoi 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT", "license_file": null, "description": "Parse integers directly from `[u8]` slices in safe code", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "num-traits", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.12", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "criterion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "atoi", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/atoi-0.4.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "benches", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/atoi-0.4.0/benches/benches.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/atoi-0.4.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Markus Klein " ], "categories": [ "parsing" ], "keywords": [ "atoi", "conversion", "integer" ], "readme": "README.md", "repository": "https://github.com/pacman82/atoi-rs", "homepage": null, "documentation": "https://docs.rs/atoi/", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "autocfg", "version": "1.1.0", "id": "autocfg 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Apache-2.0 OR MIT", "license_file": null, "description": "Automatic cfg for Rust compiler features", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "autocfg", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/autocfg-1.1.0/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "versions", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/autocfg-1.1.0/examples/versions.rs", "edition": "2015", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "traits", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/autocfg-1.1.0/examples/traits.rs", "edition": "2015", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "integers", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/autocfg-1.1.0/examples/integers.rs", "edition": "2015", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "paths", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/autocfg-1.1.0/examples/paths.rs", "edition": "2015", "doc": false, "doctest": false, "test": false }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "rustflags", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/autocfg-1.1.0/tests/rustflags.rs", "edition": "2015", "doc": false, "doctest": false, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/autocfg-1.1.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Josh Stone " ], "categories": [ "development-tools::build-utils" ], "keywords": [ "rustc", "build", "autoconf" ], "readme": "README.md", "repository": "https://github.com/cuviper/autocfg", "homepage": null, "documentation": null, "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "b_in_workspace_lib", "version": "0.1.0", "id": "b_in_workspace_lib 0.1.0 (path+file:///home/user/problematic/workspace/b_in_workspace_lib)", "license": null, "license_file": null, "description": null, "source": null, "dependencies": [ { "name": "sqlx", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.5", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [ "offline", "runtime-tokio-rustls", "sqlite" ], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "b_in_workspace_lib", "src_path": "/home/user/problematic/workspace/b_in_workspace_lib/src/lib.rs", "edition": "2021", "doc": true, "doctest": true, "test": true } ], "features": {}, "manifest_path": "/home/user/problematic/workspace/b_in_workspace_lib/Cargo.toml", "metadata": null, "publish": null, "authors": [], "categories": [], "keywords": [], "readme": null, "repository": null, "homepage": null, "documentation": null, "edition": "2021", "links": null, "default_run": null, "rust_version": null }, { "name": "base64", "version": "0.13.0", "id": "base64 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "encodes and decodes base64 as bytes or utf8", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "criterion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=0.3.2", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.6.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "structopt", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "base64", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/base64-0.13.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "make_tables", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/base64-0.13.0/examples/make_tables.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "base64", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/base64-0.13.0/examples/base64.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "encode", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/base64-0.13.0/tests/encode.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "helpers", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/base64-0.13.0/tests/helpers.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/base64-0.13.0/tests/tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "decode", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/base64-0.13.0/tests/decode.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "benchmarks", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/base64-0.13.0/benches/benchmarks.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "alloc": [], "default": [ "std" ], "std": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/base64-0.13.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Alice Maz ", "Marshall Pierce " ], "categories": [ "encoding" ], "keywords": [ "base64", "utf8", "encode", "decode", "no_std" ], "readme": "README.md", "repository": "https://github.com/marshallpierce/rust-base64", "homepage": null, "documentation": "https://docs.rs/base64", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "bitflags", "version": "1.3.2", "id": "bitflags 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "A macro to generate structures which behave like bitflags.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "compiler_builtins", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.2", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustc-std-workspace-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.0", "kind": null, "rename": "core", "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustversion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde_derive", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde_json", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "trybuild", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "walkdir", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^2.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "bitflags", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bitflags-1.3.2/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "basic", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bitflags-1.3.2/tests/basic.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "compile", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bitflags-1.3.2/tests/compile.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "compiler_builtins": [ "dep:compiler_builtins" ], "core": [ "dep:core" ], "default": [], "example_generated": [], "rustc-dep-of-std": [ "core", "compiler_builtins" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bitflags-1.3.2/Cargo.toml", "metadata": { "docs": { "rs": { "features": [ "example_generated" ] } } }, "publish": null, "authors": [ "The Rust Project Developers" ], "categories": [ "no-std" ], "keywords": [ "bit", "bitmask", "bitflags", "flags" ], "readme": "README.md", "repository": "https://github.com/bitflags/bitflags", "homepage": "https://github.com/bitflags/bitflags", "documentation": "https://docs.rs/bitflags", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "block-buffer", "version": "0.9.0", "id": "block-buffer 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Fixed size buffer for block processing of data", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "block-padding", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "generic-array", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.14", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "block-buffer", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/block-buffer-0.9.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": { "block-padding": [ "dep:block-padding" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/block-buffer-0.9.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "RustCrypto Developers" ], "categories": [ "cryptography", "no-std" ], "keywords": [ "block", "buffer" ], "readme": null, "repository": "https://github.com/RustCrypto/utils", "homepage": null, "documentation": "https://docs.rs/block-buffer", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "bumpalo", "version": "3.9.1", "id": "bumpalo 3.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "A fast bump allocation arena for Rust.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "criterion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "quickcheck", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.9.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.7", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "bumpalo", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bumpalo-3.9.1/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "try_alloc", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bumpalo-3.9.1/tests/try_alloc.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "benches", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bumpalo-3.9.1/benches/benches.rs", "edition": "2018", "required-features": [ "collections" ], "doc": false, "doctest": false, "test": false } ], "features": { "allocator_api": [], "boxed": [], "collections": [], "default": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bumpalo-3.9.1/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true } } }, "publish": null, "authors": [ "Nick Fitzgerald " ], "categories": [ "memory-management", "rust-patterns", "no-std" ], "keywords": [], "readme": "./README.md", "repository": "https://github.com/fitzgen/bumpalo", "homepage": null, "documentation": "https://docs.rs/bumpalo", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "byteorder", "version": "1.4.3", "id": "byteorder 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Unlicense OR MIT", "license_file": null, "description": "Library for reading/writing numbers in big-endian and little-endian.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "quickcheck", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.9.2", "kind": "dev", "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.7", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "byteorder", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/byteorder-1.4.3/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "bench", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/byteorder-1.4.3/benches/bench.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "default": [ "std" ], "i128": [], "std": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/byteorder-1.4.3/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Andrew Gallant " ], "categories": [ "encoding", "parsing", "no-std" ], "keywords": [ "byte", "endian", "big-endian", "little-endian", "binary" ], "readme": "README.md", "repository": "https://github.com/BurntSushi/byteorder", "homepage": "https://github.com/BurntSushi/byteorder", "documentation": "https://docs.rs/byteorder", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "bytes", "version": "1.1.0", "id": "bytes 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT", "license_file": null, "description": "Types and traits for working with bytes", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.60", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [ "alloc" ], "target": null, "registry": null }, { "name": "serde_test", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "loom", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.5", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(loom)", "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "bytes", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bytes-1.1.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_iter", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bytes-1.1.0/tests/test_iter.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_buf", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bytes-1.1.0/tests/test_buf.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_bytes_vec_alloc", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bytes-1.1.0/tests/test_bytes_vec_alloc.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_debug", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bytes-1.1.0/tests/test_debug.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_buf_mut", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bytes-1.1.0/tests/test_buf_mut.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_serde", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bytes-1.1.0/tests/test_serde.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_bytes", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bytes-1.1.0/tests/test_bytes.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_take", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bytes-1.1.0/tests/test_take.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_chain", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bytes-1.1.0/tests/test_chain.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_reader", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bytes-1.1.0/tests/test_reader.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_bytes_odd_alloc", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bytes-1.1.0/tests/test_bytes_odd_alloc.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "bytes_mut", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bytes-1.1.0/benches/bytes_mut.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "buf", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bytes-1.1.0/benches/buf.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "bytes", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bytes-1.1.0/benches/bytes.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "default": [ "std" ], "serde": [ "dep:serde" ], "std": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/bytes-1.1.0/Cargo.toml", "metadata": { "docs": { "rs": { "rustdoc-args": [ "--cfg", "docsrs" ] } } }, "publish": null, "authors": [ "Carl Lerche ", "Sean McArthur " ], "categories": [ "network-programming", "data-structures" ], "keywords": [ "buffers", "zero-copy", "io" ], "readme": "README.md", "repository": "https://github.com/tokio-rs/bytes", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "c_in_workspace_bin", "version": "0.1.0", "id": "c_in_workspace_bin 0.1.0 (path+file:///home/user/problematic/workspace/c_in_workspace_bin)", "license": null, "license_file": null, "description": null, "source": null, "dependencies": [ { "name": "b_in_workspace_lib", "source": null, "req": "*", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null, "path": "/home/user/problematic/workspace/b_in_workspace_lib" }, { "name": "sqlx", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.5", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [ "offline", "runtime-tokio-rustls", "sqlite" ], "target": null, "registry": null } ], "targets": [ { "kind": [ "bin" ], "crate_types": [ "bin" ], "name": "c_in_workspace_bin", "src_path": "/home/user/problematic/workspace/c_in_workspace_bin/src/main.rs", "edition": "2021", "doc": true, "doctest": false, "test": true } ], "features": {}, "manifest_path": "/home/user/problematic/workspace/c_in_workspace_bin/Cargo.toml", "metadata": null, "publish": null, "authors": [], "categories": [], "keywords": [], "readme": null, "repository": null, "homepage": null, "documentation": null, "edition": "2021", "links": null, "default_run": null, "rust_version": null }, { "name": "cc", "version": "1.0.73", "id": "cc 1.0.73 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "A build-time dependency for Cargo build scripts to assist in invoking the native\nC compiler to compile native C code into a static archive to be linked into Rust\ncode.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "jobserver", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.16", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tempfile", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "cc", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.0.73/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "bin" ], "crate_types": [ "bin" ], "name": "gcc-shim", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.0.73/src/bin/gcc-shim.rs", "edition": "2018", "doc": true, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "cxxflags", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.0.73/tests/cxxflags.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "cc_env", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.0.73/tests/cc_env.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "cflags", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.0.73/tests/cflags.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.0.73/tests/test.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "jobserver": [ "dep:jobserver" ], "parallel": [ "jobserver" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/cc-1.0.73/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Alex Crichton " ], "categories": [ "development-tools::build-utils" ], "keywords": [ "build-dependencies" ], "readme": "README.md", "repository": "https://github.com/alexcrichton/cc-rs", "homepage": "https://github.com/alexcrichton/cc-rs", "documentation": "https://docs.rs/cc", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "cfg-if", "version": "1.0.0", "id": "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "A macro to ergonomically define an item depending on a large number of #[cfg]\nparameters. Structured like an if-else chain, the first matching branch is the\nitem that gets emitted.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "compiler_builtins", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.2", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustc-std-workspace-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.0", "kind": null, "rename": "core", "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "cfg-if", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/cfg-if-1.0.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "xcrate", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/cfg-if-1.0.0/tests/xcrate.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "compiler_builtins": [ "dep:compiler_builtins" ], "core": [ "dep:core" ], "rustc-dep-of-std": [ "core", "compiler_builtins" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/cfg-if-1.0.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Alex Crichton " ], "categories": [], "keywords": [], "readme": "README.md", "repository": "https://github.com/alexcrichton/cfg-if", "homepage": "https://github.com/alexcrichton/cfg-if", "documentation": "https://docs.rs/cfg-if", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "cpufeatures", "version": "0.2.2", "id": "cpufeatures 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Lightweight runtime CPU feature detection for x86/x86_64 and aarch64 with\nno_std support and support for mobile targets including Android and iOS\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "libc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.68", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "aarch64-apple-darwin", "registry": null }, { "name": "libc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.68", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "aarch64-linux-android", "registry": null }, { "name": "libc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.68", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))", "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "cpufeatures", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/cpufeatures-0.2.2/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "x86", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/cpufeatures-0.2.2/tests/x86.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "aarch64", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/cpufeatures-0.2.2/tests/aarch64.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/cpufeatures-0.2.2/Cargo.toml", "metadata": null, "publish": null, "authors": [ "RustCrypto Developers" ], "categories": [ "no-std" ], "keywords": [ "cpuid", "target-feature" ], "readme": "README.md", "repository": "https://github.com/RustCrypto/utils", "homepage": null, "documentation": "https://docs.rs/cpufeatures", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "crc", "version": "2.1.0", "id": "crc 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Rust implementation of CRC(16, 32, 64) with support of various standards", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "crc-catalog", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.1.1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "crc", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crc-2.1.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "crc", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crc-2.1.0/tests/crc.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "bench", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crc-2.1.0/benches/bench.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crc-2.1.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Rui Hu ", "Akhil Velagapudi <4@4khil.com>" ], "categories": [ "algorithms", "no-std" ], "keywords": [ "crc", "crc16", "crc32", "crc64", "hash" ], "readme": "README.md", "repository": "https://github.com/mrhooray/crc-rs.git", "homepage": null, "documentation": "https://docs.rs/crc", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "crc-catalog", "version": "1.1.1", "id": "crc-catalog 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Catalog of CRC algorithms (generated from http://reveng.sourceforge.net/crc-catalogue) expressed as simple Rust structs.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "crc-catalog", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crc-catalog-1.1.1/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crc-catalog-1.1.1/Cargo.toml", "metadata": { "docs": { "rs": { "targets": [ "x86_64-unknown-linux-gnu" ] } } }, "publish": null, "authors": [ "Akhil Velagapudi " ], "categories": [ "no-std", "network-programming" ], "keywords": [ "crc" ], "readme": "README.md", "repository": "https://github.com/akhilles/crc-catalog.git", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "crossbeam-queue", "version": "0.3.5", "id": "crossbeam-queue 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Concurrent queues", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "cfg-if", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "crossbeam-utils", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8.5", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "crossbeam-queue", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-queue-0.3.5/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "seg_queue", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-queue-0.3.5/tests/seg_queue.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "array_queue", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-queue-0.3.5/tests/array_queue.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-queue-0.3.5/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "alloc": [], "default": [ "std" ], "nightly": [ "crossbeam-utils/nightly" ], "std": [ "alloc", "crossbeam-utils/std" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-queue-0.3.5/Cargo.toml", "metadata": null, "publish": null, "authors": [], "categories": [ "concurrency", "data-structures", "no-std" ], "keywords": [ "queue", "mpmc", "lock-free", "producer", "consumer" ], "readme": "README.md", "repository": "https://github.com/crossbeam-rs/crossbeam", "homepage": "https://github.com/crossbeam-rs/crossbeam/tree/master/crossbeam-queue", "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": "1.36" }, { "name": "crossbeam-utils", "version": "0.8.8", "id": "crossbeam-utils 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Utilities for concurrent programming", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "cfg-if", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "lazy_static", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.4.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustversion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "loom", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.5", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": "cfg(crossbeam_loom)", "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "crossbeam-utils", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-utils-0.8.8/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "parker", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-utils-0.8.8/tests/parker.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "thread", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-utils-0.8.8/tests/thread.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sharded_lock", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-utils-0.8.8/tests/sharded_lock.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "cache_padded", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-utils-0.8.8/tests/cache_padded.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "atomic_cell", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-utils-0.8.8/tests/atomic_cell.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "wait_group", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-utils-0.8.8/tests/wait_group.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "atomic_cell", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-utils-0.8.8/benches/atomic_cell.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-utils-0.8.8/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "default": [ "std" ], "lazy_static": [ "dep:lazy_static" ], "loom": [ "dep:loom" ], "nightly": [], "std": [ "lazy_static" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/crossbeam-utils-0.8.8/Cargo.toml", "metadata": null, "publish": null, "authors": [], "categories": [ "algorithms", "concurrency", "data-structures", "no-std" ], "keywords": [ "scoped", "thread", "atomic", "cache" ], "readme": "README.md", "repository": "https://github.com/crossbeam-rs/crossbeam", "homepage": "https://github.com/crossbeam-rs/crossbeam/tree/master/crossbeam-utils", "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": "1.36" }, { "name": "digest", "version": "0.9.0", "id": "digest 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Traits for cryptographic hash functions", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "blobby", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "generic-array", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.14", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "digest", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/digest-0.9.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": { "alloc": [], "blobby": [ "dep:blobby" ], "dev": [ "blobby" ], "std": [ "alloc" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/digest-0.9.0/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true, "rustdoc-args": [ "--cfg", "docsrs" ] } } }, "publish": null, "authors": [ "RustCrypto Developers" ], "categories": [ "cryptography", "no-std" ], "keywords": [ "digest", "crypto", "hash" ], "readme": "README.md", "repository": "https://github.com/RustCrypto/traits", "homepage": null, "documentation": "https://docs.rs/digest", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "dotenv", "version": "0.15.0", "id": "dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT", "license_file": null, "description": "A `dotenv` implementation for Rust", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "clap", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^2", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tempfile", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^3.0.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "dotenv", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/dotenv-0.15.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "bin" ], "crate_types": [ "bin" ], "name": "dotenv", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/dotenv-0.15.0/src/bin/dotenv.rs", "edition": "2018", "required-features": [ "cli" ], "doc": true, "doctest": false, "test": true }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "simple", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/dotenv-0.15.0/examples/simple.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test-variable-substitution", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/dotenv-0.15.0/tests/test-variable-substitution.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test-from-filename", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/dotenv-0.15.0/tests/test-from-filename.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test-from-path-iter", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/dotenv-0.15.0/tests/test-from-path-iter.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test-var", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/dotenv-0.15.0/tests/test-var.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test-default-location", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/dotenv-0.15.0/tests/test-default-location.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test-from-path", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/dotenv-0.15.0/tests/test-from-path.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test-from-filename-iter", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/dotenv-0.15.0/tests/test-from-filename-iter.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test-vars", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/dotenv-0.15.0/tests/test-vars.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test-dotenv-iter", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/dotenv-0.15.0/tests/test-dotenv-iter.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test-child-dir", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/dotenv-0.15.0/tests/test-child-dir.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "clap": [ "dep:clap" ], "cli": [ "clap" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/dotenv-0.15.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Noemi Lapresta ", "Craig Hills ", "Mike Piccolo ", "Alice Maz ", "Sean Griffin ", "Adam Sharp ", "Arpad Borsos " ], "categories": [], "keywords": [ "environment", "env", "dotenv", "settings", "config" ], "readme": "../README.md", "repository": "https://github.com/dotenv-rs/dotenv", "homepage": "https://github.com/dotenv-rs/dotenv", "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "either", "version": "1.6.1", "id": "either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "The enum `Either` with variants `Left` and `Right` is a general purpose sum type with two cases.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [ "derive" ], "target": null, "registry": null }, { "name": "serde_json", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "either", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/either-1.6.1/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true } ], "features": { "default": [ "use_std" ], "serde": [ "dep:serde" ], "use_std": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/either-1.6.1/Cargo.toml", "metadata": { "docs": { "rs": { "features": [ "serde" ] } }, "release": { "no-dev-version": true, "tag-name": "{{version}}" } }, "publish": null, "authors": [ "bluss" ], "categories": [ "data-structures", "no-std" ], "keywords": [ "data-structure", "no_std" ], "readme": "README-crates.io.md", "repository": "https://github.com/bluss/either", "homepage": null, "documentation": "https://docs.rs/either/1/", "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "flume", "version": "0.10.12", "id": "flume 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Apache-2.0/MIT", "license_file": null, "description": "A blazingly fast multi-producer channel", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "futures-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "futures-sink", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "nanorand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.7", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [ "getrandom" ], "target": null, "registry": null }, { "name": "pin-project", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "spin", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.9.2", "kind": null, "rename": "spin1", "optional": false, "uses_default_features": true, "features": [ "mutex" ], "target": null, "registry": null }, { "name": "async-std", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.9.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "attributes", "unstable" ], "target": null, "registry": null }, { "name": "criterion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.4", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "crossbeam-channel", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.5.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "crossbeam-utils", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8.5", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "futures", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "std" ], "target": null, "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tokio", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.16.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "rt", "macros" ], "target": null, "registry": null }, { "name": "waker-fn", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "flume", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "async", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/examples/async.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "select", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/examples/select.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "simple", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/examples/simple.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "perf", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/examples/perf.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "async", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/async.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "basic", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/basic.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "list", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/list.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "iter", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/iter.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "stream", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/stream.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "mpsc", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/mpsc.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "method_sharing", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/method_sharing.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "same_channel", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/same_channel.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "array", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/array.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "ready", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/ready.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "never", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/never.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "thread_locals", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/thread_locals.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "select", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/select.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "after", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/after.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "select_macro", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/select_macro.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "tick", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/tick.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "zero", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/zero.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "golang", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/tests/golang.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "basic", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/benches/basic.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "async": [ "futures-sink", "futures-core", "pin-project" ], "default": [ "async", "select", "eventual-fairness" ], "eventual-fairness": [ "async", "nanorand" ], "futures-core": [ "dep:futures-core" ], "futures-sink": [ "dep:futures-sink" ], "nanorand": [ "dep:nanorand" ], "pin-project": [ "dep:pin-project" ], "select": [], "spin": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/flume-0.10.12/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Joshua Barretto " ], "categories": [ "concurrency", "data-structures" ], "keywords": [ "mpsc", "fifo", "channel", "thread", "mpmc" ], "readme": "README.md", "repository": "https://github.com/zesterer/flume", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "form_urlencoded", "version": "1.0.1", "id": "form_urlencoded 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "Parser and serializer for the application/x-www-form-urlencoded syntax, as used by HTML forms.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "matches", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "percent-encoding", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^2.1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "form_urlencoded", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/form_urlencoded-1.0.1/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": false } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/form_urlencoded-1.0.1/Cargo.toml", "metadata": null, "publish": null, "authors": [ "The rust-url developers" ], "categories": [], "keywords": [], "readme": null, "repository": "https://github.com/servo/rust-url", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "futures-channel", "version": "0.3.21", "id": "futures-channel 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Channels for asynchronous communication using futures-rs.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "futures-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.21", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "futures-sink", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.21", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "futures-channel", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-channel-0.3.21/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "channel", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-channel-0.3.21/tests/channel.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "mpsc", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-channel-0.3.21/tests/mpsc.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "oneshot", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-channel-0.3.21/tests/oneshot.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "mpsc-close", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-channel-0.3.21/tests/mpsc-close.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "sync_mpsc", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-channel-0.3.21/benches/sync_mpsc.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-channel-0.3.21/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "alloc": [ "futures-core/alloc" ], "cfg-target-has-atomic": [], "default": [ "std" ], "futures-sink": [ "dep:futures-sink" ], "sink": [ "futures-sink" ], "std": [ "alloc", "futures-core/std" ], "unstable": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-channel-0.3.21/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true, "rustdoc-args": [ "--cfg", "docsrs" ] } } }, "publish": null, "authors": [], "categories": [], "keywords": [], "readme": "README.md", "repository": "https://github.com/rust-lang/futures-rs", "homepage": "https://rust-lang.github.io/futures-rs", "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": "1.45" }, { "name": "futures-core", "version": "0.3.21", "id": "futures-core 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "The core traits and types in for the `futures` library.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "futures-core", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-core-0.3.21/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-core-0.3.21/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "alloc": [], "cfg-target-has-atomic": [], "default": [ "std" ], "std": [ "alloc" ], "unstable": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-core-0.3.21/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true, "rustdoc-args": [ "--cfg", "docsrs" ] } } }, "publish": null, "authors": [], "categories": [], "keywords": [], "readme": "README.md", "repository": "https://github.com/rust-lang/futures-rs", "homepage": "https://rust-lang.github.io/futures-rs", "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": "1.36" }, { "name": "futures-executor", "version": "0.3.21", "id": "futures-executor 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Executors for asynchronous tasks based on the futures-rs library.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "futures-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.21", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "futures-task", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.21", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "futures-util", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.21", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "num_cpus", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.8.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "futures-executor", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-executor-0.3.21/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "local_pool", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-executor-0.3.21/tests/local_pool.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "thread_notify", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-executor-0.3.21/benches/thread_notify.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "default": [ "std" ], "num_cpus": [ "dep:num_cpus" ], "std": [ "futures-core/std", "futures-task/std", "futures-util/std" ], "thread-pool": [ "std", "num_cpus" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-executor-0.3.21/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true, "rustdoc-args": [ "--cfg", "docsrs" ] } } }, "publish": null, "authors": [], "categories": [], "keywords": [], "readme": "README.md", "repository": "https://github.com/rust-lang/futures-rs", "homepage": "https://rust-lang.github.io/futures-rs", "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": "1.45" }, { "name": "futures-intrusive", "version": "0.4.0", "id": "futures-intrusive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Futures based on intrusive data structures - for std and no-std environments.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "futures-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "lock_api", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "parking_lot", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.11.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "async-std", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.4", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "criterion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "crossbeam", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.7", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "futures", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "async-await" ], "target": null, "registry": null }, { "name": "futures-test", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "lazy_static", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.4.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "pin-utils", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.7", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "signal-hook", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.11", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tokio", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.11", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "full" ], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "futures_intrusive", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-intrusive-0.4.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "cancellation", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-intrusive-0.4.0/examples/cancellation.rs", "edition": "2018", "required-features": [ "std" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "philosophers", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-intrusive-0.4.0/examples/philosophers.rs", "edition": "2018", "required-features": [ "std" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "timer", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-intrusive-0.4.0/tests/timer.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "mutex", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-intrusive-0.4.0/tests/mutex.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "mpmc_channel", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-intrusive-0.4.0/tests/mpmc_channel.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "state_broadcast_channel", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-intrusive-0.4.0/tests/state_broadcast_channel.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "semaphore", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-intrusive-0.4.0/tests/semaphore.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "manual_reset_event", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-intrusive-0.4.0/tests/manual_reset_event.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "oneshot_channel", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-intrusive-0.4.0/tests/oneshot_channel.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "mpmc_channel", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-intrusive-0.4.0/benches/mpmc_channel.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "mutex", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-intrusive-0.4.0/benches/mutex.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "semaphore", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-intrusive-0.4.0/benches/semaphore.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "alloc": [ "futures-core/alloc" ], "default": [ "std" ], "parking_lot": [ "dep:parking_lot" ], "std": [ "alloc", "parking_lot" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-intrusive-0.4.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Matthias Einwag " ], "categories": [], "keywords": [], "readme": null, "repository": "https://github.com/Matthias247/futures-intrusive", "homepage": "https://github.com/Matthias247/futures-intrusive", "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "futures-sink", "version": "0.3.21", "id": "futures-sink 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "The asynchronous `Sink` trait for the futures-rs library.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "futures-sink", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-sink-0.3.21/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": { "alloc": [], "default": [ "std" ], "std": [ "alloc" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-sink-0.3.21/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true } } }, "publish": null, "authors": [], "categories": [], "keywords": [], "readme": "README.md", "repository": "https://github.com/rust-lang/futures-rs", "homepage": "https://rust-lang.github.io/futures-rs", "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": "1.36" }, { "name": "futures-task", "version": "0.3.21", "id": "futures-task 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Tools for working with tasks.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "futures-task", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-task-0.3.21/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-task-0.3.21/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "alloc": [], "cfg-target-has-atomic": [], "default": [ "std" ], "std": [ "alloc" ], "unstable": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-task-0.3.21/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true } } }, "publish": null, "authors": [], "categories": [], "keywords": [], "readme": "README.md", "repository": "https://github.com/rust-lang/futures-rs", "homepage": "https://rust-lang.github.io/futures-rs", "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": "1.45" }, { "name": "futures-util", "version": "0.3.21", "id": "futures-util 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Common utilities and extension traits for the futures-rs library.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "futures-channel", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.21", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [ "std" ], "target": null, "registry": null }, { "name": "futures-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.21", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "futures-io", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.21", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [ "std" ], "target": null, "registry": null }, { "name": "futures-macro", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=0.3.21", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "futures-sink", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.21", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "futures-task", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.21", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "futures", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.25", "kind": null, "rename": "futures_01", "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "memchr", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^2.2", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "pin-project-lite", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.4", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "pin-utils", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "slab", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.2", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tokio-io", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.9", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tokio", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.11", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "futures-util", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-util-0.3.21/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "futures_unordered", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-util-0.3.21/benches/futures_unordered.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "flatten_unordered", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-util-0.3.21/benches/flatten_unordered.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-util-0.3.21/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "alloc": [ "futures-core/alloc", "futures-task/alloc" ], "async-await": [], "async-await-macro": [ "async-await", "futures-macro" ], "bilock": [], "cfg-target-has-atomic": [], "channel": [ "std", "futures-channel" ], "compat": [ "std", "futures_01" ], "default": [ "std", "async-await", "async-await-macro" ], "futures-channel": [ "dep:futures-channel" ], "futures-io": [ "dep:futures-io" ], "futures-macro": [ "dep:futures-macro" ], "futures-sink": [ "dep:futures-sink" ], "futures_01": [ "dep:futures_01" ], "io": [ "std", "futures-io", "memchr" ], "io-compat": [ "io", "compat", "tokio-io" ], "memchr": [ "dep:memchr" ], "sink": [ "futures-sink" ], "slab": [ "dep:slab" ], "std": [ "alloc", "futures-core/std", "futures-task/std", "slab" ], "tokio-io": [ "dep:tokio-io" ], "unstable": [ "futures-core/unstable", "futures-task/unstable" ], "write-all-vectored": [ "io" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/futures-util-0.3.21/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true, "rustdoc-args": [ "--cfg", "docsrs" ] } } }, "publish": null, "authors": [], "categories": [], "keywords": [], "readme": "README.md", "repository": "https://github.com/rust-lang/futures-rs", "homepage": "https://rust-lang.github.io/futures-rs", "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": "1.45" }, { "name": "generic-array", "version": "0.14.5", "id": "generic-array 0.14.5 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT", "license_file": null, "description": "Generic types implementing functionality of arrays", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "typenum", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.12", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "bincode", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde_json", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "version_check", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.9", "kind": "build", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "generic_array", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/generic-array-0.14.5/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "iter", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/generic-array-0.14.5/tests/iter.rs", "edition": "2015", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "import_name", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/generic-array-0.14.5/tests/import_name.rs", "edition": "2015", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "hex", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/generic-array-0.14.5/tests/hex.rs", "edition": "2015", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "mod", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/generic-array-0.14.5/tests/mod.rs", "edition": "2015", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "generics", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/generic-array-0.14.5/tests/generics.rs", "edition": "2015", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "arr", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/generic-array-0.14.5/tests/arr.rs", "edition": "2015", "doc": false, "doctest": false, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/generic-array-0.14.5/build.rs", "edition": "2015", "doc": false, "doctest": false, "test": false } ], "features": { "more_lengths": [], "serde": [ "dep:serde" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/generic-array-0.14.5/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Bartłomiej Kamiński ", "Aaron Trent " ], "categories": [ "data-structures", "no-std" ], "keywords": [ "generic", "array" ], "readme": "README.md", "repository": "https://github.com/fizyk20/generic-array.git", "homepage": null, "documentation": "http://fizyk20.github.io/generic-array/generic_array/", "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "getrandom", "version": "0.2.6", "id": "getrandom 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "A small cross-platform library for retrieving random data from system source", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "cfg-if", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "compiler_builtins", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustc-std-workspace-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": "core", "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "js-sys", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": "cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))", "registry": null }, { "name": "wasm-bindgen", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.62", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": "cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))", "registry": null }, { "name": "wasm-bindgen-test", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.18", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(all(target_arch = \"wasm32\", target_os = \"unknown\"))", "registry": null }, { "name": "wasi", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.10", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(target_os = \"wasi\")", "registry": null }, { "name": "libc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.120", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": "cfg(unix)", "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "getrandom", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/getrandom-0.2.6/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "normal", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/getrandom-0.2.6/tests/normal.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "custom", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/getrandom-0.2.6/tests/custom.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "rdrand", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/getrandom-0.2.6/tests/rdrand.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "mod", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/getrandom-0.2.6/benches/mod.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "compiler_builtins": [ "dep:compiler_builtins" ], "core": [ "dep:core" ], "custom": [], "js": [ "wasm-bindgen", "js-sys" ], "js-sys": [ "dep:js-sys" ], "rdrand": [], "rustc-dep-of-std": [ "compiler_builtins", "core", "libc/rustc-dep-of-std", "wasi/rustc-dep-of-std" ], "std": [], "test-in-browser": [], "wasm-bindgen": [ "dep:wasm-bindgen" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/getrandom-0.2.6/Cargo.toml", "metadata": { "docs": { "rs": { "features": [ "std", "custom" ], "rustdoc-args": [ "--cfg", "docsrs" ] } } }, "publish": null, "authors": [ "The Rand Project Developers" ], "categories": [ "os", "no-std" ], "keywords": [], "readme": "README.md", "repository": "https://github.com/rust-random/getrandom", "homepage": null, "documentation": "https://docs.rs/getrandom", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "hashbrown", "version": "0.11.2", "id": "hashbrown 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Apache-2.0/MIT", "license_file": null, "description": "A Rust port of Google's SwissTable hash map", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "ahash", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.7.0", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "rustc-std-workspace-alloc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.0", "kind": null, "rename": "alloc", "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "bumpalo", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^3.5.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "compiler_builtins", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.2", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustc-std-workspace-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.0", "kind": null, "rename": "core", "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rayon", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.25", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "doc-comment", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "fnv", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.7", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "lazy_static", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.4", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.7.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "small_rng" ], "target": null, "registry": null }, { "name": "rayon", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde_test", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "hashbrown", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hashbrown-0.11.2/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "serde", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hashbrown-0.11.2/tests/serde.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "set", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hashbrown-0.11.2/tests/set.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "rayon", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hashbrown-0.11.2/tests/rayon.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "hasher", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hashbrown-0.11.2/tests/hasher.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "bench", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hashbrown-0.11.2/benches/bench.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "ahash": [ "dep:ahash" ], "ahash-compile-time-rng": [ "ahash/compile-time-rng" ], "alloc": [ "dep:alloc" ], "bumpalo": [ "dep:bumpalo" ], "compiler_builtins": [ "dep:compiler_builtins" ], "core": [ "dep:core" ], "default": [ "ahash", "inline-more" ], "inline-more": [], "nightly": [], "raw": [], "rayon": [ "dep:rayon" ], "rustc-dep-of-std": [ "nightly", "core", "compiler_builtins", "alloc", "rustc-internal-api" ], "rustc-internal-api": [], "serde": [ "dep:serde" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hashbrown-0.11.2/Cargo.toml", "metadata": { "docs": { "rs": { "features": [ "nightly", "rayon", "serde", "raw" ] } } }, "publish": null, "authors": [ "Amanieu d'Antras " ], "categories": [ "data-structures", "no-std" ], "keywords": [ "hash", "no_std", "hashmap", "swisstable" ], "readme": "README.md", "repository": "https://github.com/rust-lang/hashbrown", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "hashlink", "version": "0.7.0", "id": "hashlink 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "HashMap-like containers that hold their key-value pairs in a user controllable order", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "hashbrown", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.11.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "fxhash", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde_test", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "hashlink", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hashlink-0.7.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "linked_hash_set", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hashlink-0.7.0/tests/linked_hash_set.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "serde", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hashlink-0.7.0/tests/serde.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "lru_cache", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hashlink-0.7.0/tests/lru_cache.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "linked_hash_map", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hashlink-0.7.0/tests/linked_hash_map.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "serde": [ "dep:serde" ], "serde_impl": [ "serde" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hashlink-0.7.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "kyren " ], "categories": [], "keywords": [ "data-structures" ], "readme": "README.md", "repository": "https://github.com/kyren/hashlink", "homepage": null, "documentation": "https://docs.rs/hashlink", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "heck", "version": "0.3.3", "id": "heck 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "heck is a case conversion library.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "unicode-segmentation", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.2.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "heck", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/heck-0.3.3/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/heck-0.3.3/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Without Boats " ], "categories": [], "keywords": [ "string", "case", "camel", "snake", "unicode" ], "readme": "README.md", "repository": "https://github.com/withoutboats/heck", "homepage": "https://github.com/withoutboats/heck", "documentation": "https://docs.rs/heck", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "hermit-abi", "version": "0.1.19", "id": "hermit-abi 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "hermit-abi is small interface to call functions from the unikernel RustyHermit.\nIt is used to build the target `x86_64-unknown-hermit`.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "compiler_builtins", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustc-std-workspace-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.0", "kind": null, "rename": "core", "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "libc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.51", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "hermit-abi", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hermit-abi-0.1.19/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": { "compiler_builtins": [ "dep:compiler_builtins" ], "core": [ "dep:core" ], "default": [], "docs": [], "rustc-dep-of-std": [ "core", "compiler_builtins/rustc-dep-of-std", "libc/rustc-dep-of-std" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hermit-abi-0.1.19/Cargo.toml", "metadata": { "docs": { "rs": { "default-target": "x86_64-unknown-hermit", "features": [ "docs" ] } } }, "publish": null, "authors": [ "Stefan Lankes" ], "categories": [ "os" ], "keywords": [ "unikernel", "libos" ], "readme": "README.md", "repository": "https://github.com/hermitcore/libhermit-rs", "homepage": null, "documentation": "https://hermitcore.github.io/rusty-hermit/hermit_abi", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "hex", "version": "0.4.3", "id": "hex 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Encoding and decoding data into/from hexadecimal representation.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "criterion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "faster-hex", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.5", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "pretty_assertions", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.6", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustc-hex", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^2.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "derive" ], "target": null, "registry": null }, { "name": "serde_json", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "version-sync", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.9", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "hex", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hex-0.4.3/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "version-number", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hex-0.4.3/tests/version-number.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "serde", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hex-0.4.3/tests/serde.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "hex", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hex-0.4.3/benches/hex.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "alloc": [], "default": [ "std" ], "serde": [ "dep:serde" ], "std": [ "alloc" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/hex-0.4.3/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true, "rustdoc-args": [ "--cfg", "docsrs" ] } } }, "publish": null, "authors": [ "KokaKiwi " ], "categories": [ "encoding", "no-std" ], "keywords": [ "no_std", "hex" ], "readme": "README.md", "repository": "https://github.com/KokaKiwi/rust-hex", "homepage": null, "documentation": "https://docs.rs/hex/", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "idna", "version": "0.2.3", "id": "idna 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "IDNA (Internationalizing Domain Names in Applications) and Punycode.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "matches", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "unicode-bidi", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "unicode-normalization", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.17", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "assert_matches", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "bencher", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustc-test", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde_json", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "idna", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/idna-0.2.3/src/lib.rs", "edition": "2018", "doc": true, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/idna-0.2.3/tests/tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "unit", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/idna-0.2.3/tests/unit.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "all", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/idna-0.2.3/benches/all.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/idna-0.2.3/Cargo.toml", "metadata": null, "publish": null, "authors": [ "The rust-url developers" ], "categories": [], "keywords": [], "readme": null, "repository": "https://github.com/servo/rust-url/", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "indexmap", "version": "1.8.1", "id": "indexmap 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Apache-2.0/MIT", "license_file": null, "description": "A hash table with consistent order and fast iteration.\n\nThe indexmap is a hash table where the iteration order of the key-value\npairs is independent of the hash values of the keys. It has the usual\nhash table functionality, it preserves insertion order except after\nremovals, and it allows lookup of its elements by either hash table key\nor numerical index. A corresponding hash set type is also provided.\n\nThis crate was initially published under the name ordermap, but it was renamed to\nindexmap.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "hashbrown", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.11", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [ "raw" ], "target": null, "registry": null }, { "name": "rayon", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.4.1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustc-rayon", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "fnv", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "fxhash", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "itertools", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.10", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "lazy_static", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "quickcheck", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "small_rng" ], "target": null, "registry": null }, { "name": "serde_derive", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "autocfg", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": "build", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "indexmap", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/indexmap-1.8.1/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "equivalent_trait", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/indexmap-1.8.1/tests/equivalent_trait.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/indexmap-1.8.1/tests/tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "macros_full_path", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/indexmap-1.8.1/tests/macros_full_path.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "quick", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/indexmap-1.8.1/tests/quick.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "faststring", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/indexmap-1.8.1/benches/faststring.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "bench", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/indexmap-1.8.1/benches/bench.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/indexmap-1.8.1/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "rayon": [ "dep:rayon" ], "rustc-rayon": [ "dep:rustc-rayon" ], "serde": [ "dep:serde" ], "serde-1": [ "serde" ], "std": [], "test_debug": [], "test_low_transition_point": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/indexmap-1.8.1/Cargo.toml", "metadata": { "docs": { "rs": { "features": [ "serde-1", "rayon" ] } }, "release": { "no-dev-version": true, "tag-name": "{{version}}" } }, "publish": null, "authors": [ "bluss", "Josh Stone " ], "categories": [ "data-structures", "no-std" ], "keywords": [ "hashmap", "no_std" ], "readme": null, "repository": "https://github.com/bluss/indexmap", "homepage": null, "documentation": "https://docs.rs/indexmap/", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "instant", "version": "0.1.12", "id": "instant 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "license": "BSD-3-Clause", "license_file": null, "description": "A partial replacement for std::time::Instant that works on WASM too.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "cfg-if", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "wasm-bindgen-test", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "js-sys", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": "asmjs-unknown-emscripten", "registry": null }, { "name": "stdweb", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": "asmjs-unknown-emscripten", "registry": null }, { "name": "wasm-bindgen", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2", "kind": null, "rename": "wasm-bindgen_rs", "optional": true, "uses_default_features": true, "features": [], "target": "asmjs-unknown-emscripten", "registry": null }, { "name": "web-sys", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [ "Window", "Performance", "PerformanceTiming" ], "target": "asmjs-unknown-emscripten", "registry": null }, { "name": "js-sys", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": "wasm32-unknown-emscripten", "registry": null }, { "name": "stdweb", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": "wasm32-unknown-emscripten", "registry": null }, { "name": "wasm-bindgen", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2", "kind": null, "rename": "wasm-bindgen_rs", "optional": true, "uses_default_features": true, "features": [], "target": "wasm32-unknown-emscripten", "registry": null }, { "name": "web-sys", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [ "Window", "Performance", "PerformanceTiming" ], "target": "wasm32-unknown-emscripten", "registry": null }, { "name": "js-sys", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": "wasm32-unknown-unknown", "registry": null }, { "name": "stdweb", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": "wasm32-unknown-unknown", "registry": null }, { "name": "wasm-bindgen", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2", "kind": null, "rename": "wasm-bindgen_rs", "optional": true, "uses_default_features": true, "features": [], "target": "wasm32-unknown-unknown", "registry": null }, { "name": "web-sys", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [ "Window", "Performance", "PerformanceTiming" ], "target": "wasm32-unknown-unknown", "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "instant", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/instant-0.1.12/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "wasm", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/instant-0.1.12/tests/wasm.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "inaccurate": [], "js-sys": [ "dep:js-sys" ], "now": [], "stdweb": [ "dep:stdweb" ], "wasm-bindgen": [ "js-sys", "wasm-bindgen_rs", "web-sys" ], "wasm-bindgen_rs": [ "dep:wasm-bindgen_rs" ], "web-sys": [ "dep:web-sys" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/instant-0.1.12/Cargo.toml", "metadata": null, "publish": null, "authors": [ "sebcrozet " ], "categories": [], "keywords": [ "time", "wasm" ], "readme": "README.md", "repository": "https://github.com/sebcrozet/instant", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "itertools", "version": "0.10.3", "id": "itertools 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "Extra iterator adaptors, iterator methods, free functions, and macros.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "either", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "criterion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "paste", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "permutohedron", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "quickcheck", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.9", "kind": "dev", "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.7", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "itertools", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "iris", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/examples/iris.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "tuples", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/tests/tuples.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "specializations", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/tests/specializations.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "peeking_take_while", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/tests/peeking_take_while.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_core", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/tests/test_core.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "quick", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/tests/quick.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_std", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/tests/test_std.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "adaptors_no_collect", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/tests/adaptors_no_collect.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "macros_hygiene", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/tests/macros_hygiene.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "zip", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/tests/zip.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "merge_join", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/tests/merge_join.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "flatten_ok", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/tests/flatten_ok.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "tuple_combinations", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/benches/tuple_combinations.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "tuples", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/benches/tuples.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "fold_specialization", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/benches/fold_specialization.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "combinations_with_replacement", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/benches/combinations_with_replacement.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "tree_fold1", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/benches/tree_fold1.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "bench1", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/benches/bench1.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "combinations", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/benches/combinations.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "powerset", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/benches/powerset.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "default": [ "use_std" ], "use_alloc": [], "use_std": [ "use_alloc" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itertools-0.10.3/Cargo.toml", "metadata": { "release": { "no-dev-version": true } }, "publish": null, "authors": [ "bluss" ], "categories": [ "algorithms", "rust-patterns" ], "keywords": [ "iterator", "data-structure", "zip", "product", "group-by" ], "readme": "README.md", "repository": "https://github.com/rust-itertools/itertools", "homepage": null, "documentation": "https://docs.rs/itertools/", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "itoa", "version": "1.0.1", "id": "itoa 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Fast integer primitive to string conversion", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "itoa", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itoa-1.0.1/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itoa-1.0.1/tests/test.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "bench", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itoa-1.0.1/benches/bench.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/itoa-1.0.1/Cargo.toml", "metadata": { "docs": { "rs": { "targets": [ "x86_64-unknown-linux-gnu" ] } } }, "publish": null, "authors": [ "David Tolnay " ], "categories": [ "value-formatting" ], "keywords": [], "readme": "README.md", "repository": "https://github.com/dtolnay/itoa", "homepage": null, "documentation": "https://docs.rs/itoa", "edition": "2018", "links": null, "default_run": null, "rust_version": "1.36" }, { "name": "js-sys", "version": "0.3.57", "id": "js-sys 0.3.57 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "Bindings for all JS global objects and functions in all JS environments like\nNode.js and browsers, built on `#[wasm_bindgen]` using the `wasm-bindgen` crate.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "wasm-bindgen", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.80", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "wasm-bindgen-futures", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.30", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(target_arch = \"wasm32\")", "registry": null }, { "name": "wasm-bindgen-test", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=0.3.30", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(target_arch = \"wasm32\")", "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "js-sys", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/js-sys-0.3.57/src/lib.rs", "edition": "2018", "doc": true, "doctest": false, "test": false }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "headless", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/js-sys-0.3.57/tests/headless.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "wasm", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/js-sys-0.3.57/tests/wasm/main.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/js-sys-0.3.57/Cargo.toml", "metadata": null, "publish": null, "authors": [ "The wasm-bindgen Developers" ], "categories": [ "wasm" ], "keywords": [], "readme": "./README.md", "repository": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/js-sys", "homepage": "https://rustwasm.github.io/wasm-bindgen/", "documentation": "https://docs.rs/js-sys", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "lazy_static", "version": "1.4.0", "id": "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "A macro for declaring lazily evaluated statics in Rust.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "spin", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.5.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "doc-comment", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "lazy_static", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/lazy_static-1.4.0/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "no_std", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/lazy_static-1.4.0/tests/no_std.rs", "edition": "2015", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/lazy_static-1.4.0/tests/test.rs", "edition": "2015", "doc": false, "doctest": false, "test": true } ], "features": { "spin": [ "dep:spin" ], "spin_no_std": [ "spin" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/lazy_static-1.4.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Marvin Löbel " ], "categories": [ "no-std", "rust-patterns", "memory-management" ], "keywords": [ "macro", "lazy", "static" ], "readme": "README.md", "repository": "https://github.com/rust-lang-nursery/lazy-static.rs", "homepage": null, "documentation": "https://docs.rs/lazy_static", "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "libc", "version": "0.2.122", "id": "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Raw FFI bindings to platform libraries like libc.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "rustc-std-workspace-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "libc", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.122/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "const_fn", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.122/tests/const_fn.rs", "edition": "2015", "doc": false, "doctest": false, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.122/build.rs", "edition": "2015", "doc": false, "doctest": false, "test": false } ], "features": { "align": [], "const-extern-fn": [], "default": [ "std" ], "extra_traits": [], "rustc-dep-of-std": [ "align", "rustc-std-workspace-core" ], "rustc-std-workspace-core": [ "dep:rustc-std-workspace-core" ], "std": [], "use_std": [ "std" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.122/Cargo.toml", "metadata": { "docs": { "rs": { "features": [ "const-extern-fn", "extra_traits" ] } } }, "publish": null, "authors": [ "The Rust Project Developers" ], "categories": [ "external-ffi-bindings", "no-std", "os" ], "keywords": [ "libc", "ffi", "bindings", "operating", "system" ], "readme": "README.md", "repository": "https://github.com/rust-lang/libc", "homepage": "https://github.com/rust-lang/libc", "documentation": "https://docs.rs/libc/", "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "libsqlite3-sys", "version": "0.23.2", "id": "libsqlite3-sys 0.23.2 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT", "license_file": null, "description": "Native bindings to the libsqlite3 library", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "openssl-sys", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.9", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "bindgen", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.59", "kind": "build", "rename": null, "optional": true, "uses_default_features": false, "features": [ "runtime" ], "target": null, "registry": null }, { "name": "cc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "build", "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "pkg-config", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.19", "kind": "build", "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "vcpkg", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2", "kind": "build", "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "libsqlite3-sys", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/libsqlite3-sys-0.23.2/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/libsqlite3-sys-0.23.2/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "bindgen": [ "dep:bindgen" ], "buildtime_bindgen": [ "bindgen", "pkg-config", "vcpkg" ], "bundled": [ "cc", "bundled_bindings" ], "bundled-sqlcipher": [ "bundled" ], "bundled-sqlcipher-vendored-openssl": [ "bundled-sqlcipher", "openssl-sys/vendored" ], "bundled-windows": [ "cc", "bundled_bindings" ], "bundled_bindings": [], "cc": [ "dep:cc" ], "default": [ "min_sqlite_version_3_6_8" ], "in_gecko": [], "min_sqlite_version_3_6_23": [ "pkg-config", "vcpkg" ], "min_sqlite_version_3_6_8": [ "pkg-config", "vcpkg" ], "min_sqlite_version_3_7_16": [ "pkg-config", "vcpkg" ], "min_sqlite_version_3_7_7": [ "pkg-config", "vcpkg" ], "openssl-sys": [ "dep:openssl-sys" ], "pkg-config": [ "dep:pkg-config" ], "preupdate_hook": [ "buildtime_bindgen" ], "session": [ "preupdate_hook", "buildtime_bindgen" ], "sqlcipher": [], "unlock_notify": [], "vcpkg": [ "dep:vcpkg" ], "wasm32-wasi-vfs": [], "winsqlite3": [ "min_sqlite_version_3_7_16" ], "with-asan": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/libsqlite3-sys-0.23.2/Cargo.toml", "metadata": null, "publish": null, "authors": [ "The rusqlite developers" ], "categories": [ "external-ffi-bindings" ], "keywords": [ "sqlite", "sqlcipher", "ffi" ], "readme": "README.md", "repository": "https://github.com/rusqlite/rusqlite", "homepage": null, "documentation": null, "edition": "2018", "links": "sqlite3", "default_run": null, "rust_version": null }, { "name": "lock_api", "version": "0.4.7", "id": "lock_api 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Wrappers to create fully-featured Mutex and RwLock types. Compatible with no_std.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "owning_ref", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "scopeguard", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.126", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "autocfg", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.1.0", "kind": "build", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "lock_api", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/lock_api-0.4.7/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/lock_api-0.4.7/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "arc_lock": [], "nightly": [], "owning_ref": [ "dep:owning_ref" ], "serde": [ "dep:serde" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/lock_api-0.4.7/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Amanieu d'Antras " ], "categories": [ "concurrency", "no-std" ], "keywords": [ "mutex", "rwlock", "lock", "no_std" ], "readme": null, "repository": "https://github.com/Amanieu/parking_lot", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "log", "version": "0.4.16", "id": "log 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "A lightweight logging facade for Rust\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "cfg-if", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "sval", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=1.0.0-alpha.5", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "value-bag", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=1.0.0-alpha.8", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "rustversion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "derive" ], "target": null, "registry": null }, { "name": "serde_test", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "sval", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=1.0.0-alpha.5", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "derive" ], "target": null, "registry": null }, { "name": "value-bag", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=1.0.0-alpha.8", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "test" ], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "log", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.16/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "filters", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.16/tests/filters.rs", "edition": "2015", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "macros", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.16/tests/macros.rs", "edition": "2015", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "value", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.16/benches/value.rs", "edition": "2015", "doc": false, "doctest": false, "test": false }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.16/build.rs", "edition": "2015", "doc": false, "doctest": false, "test": false } ], "features": { "kv_unstable": [ "value-bag" ], "kv_unstable_serde": [ "kv_unstable_std", "value-bag/serde", "serde" ], "kv_unstable_std": [ "std", "kv_unstable", "value-bag/error" ], "kv_unstable_sval": [ "kv_unstable", "value-bag/sval", "sval" ], "max_level_debug": [], "max_level_error": [], "max_level_info": [], "max_level_off": [], "max_level_trace": [], "max_level_warn": [], "release_max_level_debug": [], "release_max_level_error": [], "release_max_level_info": [], "release_max_level_off": [], "release_max_level_trace": [], "release_max_level_warn": [], "serde": [ "dep:serde" ], "std": [], "sval": [ "dep:sval" ], "value-bag": [ "dep:value-bag" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/log-0.4.16/Cargo.toml", "metadata": { "docs": { "rs": { "features": [ "std", "serde", "kv_unstable_std", "kv_unstable_sval", "kv_unstable_serde" ] } } }, "publish": null, "authors": [ "The Rust Project Developers" ], "categories": [ "development-tools::debugging" ], "keywords": [ "logging" ], "readme": "README.md", "repository": "https://github.com/rust-lang/log", "homepage": null, "documentation": "https://docs.rs/log", "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "matches", "version": "0.1.9", "id": "matches 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT", "license_file": null, "description": "A macro to evaluate, as a boolean, whether an expression matches a pattern.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "matches", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/matches-0.1.9/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "macro_use_one", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/matches-0.1.9/tests/macro_use_one.rs", "edition": "2015", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "use_star", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/matches-0.1.9/tests/use_star.rs", "edition": "2015", "doc": false, "doctest": false, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/matches-0.1.9/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Simon Sapin " ], "categories": [], "keywords": [], "readme": null, "repository": "https://github.com/SimonSapin/rust-std-candidates", "homepage": null, "documentation": "https://docs.rs/matches/", "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "memchr", "version": "2.4.1", "id": "memchr 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Unlicense/MIT", "license_file": null, "description": "Safe interface to memchr.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "compiler_builtins", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.2", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustc-std-workspace-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.0", "kind": null, "rename": "core", "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "libc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.18", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "quickcheck", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "memchr", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/memchr-2.4.1/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/memchr-2.4.1/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "compiler_builtins": [ "dep:compiler_builtins" ], "core": [ "dep:core" ], "default": [ "std" ], "libc": [ "dep:libc" ], "rustc-dep-of-std": [ "core", "compiler_builtins" ], "std": [], "use_std": [ "std" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/memchr-2.4.1/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Andrew Gallant ", "bluss" ], "categories": [], "keywords": [ "memchr", "char", "scan", "strchr", "string" ], "readme": "README.md", "repository": "https://github.com/BurntSushi/memchr", "homepage": "https://github.com/BurntSushi/memchr", "documentation": "https://docs.rs/memchr/", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "minimal-lexical", "version": "0.2.1", "id": "minimal-lexical 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "Fast float parsing conversion routines.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "minimal-lexical", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/minimal-lexical-0.2.1/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "libm_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/minimal-lexical-0.2.1/tests/libm_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "number_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/minimal-lexical-0.2.1/tests/number_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "vec_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/minimal-lexical-0.2.1/tests/vec_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "rounding_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/minimal-lexical-0.2.1/tests/rounding_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "bellerophon", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/minimal-lexical-0.2.1/tests/bellerophon.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "slow_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/minimal-lexical-0.2.1/tests/slow_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "parse_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/minimal-lexical-0.2.1/tests/parse_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "mask_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/minimal-lexical-0.2.1/tests/mask_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "lemire_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/minimal-lexical-0.2.1/tests/lemire_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "integration_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/minimal-lexical-0.2.1/tests/integration_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "stackvec", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/minimal-lexical-0.2.1/tests/stackvec.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "bellerophon_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/minimal-lexical-0.2.1/tests/bellerophon_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "alloc": [], "compact": [], "default": [ "std" ], "lint": [], "nightly": [], "std": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/minimal-lexical-0.2.1/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Alex Huszagh " ], "categories": [ "parsing", "no-std" ], "keywords": [ "parsing", "no_std" ], "readme": "README.md", "repository": "https://github.com/Alexhuszagh/minimal-lexical", "homepage": null, "documentation": "https://docs.rs/minimal-lexical", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "mio", "version": "0.8.2", "id": "mio 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT", "license_file": null, "description": "Lightweight non-blocking IO", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "log", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.8", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "env_logger", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8.4", "kind": "dev", "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "libc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.86", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(target_os = \"wasi\")", "registry": null }, { "name": "wasi", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.11.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(target_os = \"wasi\")", "registry": null }, { "name": "libc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.86", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(unix)", "registry": null }, { "name": "miow", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.6", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(windows)", "registry": null }, { "name": "ntapi", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(windows)", "registry": null }, { "name": "winapi", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [ "winsock2", "mswsock" ], "target": "cfg(windows)", "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "mio", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/mio-0.8.2/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "tcp_server", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/mio-0.8.2/examples/tcp_server.rs", "edition": "2018", "required-features": [ "os-poll", "net" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "tcp_listenfd_server", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/mio-0.8.2/examples/tcp_listenfd_server.rs", "edition": "2018", "required-features": [ "os-poll", "net" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "udp_server", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/mio-0.8.2/examples/udp_server.rs", "edition": "2018", "required-features": [ "os-poll", "net" ], "doc": false, "doctest": false, "test": false } ], "features": { "default": [], "net": [], "os-ext": [ "os-poll" ], "os-poll": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/mio-0.8.2/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true, "rustdoc-args": [ "--cfg", "docsrs" ], "targets": [ "aarch64-apple-ios", "aarch64-linux-android", "x86_64-apple-darwin", "x86_64-pc-windows-msvc", "x86_64-unknown-dragonfly", "x86_64-unknown-freebsd", "x86_64-unknown-illumos", "x86_64-unknown-linux-gnu", "x86_64-unknown-netbsd", "x86_64-unknown-openbsd" ] } }, "playground": { "features": [ "os-poll", "os-ext", "net" ] } }, "publish": null, "authors": [ "Carl Lerche ", "Thomas de Zeeuw ", "Tokio Contributors " ], "categories": [ "asynchronous" ], "keywords": [ "io", "async", "non-blocking" ], "readme": "README.md", "repository": "https://github.com/tokio-rs/mio", "homepage": "https://github.com/tokio-rs/mio", "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "miow", "version": "0.3.7", "id": "miow 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "A zero overhead I/O library for Windows, focusing on IOCP and Async I/O\nabstractions.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "winapi", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.3", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [ "std", "fileapi", "handleapi", "ioapiset", "minwindef", "namedpipeapi", "ntdef", "synchapi", "winerror", "winsock2", "ws2def", "ws2ipdef" ], "target": null, "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "socket2", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "miow", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/miow-0.3.7/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/miow-0.3.7/Cargo.toml", "metadata": { "docs": { "rs": { "default-target": "x86_64-pc-windows-msvc", "targets": [ "aarch64-pc-windows-msvc", "i686-pc-windows-msvc", "x86_64-pc-windows-msvc" ] } } }, "publish": null, "authors": [ "Alex Crichton " ], "categories": [], "keywords": [ "iocp", "windows", "io", "overlapped" ], "readme": "README.md", "repository": "https://github.com/yoshuawuyts/miow", "homepage": "https://github.com/yoshuawuyts/miow", "documentation": "https://docs.rs/miow/0.3/x86_64-pc-windows-msvc/miow/", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "nom", "version": "7.1.1", "id": "nom 7.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT", "license_file": null, "description": "A byte-oriented, zero-copy, parser combinators library", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "memchr", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^2.3", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "minimal-lexical", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.0", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "doc-comment", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "proptest", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "nom", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "json", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/examples/json.rs", "edition": "2018", "required-features": [ "alloc" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "iterator", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/examples/iterator.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "s_expression", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/examples/s_expression.rs", "edition": "2018", "required-features": [ "alloc" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "string", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/examples/string.rs", "edition": "2018", "required-features": [ "alloc" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "arithmetic", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/tests/arithmetic.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "arithmetic_ast", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/tests/arithmetic_ast.rs", "edition": "2018", "required-features": [ "alloc" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "css", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/tests/css.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "custom_errors", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/tests/custom_errors.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "float", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/tests/float.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "ini", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/tests/ini.rs", "edition": "2018", "required-features": [ "alloc" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "ini_str", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/tests/ini_str.rs", "edition": "2018", "required-features": [ "alloc" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "issues", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/tests/issues.rs", "edition": "2018", "required-features": [ "alloc" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "json", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/tests/json.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "mp4", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/tests/mp4.rs", "edition": "2018", "required-features": [ "alloc" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "multiline", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/tests/multiline.rs", "edition": "2018", "required-features": [ "alloc" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "overflow", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/tests/overflow.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "reborrow_fold", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/tests/reborrow_fold.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "fnmut", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/tests/fnmut.rs", "edition": "2018", "required-features": [ "alloc" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "escaped", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/tests/escaped.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "alloc": [], "default": [ "std" ], "docsrs": [], "std": [ "alloc", "memchr/std", "minimal-lexical/std" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/nom-7.1.1/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true, "features": [ "alloc", "std", "docsrs" ] } } }, "publish": null, "authors": [ "contact@geoffroycouprie.com" ], "categories": [ "parsing" ], "keywords": [ "parser", "parser-combinators", "parsing", "streaming", "bit" ], "readme": "README.md", "repository": "https://github.com/Geal/nom", "homepage": null, "documentation": "https://docs.rs/nom", "edition": "2018", "links": null, "default_run": null, "rust_version": "1.48" }, { "name": "ntapi", "version": "0.3.7", "id": "ntapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Apache-2.0 OR MIT", "license_file": null, "description": "FFI bindings for Native API", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "winapi", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.9", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [ "cfg", "evntrace", "in6addr", "inaddr", "minwinbase", "ntsecapi", "windef", "winioctl" ], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "ntapi", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ntapi-0.3.7/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ntapi-0.3.7/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "beta": [], "default": [ "user" ], "func-types": [], "impl-default": [ "winapi/impl-default" ], "kernel": [], "nightly": [ "beta" ], "user": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ntapi-0.3.7/Cargo.toml", "metadata": { "docs": { "rs": { "default-target": "x86_64-pc-windows-msvc", "features": [ "beta" ], "targets": [ "aarch64-pc-windows-msvc", "i686-pc-windows-msvc", "x86_64-pc-windows-msvc" ] } } }, "publish": null, "authors": [ "MSxDOS " ], "categories": [ "external-ffi-bindings", "no-std", "os::windows-apis" ], "keywords": [ "windows", "ffi", "ntapi", "native", "win32" ], "readme": "README.md", "repository": "https://github.com/MSxDOS/ntapi", "homepage": null, "documentation": "https://docs.rs/ntapi/*/x86_64-pc-windows-msvc/ntapi/", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "num-traits", "version": "0.2.14", "id": "num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Numeric traits for generic mathematics", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "libm", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "autocfg", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": "build", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "num-traits", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/num-traits-0.2.14/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "cast", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/num-traits-0.2.14/tests/cast.rs", "edition": "2015", "doc": false, "doctest": false, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/num-traits-0.2.14/build.rs", "edition": "2015", "doc": false, "doctest": false, "test": false } ], "features": { "default": [ "std" ], "i128": [], "libm": [ "dep:libm" ], "std": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/num-traits-0.2.14/Cargo.toml", "metadata": { "docs": { "rs": { "features": [ "std" ] } } }, "publish": null, "authors": [ "The Rust Project Developers" ], "categories": [ "algorithms", "science", "no-std" ], "keywords": [ "mathematics", "numerics" ], "readme": "README.md", "repository": "https://github.com/rust-num/num-traits", "homepage": "https://github.com/rust-num/num-traits", "documentation": "https://docs.rs/num-traits", "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "num_cpus", "version": "1.13.1", "id": "num_cpus 1.13.1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Get the number of CPUs on a machine.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "hermit-abi", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.3", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(all(any(target_arch = \"x86_64\", target_arch = \"aarch64\"), target_os = \"hermit\"))", "registry": null }, { "name": "libc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.26", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(not(windows))", "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "num_cpus", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/num_cpus-1.13.1/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "values", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/num_cpus-1.13.1/examples/values.rs", "edition": "2015", "doc": false, "doctest": false, "test": false } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/num_cpus-1.13.1/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Sean McArthur " ], "categories": [ "hardware-support" ], "keywords": [ "cpu", "cpus", "cores" ], "readme": "README.md", "repository": "https://github.com/seanmonstar/num_cpus", "homepage": null, "documentation": "https://docs.rs/num_cpus", "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "once_cell", "version": "1.10.0", "id": "once_cell 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Single assignment cells and lazy values.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "atomic-polyfill", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "parking_lot", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.12", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "crossbeam-utils", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.7.2", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "lazy_static", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "regex", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.2.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "once_cell", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/once_cell-1.10.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "bench", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/once_cell-1.10.0/examples/bench.rs", "edition": "2018", "required-features": [ "std" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "bench_acquire", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/once_cell-1.10.0/examples/bench_acquire.rs", "edition": "2018", "required-features": [ "std" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "bench_vs_lazy_static", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/once_cell-1.10.0/examples/bench_vs_lazy_static.rs", "edition": "2018", "required-features": [ "std" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "lazy_static", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/once_cell-1.10.0/examples/lazy_static.rs", "edition": "2018", "required-features": [ "std" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "reentrant_init_deadlocks", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/once_cell-1.10.0/examples/reentrant_init_deadlocks.rs", "edition": "2018", "required-features": [ "std" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "regex", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/once_cell-1.10.0/examples/regex.rs", "edition": "2018", "required-features": [ "std" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "test_synchronization", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/once_cell-1.10.0/examples/test_synchronization.rs", "edition": "2018", "required-features": [ "std" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "it", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/once_cell-1.10.0/tests/it.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "alloc": [ "race" ], "atomic-polyfill": [ "dep:atomic-polyfill" ], "default": [ "std" ], "parking_lot": [ "dep:parking_lot" ], "race": [], "std": [ "alloc" ], "unstable": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/once_cell-1.10.0/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true } } }, "publish": null, "authors": [ "Aleksey Kladov " ], "categories": [ "rust-patterns", "memory-management" ], "keywords": [ "lazy", "static" ], "readme": "README.md", "repository": "https://github.com/matklad/once_cell", "homepage": null, "documentation": "https://docs.rs/once_cell", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "opaque-debug", "version": "0.3.0", "id": "opaque-debug 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Macro for opaque Debug trait implementation", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "opaque-debug", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/opaque-debug-0.3.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "mod", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/opaque-debug-0.3.0/tests/mod.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/opaque-debug-0.3.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "RustCrypto Developers" ], "categories": [], "keywords": [], "readme": null, "repository": "https://github.com/RustCrypto/utils", "homepage": null, "documentation": "https://docs.rs/opaque-debug", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "parking_lot", "version": "0.11.2", "id": "parking_lot 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Apache-2.0/MIT", "license_file": null, "description": "More compact and efficient implementations of the standard synchronization primitives.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "instant", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.9", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "lock_api", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.5", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "parking_lot_core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8.4", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "bincode", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.3.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "parking_lot", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/parking_lot-0.11.2/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "issue_203", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/parking_lot-0.11.2/tests/issue_203.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "arc_lock": [ "lock_api/arc_lock" ], "deadlock_detection": [ "parking_lot_core/deadlock_detection" ], "default": [], "nightly": [ "parking_lot_core/nightly", "lock_api/nightly" ], "owning_ref": [ "lock_api/owning_ref" ], "send_guard": [], "serde": [ "lock_api/serde" ], "stdweb": [ "instant/stdweb" ], "wasm-bindgen": [ "instant/wasm-bindgen" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/parking_lot-0.11.2/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Amanieu d'Antras " ], "categories": [ "concurrency" ], "keywords": [ "mutex", "condvar", "rwlock", "once", "thread" ], "readme": "README.md", "repository": "https://github.com/Amanieu/parking_lot", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "parking_lot_core", "version": "0.8.5", "id": "parking_lot_core 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Apache-2.0/MIT", "license_file": null, "description": "An advanced API for creating custom synchronization primitives.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "backtrace", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.60", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "cfg-if", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "instant", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.9", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "petgraph", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.5.1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "smallvec", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.6.1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "thread-id", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^4.0.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "redox_syscall", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.8", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(target_os = \"redox\")", "registry": null }, { "name": "libc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.95", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(unix)", "registry": null }, { "name": "winapi", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.9", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [ "winnt", "ntstatus", "minwindef", "winerror", "winbase", "errhandlingapi", "handleapi" ], "target": "cfg(windows)", "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "parking_lot_core", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/parking_lot_core-0.8.5/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/parking_lot_core-0.8.5/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "backtrace": [ "dep:backtrace" ], "deadlock_detection": [ "petgraph", "thread-id", "backtrace" ], "nightly": [], "petgraph": [ "dep:petgraph" ], "thread-id": [ "dep:thread-id" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/parking_lot_core-0.8.5/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Amanieu d'Antras " ], "categories": [ "concurrency" ], "keywords": [ "mutex", "condvar", "rwlock", "once", "thread" ], "readme": null, "repository": "https://github.com/Amanieu/parking_lot", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "paste", "version": "1.0.7", "id": "paste 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Macros for all your token pasting needs", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "paste-test-suite", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustversion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "trybuild", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.49", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "diff" ], "target": null, "registry": null } ], "targets": [ { "kind": [ "proc-macro" ], "crate_types": [ "proc-macro" ], "name": "paste", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/paste-1.0.7/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_attr", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/paste-1.0.7/tests/test_attr.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_doc", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/paste-1.0.7/tests/test_doc.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "compiletest", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/paste-1.0.7/tests/compiletest.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_expr", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/paste-1.0.7/tests/test_expr.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_item", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/paste-1.0.7/tests/test_item.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/paste-1.0.7/Cargo.toml", "metadata": { "docs": { "rs": { "targets": [ "x86_64-unknown-linux-gnu" ] } } }, "publish": null, "authors": [ "David Tolnay " ], "categories": [ "no-std" ], "keywords": [], "readme": "README.md", "repository": "https://github.com/dtolnay/paste", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": "1.31" }, { "name": "percent-encoding", "version": "2.1.0", "id": "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "Percent encoding and decoding", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "percent-encoding", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/percent-encoding-2.1.0/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": false } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/percent-encoding-2.1.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "The rust-url developers" ], "categories": [], "keywords": [], "readme": null, "repository": "https://github.com/servo/rust-url/", "homepage": null, "documentation": null, "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "pin-project", "version": "1.0.10", "id": "pin-project 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Apache-2.0 OR MIT", "license_file": null, "description": "A crate for safe and ergonomic pin-projection.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "pin-project-internal", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=1.0.10", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "macrotest", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.8", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustversion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "static_assertions", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "trybuild", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.49", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "pin-project", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "not_unpin-expanded", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/examples/not_unpin-expanded.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "pinned_drop", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/examples/pinned_drop.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "pinned_drop-expanded", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/examples/pinned_drop-expanded.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "project_replace", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/examples/project_replace.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "not_unpin", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/examples/not_unpin.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "project_replace-expanded", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/examples/project_replace-expanded.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "unsafe_unpin-expanded", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/examples/unsafe_unpin-expanded.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "enum-default", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/examples/enum-default.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "enum-default-expanded", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/examples/enum-default-expanded.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "unsafe_unpin", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/examples/unsafe_unpin.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "struct-default", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/examples/struct-default.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "struct-default-expanded", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/examples/struct-default-expanded.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "cfg", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/tests/cfg.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "pinned_drop", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/tests/pinned_drop.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "proper_unpin", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/tests/proper_unpin.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "pin_project", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/tests/pin_project.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "drop_order", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/tests/drop_order.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "compiletest", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/tests/compiletest.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "expandtest", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/tests/expandtest.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "repr_packed", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/tests/repr_packed.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "unsafe_unpin", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/tests/unsafe_unpin.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "lint", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/tests/lint.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-1.0.10/Cargo.toml", "metadata": { "docs": { "rs": { "targets": [ "x86_64-unknown-linux-gnu" ] } } }, "publish": null, "authors": [], "categories": [ "no-std", "rust-patterns" ], "keywords": [ "pin", "macros", "attribute" ], "readme": "README.md", "repository": "https://github.com/taiki-e/pin-project", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": "1.37" }, { "name": "pin-project-internal", "version": "1.0.10", "id": "pin-project-internal 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Apache-2.0 OR MIT", "license_file": null, "description": "Implementation detail of the `pin-project` crate.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "proc-macro2", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "quote", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "syn", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.56", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [ "full", "visit-mut" ], "target": null, "registry": null } ], "targets": [ { "kind": [ "proc-macro" ], "crate_types": [ "proc-macro" ], "name": "pin-project-internal", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-internal-1.0.10/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-internal-1.0.10/Cargo.toml", "metadata": { "docs": { "rs": { "targets": [ "x86_64-unknown-linux-gnu" ] } } }, "publish": null, "authors": [], "categories": [ "no-std", "rust-patterns" ], "keywords": [ "pin", "macros", "attribute" ], "readme": null, "repository": "https://github.com/taiki-e/pin-project", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "pin-project-lite", "version": "0.2.8", "id": "pin-project-lite 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Apache-2.0 OR MIT", "license_file": null, "description": "A lightweight version of pin-project written with declarative macros.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "macrotest", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.8", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustversion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "static_assertions", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "trybuild", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.49", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "pin-project-lite", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-lite-0.2.8/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "proper_unpin", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-lite-0.2.8/tests/proper_unpin.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "drop_order", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-lite-0.2.8/tests/drop_order.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "compiletest", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-lite-0.2.8/tests/compiletest.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "expandtest", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-lite-0.2.8/tests/expandtest.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "lint", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-lite-0.2.8/tests/lint.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-lite-0.2.8/tests/test.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-project-lite-0.2.8/Cargo.toml", "metadata": { "docs": { "rs": { "targets": [ "x86_64-unknown-linux-gnu" ] } } }, "publish": null, "authors": [], "categories": [ "no-std", "rust-patterns" ], "keywords": [ "pin", "macros" ], "readme": "README.md", "repository": "https://github.com/taiki-e/pin-project-lite", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": "1.37" }, { "name": "pin-utils", "version": "0.1.0", "id": "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Utilities for pinning\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "pin-utils", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-utils-0.1.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "stack_pin", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-utils-0.1.0/tests/stack_pin.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "projection", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-utils-0.1.0/tests/projection.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pin-utils-0.1.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Josef Brandl " ], "categories": [], "keywords": [], "readme": "README.md", "repository": "https://github.com/rust-lang-nursery/pin-utils", "homepage": null, "documentation": "https://docs.rs/pin-utils", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "pkg-config", "version": "0.3.25", "id": "pkg-config 0.3.25 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "A library to run the pkg-config system tool at build time in order to be used in\nCargo build scripts.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "lazy_static", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "pkg-config", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pkg-config-0.3.25/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pkg-config-0.3.25/tests/test.rs", "edition": "2015", "doc": false, "doctest": false, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/pkg-config-0.3.25/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Alex Crichton " ], "categories": [], "keywords": [ "build-dependencies" ], "readme": "README.md", "repository": "https://github.com/rust-lang/pkg-config-rs", "homepage": null, "documentation": "https://docs.rs/pkg-config", "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "proc-macro2", "version": "1.0.37", "id": "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "A substitute implementation of the compiler's `proc_macro` API to decouple\ntoken-based libraries from the procedural macro use case.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "unicode-xid", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.2", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "quote", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "proc-macro2", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.37/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "features", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.37/tests/features.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_fmt", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.37/tests/test_fmt.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "marker", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.37/tests/marker.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "comments", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.37/tests/comments.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.37/tests/test.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.37/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "default": [ "proc-macro" ], "nightly": [], "proc-macro": [], "span-locations": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/proc-macro2-1.0.37/Cargo.toml", "metadata": { "docs": { "rs": { "rustc-args": [ "--cfg", "procmacro2_semver_exempt" ], "rustdoc-args": [ "--cfg", "procmacro2_semver_exempt", "--cfg", "doc_cfg" ], "targets": [ "x86_64-unknown-linux-gnu" ] } }, "playground": { "features": [ "span-locations" ] } }, "publish": null, "authors": [ "David Tolnay ", "Alex Crichton " ], "categories": [ "development-tools::procedural-macro-helpers" ], "keywords": [ "macros" ], "readme": "README.md", "repository": "https://github.com/dtolnay/proc-macro2", "homepage": null, "documentation": "https://docs.rs/proc-macro2", "edition": "2018", "links": null, "default_run": null, "rust_version": "1.31" }, { "name": "quote", "version": "1.0.17", "id": "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Quasi-quoting macro quote!(...)", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "proc-macro2", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.36", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "rustversion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "trybuild", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.52", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "diff" ], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "quote", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/quote-1.0.17/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "compiletest", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/quote-1.0.17/tests/compiletest.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/quote-1.0.17/tests/test.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "default": [ "proc-macro" ], "proc-macro": [ "proc-macro2/proc-macro" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/quote-1.0.17/Cargo.toml", "metadata": { "docs": { "rs": { "targets": [ "x86_64-unknown-linux-gnu" ] } } }, "publish": null, "authors": [ "David Tolnay " ], "categories": [ "development-tools::procedural-macro-helpers" ], "keywords": [ "syn" ], "readme": "README.md", "repository": "https://github.com/dtolnay/quote", "homepage": null, "documentation": "https://docs.rs/quote/", "edition": "2018", "links": null, "default_run": null, "rust_version": "1.31" }, { "name": "redox_syscall", "version": "0.2.13", "id": "redox_syscall 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT", "license_file": null, "description": "A Rust library to access raw Redox system calls", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "bitflags", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "syscall", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/redox_syscall-0.2.13/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/redox_syscall-0.2.13/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Jeremy Soller " ], "categories": [], "keywords": [], "readme": "README.md", "repository": "https://gitlab.redox-os.org/redox-os/syscall", "homepage": null, "documentation": "https://docs.rs/redox_syscall", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "ring", "version": "0.16.20", "id": "ring 0.16.20 (registry+https://github.com/rust-lang/crates.io-index)", "license": null, "license_file": "LICENSE", "description": "Safe, fast, small crypto using Rust.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "untrusted", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.7.1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "cc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.62", "kind": "build", "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "web-sys", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.37", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [ "Crypto", "Window" ], "target": "cfg(all(target_arch = \"wasm32\", target_vendor = \"unknown\", target_os = \"unknown\", target_env = \"\"))", "registry": null }, { "name": "spin", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.5.2", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": "cfg(any(target_arch = \"x86\", target_arch = \"x86_64\", all(any(target_arch = \"aarch64\", target_arch = \"arm\"), any(target_os = \"android\", target_os = \"fuchsia\", target_os = \"linux\"))))", "registry": null }, { "name": "libc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.69", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": "cfg(any(target_os = \"android\", target_os = \"linux\"))", "registry": null }, { "name": "once_cell", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.5.2", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [ "std" ], "target": "cfg(any(target_os = \"android\", target_os = \"linux\"))", "registry": null }, { "name": "once_cell", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.5.2", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [ "std" ], "target": "cfg(any(target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"illumos\", target_os = \"netbsd\", target_os = \"openbsd\", target_os = \"solaris\"))", "registry": null }, { "name": "libc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.80", "kind": "dev", "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": "cfg(any(unix, windows))", "registry": null }, { "name": "wasm-bindgen-test", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.18", "kind": "dev", "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": "cfg(target_arch = \"wasm32\")", "registry": null }, { "name": "winapi", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.8", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [ "ntsecapi", "wtypesbase" ], "target": "cfg(target_os = \"windows\")", "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "ring", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "signature_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/tests/signature_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "rsa_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/tests/rsa_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "hmac_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/tests/hmac_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "constant_time_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/tests/constant_time_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "pbkdf2_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/tests/pbkdf2_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "ecdsa_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/tests/ecdsa_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "rand_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/tests/rand_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "hkdf_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/tests/hkdf_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "agreement_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/tests/agreement_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "quic_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/tests/quic_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "aead_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/tests/aead_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "digest_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/tests/digest_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "ed25519_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/tests/ed25519_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "alloc": [], "default": [ "alloc", "dev_urandom_fallback" ], "dev_urandom_fallback": [ "once_cell" ], "internal_benches": [], "once_cell": [ "dep:once_cell" ], "slow_tests": [], "std": [ "alloc" ], "test_logging": [], "wasm32_c": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ring-0.16.20/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true } } }, "publish": null, "authors": [ "Brian Smith " ], "categories": [ "cryptography", "no-std" ], "keywords": [ "crypto", "cryptography", "rand", "ECC", "RSA" ], "readme": "doc/link-to-readme.md", "repository": "https://github.com/briansmith/ring", "homepage": null, "documentation": "https://briansmith.org/rustdoc/ring/", "edition": "2018", "links": "ring-asm", "default_run": null, "rust_version": null }, { "name": "rustls", "version": "0.19.1", "id": "rustls 0.19.1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Apache-2.0/ISC/MIT", "license_file": null, "description": "Rustls is a modern TLS library written in Rust.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "base64", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.13.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "log", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.4", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "ring", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.16.11", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "sct", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.6.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "webpki", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.21.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "criterion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "env_logger", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8.2", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "log", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.4", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "webpki-roots", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.21", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "rustls", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/rustls-0.19.1/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "bogo_shim", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/rustls-0.19.1/examples/internal/bogo_shim.rs", "edition": "2018", "required-features": [ "dangerous_configuration", "quic" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "trytls_shim", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/rustls-0.19.1/examples/internal/trytls_shim.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "bench", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/rustls-0.19.1/examples/internal/bench.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "simple_0rtt_client", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/rustls-0.19.1/examples/simple_0rtt_client.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "limitedclient", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/rustls-0.19.1/examples/limitedclient.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "simpleclient", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/rustls-0.19.1/examples/simpleclient.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "benchmarks", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/rustls-0.19.1/tests/benchmarks.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "api", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/rustls-0.19.1/tests/api.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "benchmarks", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/rustls-0.19.1/tests/benchmarks.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "dangerous_configuration": [], "default": [ "logging" ], "log": [ "dep:log" ], "logging": [ "log" ], "quic": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/rustls-0.19.1/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true, "rustdoc-args": [ "--cfg", "docsrs" ] } } }, "publish": null, "authors": [ "Joseph Birr-Pixton " ], "categories": [ "network-programming", "cryptography" ], "keywords": [], "readme": "../README.md", "repository": "https://github.com/ctz/rustls", "homepage": "https://github.com/ctz/rustls", "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "ryu", "version": "1.0.9", "id": "ryu 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Apache-2.0 OR BSL-1.0", "license_file": null, "description": "Fast floating point to string conversion", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "no-panic", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "num_cpus", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.8", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rand_xorshift", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "ryu", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ryu-1.0.9/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "upstream_benchmark", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ryu-1.0.9/examples/upstream_benchmark.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "d2s_test", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ryu-1.0.9/tests/d2s_test.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "common_test", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ryu-1.0.9/tests/common_test.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "f2s_test", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ryu-1.0.9/tests/f2s_test.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "s2d_test", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ryu-1.0.9/tests/s2d_test.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "d2s_table_test", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ryu-1.0.9/tests/d2s_table_test.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "exhaustive", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ryu-1.0.9/tests/exhaustive.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "s2f_test", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ryu-1.0.9/tests/s2f_test.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "bench", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ryu-1.0.9/benches/bench.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "no-panic": [ "dep:no-panic" ], "small": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/ryu-1.0.9/Cargo.toml", "metadata": { "docs": { "rs": { "targets": [ "x86_64-unknown-linux-gnu" ] } } }, "publish": null, "authors": [ "David Tolnay " ], "categories": [ "value-formatting" ], "keywords": [], "readme": "README.md", "repository": "https://github.com/dtolnay/ryu", "homepage": null, "documentation": "https://docs.rs/ryu", "edition": "2018", "links": null, "default_run": null, "rust_version": "1.36" }, { "name": "scopeguard", "version": "1.1.0", "id": "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "A RAII scope guard that will run a given closure when it goes out of scope,\neven if the code between panics (assuming unwinding panic).\n\nDefines the macros `defer!`, `defer_on_unwind!`, `defer_on_success!` as\nshorthands for guards with one of the implemented strategies.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "scopeguard", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/scopeguard-1.1.0/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "readme", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/scopeguard-1.1.0/examples/readme.rs", "edition": "2015", "doc": false, "doctest": false, "test": false } ], "features": { "default": [ "use_std" ], "use_std": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/scopeguard-1.1.0/Cargo.toml", "metadata": { "release": { "no-dev-version": true } }, "publish": null, "authors": [ "bluss" ], "categories": [ "rust-patterns", "no-std" ], "keywords": [ "scope-guard", "defer", "panic", "unwind" ], "readme": null, "repository": "https://github.com/bluss/scopeguard", "homepage": null, "documentation": "https://docs.rs/scopeguard/", "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "sct", "version": "0.6.1", "id": "sct 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Apache-2.0/ISC/MIT", "license_file": null, "description": "Certificate transparency SCT verification library", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "ring", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.16.20", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "untrusted", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.7.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "sct", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sct-0.6.1/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sct-0.6.1/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Joseph Birr-Pixton " ], "categories": [ "network-programming", "cryptography" ], "keywords": [], "readme": "README.md", "repository": "https://github.com/ctz/sct.rs", "homepage": "https://github.com/ctz/sct.rs", "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "serde", "version": "1.0.136", "id": "serde 1.0.136 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "A generic serialization/deserialization framework", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "serde_derive", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=1.0.136", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde_derive", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "serde", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.136/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.136/build.rs", "edition": "2015", "doc": false, "doctest": false, "test": false } ], "features": { "alloc": [], "default": [ "std" ], "derive": [ "serde_derive" ], "rc": [], "serde_derive": [ "dep:serde_derive" ], "std": [], "unstable": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/serde-1.0.136/Cargo.toml", "metadata": { "docs": { "rs": { "targets": [ "x86_64-unknown-linux-gnu" ] } }, "playground": { "features": [ "derive", "rc" ] } }, "publish": null, "authors": [ "Erick Tryzelaar ", "David Tolnay " ], "categories": [ "encoding" ], "keywords": [ "serde", "serialization", "no_std" ], "readme": "crates-io.md", "repository": "https://github.com/serde-rs/serde", "homepage": "https://serde.rs", "documentation": "https://docs.serde.rs/serde/", "edition": "2015", "links": null, "default_run": null, "rust_version": "1.15" }, { "name": "serde_derive", "version": "1.0.136", "id": "serde_derive 1.0.136 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Macros 1.1 implementation of #[derive(Serialize, Deserialize)]", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "proc-macro2", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "quote", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "syn", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.60", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "proc-macro" ], "crate_types": [ "proc-macro" ], "name": "serde_derive", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/serde_derive-1.0.136/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/serde_derive-1.0.136/build.rs", "edition": "2015", "doc": false, "doctest": false, "test": false } ], "features": { "default": [], "deserialize_in_place": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/serde_derive-1.0.136/Cargo.toml", "metadata": { "docs": { "rs": { "targets": [ "x86_64-unknown-linux-gnu" ] } } }, "publish": null, "authors": [ "Erick Tryzelaar ", "David Tolnay " ], "categories": [], "keywords": [ "serde", "serialization", "no_std" ], "readme": "crates-io.md", "repository": "https://github.com/serde-rs/serde", "homepage": "https://serde.rs", "documentation": "https://serde.rs/derive.html", "edition": "2015", "links": null, "default_run": null, "rust_version": "1.31" }, { "name": "serde_json", "version": "1.0.79", "id": "serde_json 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "A JSON serialization file format", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "indexmap", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.5", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "itoa", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "ryu", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.100", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "automod", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "ref-cast", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustversion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde_bytes", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.11", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde_derive", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde_stacker", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "trybuild", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.49", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "diff" ], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "serde_json", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.79/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "regression", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.79/tests/regression.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "stream", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.79/tests/stream.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "compiletest", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.79/tests/compiletest.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "map", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.79/tests/map.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "lexical", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.79/tests/lexical.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "debug", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.79/tests/debug.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.79/tests/test.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.79/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "alloc": [ "serde/alloc" ], "arbitrary_precision": [], "default": [ "std" ], "float_roundtrip": [], "indexmap": [ "dep:indexmap" ], "preserve_order": [ "indexmap" ], "raw_value": [], "std": [ "serde/std" ], "unbounded_depth": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.79/Cargo.toml", "metadata": { "docs": { "rs": { "features": [ "raw_value", "unbounded_depth" ], "targets": [ "x86_64-unknown-linux-gnu" ], "rustdoc-args": [ "--cfg", "docsrs" ] } }, "playground": { "features": [ "raw_value" ] } }, "publish": null, "authors": [ "Erick Tryzelaar ", "David Tolnay " ], "categories": [ "encoding" ], "keywords": [ "json", "serde", "serialization" ], "readme": "README.md", "repository": "https://github.com/serde-rs/json", "homepage": null, "documentation": "https://docs.serde.rs/serde_json/", "edition": "2018", "links": null, "default_run": null, "rust_version": "1.36" }, { "name": "sha2", "version": "0.9.9", "id": "sha2 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Pure Rust implementation of the SHA-2 hash function family\nincluding SHA-224, SHA-256, SHA-384, and SHA-512.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "block-buffer", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.9", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "cfg-if", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "digest", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.9", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "opaque-debug", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "sha2-asm", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.6.1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "digest", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.9", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "dev" ], "target": null, "registry": null }, { "name": "hex-literal", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "cpufeatures", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(any(target_arch = \"aarch64\", target_arch = \"x86_64\", target_arch = \"x86\"))", "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "sha2", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sha2-0.9.9/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "sha512sum", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sha2-0.9.9/examples/sha512sum.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "sha256sum", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sha2-0.9.9/examples/sha256sum.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "lib", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sha2-0.9.9/tests/lib.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "sha512", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sha2-0.9.9/benches/sha512.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "sha256", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sha2-0.9.9/benches/sha256.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "asm": [ "sha2-asm" ], "asm-aarch64": [ "asm" ], "compress": [], "default": [ "std" ], "force-soft": [], "sha2-asm": [ "dep:sha2-asm" ], "std": [ "digest/std" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sha2-0.9.9/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true, "rustdoc-args": [ "--cfg", "docsrs" ] } } }, "publish": null, "authors": [ "RustCrypto Developers" ], "categories": [ "cryptography", "no-std" ], "keywords": [ "crypto", "sha2", "hash", "digest" ], "readme": "README.md", "repository": "https://github.com/RustCrypto/hashes", "homepage": null, "documentation": "https://docs.rs/sha2", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "slab", "version": "0.4.6", "id": "slab 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT", "license_file": null, "description": "Pre-allocated storage for a uniform data type", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.95", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [ "alloc" ], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "derive" ], "target": null, "registry": null }, { "name": "serde_test", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "slab", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/slab-0.4.6/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "serde", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/slab-0.4.6/tests/serde.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "slab", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/slab-0.4.6/tests/slab.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "default": [ "std" ], "serde": [ "dep:serde" ], "std": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/slab-0.4.6/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Carl Lerche " ], "categories": [ "memory-management", "data-structures", "no-std" ], "keywords": [ "slab", "allocator", "no_std" ], "readme": "README.md", "repository": "https://github.com/tokio-rs/slab", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": "1.31" }, { "name": "smallvec", "version": "1.8.0", "id": "smallvec 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "'Small vector' optimization: store up to a small number of items on the stack", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "arbitrary", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "bincode", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "smallvec", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/smallvec-1.8.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "macro", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/smallvec-1.8.0/tests/macro.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "bench", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/smallvec-1.8.0/benches/bench.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "arbitrary": [ "dep:arbitrary" ], "const_generics": [], "const_new": [ "const_generics" ], "may_dangle": [], "serde": [ "dep:serde" ], "specialization": [], "union": [], "write": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/smallvec-1.8.0/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true, "rustdoc-args": [ "--cfg", "docsrs" ] } } }, "publish": null, "authors": [ "The Servo Project Developers" ], "categories": [ "data-structures" ], "keywords": [ "small", "vec", "vector", "stack", "no_std" ], "readme": "README.md", "repository": "https://github.com/servo/rust-smallvec", "homepage": null, "documentation": "https://docs.rs/smallvec/", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "socket2", "version": "0.4.4", "id": "socket2 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Utilities for handling networking sockets with a maximal amount of configuration\npossible intended.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "libc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.114", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(unix)", "registry": null }, { "name": "winapi", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.9", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [ "handleapi", "ws2ipdef", "ws2tcpip" ], "target": "cfg(windows)", "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "socket2", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/socket2-0.4.4/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": { "all": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/socket2-0.4.4/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true, "rustdoc-args": [ "--cfg", "docsrs" ] } }, "playground": { "features": [ "all" ] } }, "publish": null, "authors": [ "Alex Crichton ", "Thomas de Zeeuw " ], "categories": [ "api-bindings", "network-programming" ], "keywords": [ "io", "socket", "network" ], "readme": "README.md", "repository": "https://github.com/rust-lang/socket2", "homepage": "https://github.com/rust-lang/socket2", "documentation": "https://docs.rs/socket2", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "spin", "version": "0.5.2", "id": "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT", "license_file": null, "description": "Synchronization primitives based on spinning.\nThey may contain data, are usable without `std`,\nand static initializers are available.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "spin", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/spin-0.5.2/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "debug", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/spin-0.5.2/examples/debug.rs", "edition": "2015", "doc": false, "doctest": false, "test": false } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/spin-0.5.2/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Mathijs van de Nes ", "John Ericson " ], "categories": [], "keywords": [ "spinlock", "mutex", "rwlock" ], "readme": "README.md", "repository": "https://github.com/mvdnes/spin-rs.git", "homepage": null, "documentation": "https://mvdnes.github.io/rust-docs/spin-rs/spin/index.html", "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "spin", "version": "0.9.2", "id": "spin 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT", "license_file": null, "description": "Spin-based synchronization primitives", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "lock_api", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4", "kind": null, "rename": "lock_api_crate", "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "spin", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/spin-0.9.2/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "example" ], "crate_types": [ "bin" ], "name": "debug", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/spin-0.9.2/examples/debug.rs", "edition": "2015", "doc": false, "doctest": false, "test": false } ], "features": { "barrier": [ "mutex" ], "default": [ "lock_api", "mutex", "spin_mutex", "rwlock", "once", "lazy", "barrier" ], "lazy": [ "once" ], "lock_api": [ "lock_api_crate" ], "lock_api_crate": [ "dep:lock_api_crate" ], "mutex": [], "once": [], "rwlock": [], "spin_mutex": [ "mutex" ], "std": [], "ticket_mutex": [ "mutex" ], "use_ticket_mutex": [ "mutex", "ticket_mutex" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/spin-0.9.2/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true, "rustdoc-args": [ "--cfg", "docsrs" ] } } }, "publish": null, "authors": [ "Mathijs van de Nes ", "John Ericson ", "Joshua Barretto " ], "categories": [], "keywords": [ "spinlock", "mutex", "rwlock" ], "readme": "README.md", "repository": "https://github.com/mvdnes/spin-rs.git", "homepage": null, "documentation": null, "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "sqlformat", "version": "0.1.8", "id": "sqlformat 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Formats whitespace in a SQL string to make it easier to read", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "itertools", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.10", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "nom", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^7.0.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "unicode_categories", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "criterion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "indoc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "sqlformat", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlformat-0.1.8/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "bench", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlformat-0.1.8/benches/bench.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlformat-0.1.8/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Josh Holmer " ], "categories": [ "development-tools" ], "keywords": [ "sql" ], "readme": "README.md", "repository": "https://github.com/shssoichiro/sqlformat-rs", "homepage": "https://github.com/shssoichiro/sqlformat-rs", "documentation": "https://docs.rs/sqlformat", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "sqlx", "version": "0.5.11", "id": "sqlx 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "🧰 The Rust SQL Toolkit. An async, pure Rust SQL crate featuring compile-time checked queries without a DSL. Supports PostgreSQL, MySQL, and SQLite.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "sqlx-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.5.11", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "sqlx-macros", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.5.11", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "anyhow", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.52", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "async-std", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.10.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "attributes" ], "target": null, "registry": null }, { "name": "dotenv", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.15.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "env_logger", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8.4", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "futures", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.19", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "hex", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "paste", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.6", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8.4", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rand_xoshiro", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.6.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.132", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "derive" ], "target": null, "registry": null }, { "name": "serde_json", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.73", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "time", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.27", "kind": "dev", "rename": "time_", "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tokio", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.15.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "full" ], "target": null, "registry": null }, { "name": "trybuild", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.53", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "url", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^2.2.2", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "sqlx", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "any", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/any/any.rs", "edition": "2018", "required-features": [ "any" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "any-pool", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/any/pool.rs", "edition": "2018", "required-features": [ "any" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "migrate-macro", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/migrate/macro.rs", "edition": "2018", "required-features": [ "macros", "migrate" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sqlite", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/sqlite/sqlite.rs", "edition": "2018", "required-features": [ "sqlite" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sqlite-types", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/sqlite/types.rs", "edition": "2018", "required-features": [ "sqlite" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sqlite-describe", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/sqlite/describe.rs", "edition": "2018", "required-features": [ "sqlite" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sqlite-macros", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/sqlite/macros.rs", "edition": "2018", "required-features": [ "sqlite", "macros" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sqlite-derives", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/sqlite/derives.rs", "edition": "2018", "required-features": [ "sqlite", "macros" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "mysql", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/mysql/mysql.rs", "edition": "2018", "required-features": [ "mysql" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "mysql-types", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/mysql/types.rs", "edition": "2018", "required-features": [ "mysql" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "mysql-describe", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/mysql/describe.rs", "edition": "2018", "required-features": [ "mysql" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "mysql-macros", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/mysql/macros.rs", "edition": "2018", "required-features": [ "mysql", "macros" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "postgres", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/postgres/postgres.rs", "edition": "2018", "required-features": [ "postgres" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "postgres-types", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/postgres/types.rs", "edition": "2018", "required-features": [ "postgres" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "postgres-describe", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/postgres/describe.rs", "edition": "2018", "required-features": [ "postgres" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "postgres-macros", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/postgres/macros.rs", "edition": "2018", "required-features": [ "postgres", "macros" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "postgres-derives", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/postgres/derives.rs", "edition": "2018", "required-features": [ "postgres", "macros" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "mssql", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/mssql/mssql.rs", "edition": "2018", "required-features": [ "mssql" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "mssql-types", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/mssql/types.rs", "edition": "2018", "required-features": [ "mssql" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "mssql-describe", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/mssql/describe.rs", "edition": "2018", "required-features": [ "mssql" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "mssql-macros", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/mssql/macros.rs", "edition": "2018", "required-features": [ "mssql", "macros" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "ui-tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/tests/ui-tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "_rt-actix": [], "_rt-async-std": [], "_rt-tokio": [], "all": [ "tls", "all-databases", "all-types" ], "all-databases": [ "mysql", "sqlite", "postgres", "mssql", "any" ], "all-types": [ "bigdecimal", "decimal", "json", "time", "chrono", "ipnetwork", "mac_address", "uuid", "bit-vec", "bstr", "git2" ], "any": [ "sqlx-core/any" ], "bigdecimal": [ "sqlx-core/bigdecimal", "sqlx-macros/bigdecimal" ], "bit-vec": [ "sqlx-core/bit-vec", "sqlx-macros/bit-vec" ], "bstr": [ "sqlx-core/bstr" ], "chrono": [ "sqlx-core/chrono", "sqlx-macros/chrono" ], "decimal": [ "sqlx-core/decimal", "sqlx-macros/decimal" ], "default": [ "macros", "migrate" ], "git2": [ "sqlx-core/git2" ], "ipnetwork": [ "sqlx-core/ipnetwork", "sqlx-macros/ipnetwork" ], "json": [ "sqlx-core/json", "sqlx-macros/json" ], "mac_address": [ "sqlx-core/mac_address", "sqlx-macros/mac_address" ], "macros": [ "sqlx-macros" ], "migrate": [ "sqlx-macros/migrate", "sqlx-core/migrate" ], "mssql": [ "sqlx-core/mssql", "sqlx-macros/mssql" ], "mysql": [ "sqlx-core/mysql", "sqlx-macros/mysql" ], "offline": [ "sqlx-macros/offline", "sqlx-core/offline" ], "postgres": [ "sqlx-core/postgres", "sqlx-macros/postgres" ], "runtime-actix": [], "runtime-actix-native-tls": [ "sqlx-core/runtime-actix-native-tls", "sqlx-macros/runtime-actix-native-tls", "_rt-actix" ], "runtime-actix-rustls": [ "sqlx-core/runtime-actix-rustls", "sqlx-macros/runtime-actix-rustls", "_rt-actix" ], "runtime-async-std": [], "runtime-async-std-native-tls": [ "sqlx-core/runtime-async-std-native-tls", "sqlx-macros/runtime-async-std-native-tls", "_rt-async-std" ], "runtime-async-std-rustls": [ "sqlx-core/runtime-async-std-rustls", "sqlx-macros/runtime-async-std-rustls", "_rt-async-std" ], "runtime-tokio": [], "runtime-tokio-native-tls": [ "sqlx-core/runtime-tokio-native-tls", "sqlx-macros/runtime-tokio-native-tls", "_rt-tokio" ], "runtime-tokio-rustls": [ "sqlx-core/runtime-tokio-rustls", "sqlx-macros/runtime-tokio-rustls", "_rt-tokio" ], "sqlite": [ "sqlx-core/sqlite", "sqlx-macros/sqlite" ], "sqlx-macros": [ "dep:sqlx-macros" ], "time": [ "sqlx-core/time", "sqlx-macros/time" ], "tls": [], "uuid": [ "sqlx-core/uuid", "sqlx-macros/uuid" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-0.5.11/Cargo.toml", "metadata": { "docs": { "rs": { "features": [ "all", "runtime-async-std-native-tls" ], "rustdoc-args": [ "--cfg", "docsrs" ] } } }, "publish": null, "authors": [ "Ryan Leckey ", "Austin Bonander ", "Chloe Ross ", "Daniel Akhterov " ], "categories": [ "database", "asynchronous" ], "keywords": [ "database", "async", "postgres", "mysql", "sqlite" ], "readme": "README.md", "repository": "https://github.com/launchbadge/sqlx", "homepage": null, "documentation": "https://docs.rs/sqlx", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "sqlx-core", "version": "0.5.11", "id": "sqlx-core 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Core of SQLx, the rust SQL toolkit. Not intended to be used directly.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "ahash", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.7.6", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "atoi", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "base64", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.13.0", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [ "std" ], "target": null, "registry": null }, { "name": "bigdecimal", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.2", "kind": null, "rename": "bigdecimal_", "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "bit-vec", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.6.3", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "bitflags", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.3.2", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "bstr", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.17", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [ "std" ], "target": null, "registry": null }, { "name": "byteorder", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.4.3", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [ "std" ], "target": null, "registry": null }, { "name": "bytes", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "chrono", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.19", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [ "clock" ], "target": null, "registry": null }, { "name": "crc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^2.1.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "crossbeam-queue", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.2", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "digest", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.9.0", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [ "std" ], "target": null, "registry": null }, { "name": "dirs", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^4.0.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "either", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.6.1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "encoding_rs", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8.30", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "flume", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.10.9", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [ "async" ], "target": null, "registry": null }, { "name": "futures-channel", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.19", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [ "sink", "alloc", "std" ], "target": null, "registry": null }, { "name": "futures-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.19", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "futures-executor", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.19", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "futures-intrusive", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "futures-util", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.19", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [ "alloc", "sink" ], "target": null, "registry": null }, { "name": "generic-array", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.14.4", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "git2", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.13.25", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "hashlink", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.7.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "hex", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.3", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "hmac", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.11.0", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "indexmap", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.6.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "ipnetwork", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.17.0", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "itoa", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "libc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.112", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "libsqlite3-sys", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.23.2", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [ "pkg-config", "vcpkg", "bundled", "unlock_notify" ], "target": null, "registry": null }, { "name": "log", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.14", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "mac_address", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.1.2", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "md-5", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.9.1", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "memchr", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^2.4.1", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "num-bigint", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.3", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [ "std" ], "target": null, "registry": null }, { "name": "once_cell", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.9.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "paste", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.6", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "percent-encoding", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^2.1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8.4", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [ "std", "std_rng" ], "target": null, "registry": null }, { "name": "regex", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.5.4", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rsa", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.5.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rust_decimal", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.19.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustls", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.19.1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [ "dangerous_configuration" ], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.132", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [ "derive", "rc" ], "target": null, "registry": null }, { "name": "serde_json", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.73", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [ "raw_value" ], "target": null, "registry": null }, { "name": "sha-1", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.9.8", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "sha2", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.9.8", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "smallvec", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.7.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "sqlformat", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.8", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "sqlx-rt", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.5.11", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "stringprep", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.2", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "thiserror", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.30", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "time", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.27", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tokio-stream", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.8", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [ "fs" ], "target": null, "registry": null }, { "name": "url", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^2.2.2", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "uuid", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8.2", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [ "std" ], "target": null, "registry": null }, { "name": "webpki", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.21.4", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "webpki-roots", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.21.1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "whoami", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.2.1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "sqlx", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.5.11", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "postgres", "sqlite" ], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "sqlx-core", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-core-0.5.11/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": { "_rt-actix": [ "tokio-stream" ], "_rt-async-std": [], "_rt-tokio": [ "tokio-stream" ], "_tls-native-tls": [], "_tls-rustls": [ "rustls", "webpki", "webpki-roots" ], "all-databases": [ "postgres", "mysql", "sqlite", "mssql", "any" ], "all-types": [ "chrono", "time", "bigdecimal", "decimal", "ipnetwork", "mac_address", "json", "uuid", "bit-vec" ], "any": [], "base64": [ "dep:base64" ], "bigdecimal": [ "bigdecimal_", "num-bigint" ], "bigdecimal_": [ "dep:bigdecimal_" ], "bit-vec": [ "dep:bit-vec" ], "bstr": [ "dep:bstr" ], "chrono": [ "dep:chrono" ], "crc": [ "dep:crc" ], "decimal": [ "rust_decimal", "num-bigint" ], "default": [ "migrate" ], "digest": [ "dep:digest" ], "dirs": [ "dep:dirs" ], "encoding_rs": [ "dep:encoding_rs" ], "flume": [ "dep:flume" ], "futures-executor": [ "dep:futures-executor" ], "generic-array": [ "dep:generic-array" ], "git2": [ "dep:git2" ], "hmac": [ "dep:hmac" ], "ipnetwork": [ "dep:ipnetwork" ], "json": [ "serde", "serde_json" ], "libsqlite3-sys": [ "dep:libsqlite3-sys" ], "mac_address": [ "dep:mac_address" ], "md-5": [ "dep:md-5" ], "migrate": [ "sha2", "crc" ], "mssql": [ "uuid", "encoding_rs", "regex" ], "mysql": [ "sha-1", "sha2", "generic-array", "num-bigint", "digest", "rand", "rsa" ], "num-bigint": [ "dep:num-bigint" ], "offline": [ "serde", "either/serde" ], "postgres": [ "md-5", "sha2", "base64", "sha-1", "rand", "hmac", "futures-channel/sink", "futures-util/sink", "json", "dirs", "whoami" ], "rand": [ "dep:rand" ], "regex": [ "dep:regex" ], "rsa": [ "dep:rsa" ], "runtime-actix-native-tls": [ "sqlx-rt/runtime-actix-native-tls", "_tls-native-tls", "_rt-actix" ], "runtime-actix-rustls": [ "sqlx-rt/runtime-actix-rustls", "_tls-rustls", "_rt-actix" ], "runtime-async-std-native-tls": [ "sqlx-rt/runtime-async-std-native-tls", "_tls-native-tls", "_rt-async-std" ], "runtime-async-std-rustls": [ "sqlx-rt/runtime-async-std-rustls", "_tls-rustls", "_rt-async-std" ], "runtime-tokio-native-tls": [ "sqlx-rt/runtime-tokio-native-tls", "_tls-native-tls", "_rt-tokio" ], "runtime-tokio-rustls": [ "sqlx-rt/runtime-tokio-rustls", "_tls-rustls", "_rt-tokio" ], "rust_decimal": [ "dep:rust_decimal" ], "rustls": [ "dep:rustls" ], "serde": [ "dep:serde" ], "serde_json": [ "dep:serde_json" ], "sha-1": [ "dep:sha-1" ], "sha2": [ "dep:sha2" ], "sqlite": [ "libsqlite3-sys", "futures-executor", "flume" ], "time": [ "dep:time" ], "tokio-stream": [ "dep:tokio-stream" ], "uuid": [ "dep:uuid" ], "webpki": [ "dep:webpki" ], "webpki-roots": [ "dep:webpki-roots" ], "whoami": [ "dep:whoami" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-core-0.5.11/Cargo.toml", "metadata": { "docs": { "rs": { "features": [ "all-databases", "all-types", "offline", "runtime-async-std-native-tls" ] } } }, "publish": null, "authors": [ "Ryan Leckey ", "Austin Bonander ", "Chloe Ross ", "Daniel Akhterov " ], "categories": [], "keywords": [], "readme": null, "repository": "https://github.com/launchbadge/sqlx", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "sqlx-macros", "version": "0.5.11", "id": "sqlx-macros 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Macros for SQLx, the rust SQL toolkit. Not intended to be used directly.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "dotenv", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.15.0", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "either", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.6.1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "heck", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.3", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "hex", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.3", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "once_cell", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.9.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "proc-macro2", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.36", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "quote", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.14", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.132", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [ "derive" ], "target": null, "registry": null }, { "name": "serde_json", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.73", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "sha2", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.9.8", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "sqlx-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.5.11", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "sqlx-rt", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.5.11", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "syn", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.84", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [ "full" ], "target": null, "registry": null }, { "name": "url", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^2.2.2", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "proc-macro" ], "crate_types": [ "proc-macro" ], "name": "sqlx-macros", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-macros-0.5.11/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": { "_rt-actix": [], "_rt-async-std": [], "_rt-tokio": [], "bigdecimal": [ "sqlx-core/bigdecimal" ], "bit-vec": [ "sqlx-core/bit-vec" ], "chrono": [ "sqlx-core/chrono" ], "decimal": [ "sqlx-core/decimal" ], "default": [ "runtime-async-std-native-tls", "migrate" ], "hex": [ "dep:hex" ], "ipnetwork": [ "sqlx-core/ipnetwork" ], "json": [ "sqlx-core/json", "serde_json" ], "mac_address": [ "sqlx-core/mac_address" ], "migrate": [ "sha2", "sqlx-core/migrate" ], "mssql": [ "sqlx-core/mssql" ], "mysql": [ "sqlx-core/mysql" ], "offline": [ "sqlx-core/offline", "hex", "serde", "serde_json", "sha2" ], "postgres": [ "sqlx-core/postgres" ], "runtime-actix-native-tls": [ "sqlx-core/runtime-actix-native-tls", "sqlx-rt/runtime-actix-native-tls", "_rt-actix" ], "runtime-actix-rustls": [ "sqlx-core/runtime-actix-rustls", "sqlx-rt/runtime-actix-rustls", "_rt-actix" ], "runtime-async-std-native-tls": [ "sqlx-core/runtime-async-std-native-tls", "sqlx-rt/runtime-async-std-native-tls", "_rt-async-std" ], "runtime-async-std-rustls": [ "sqlx-core/runtime-async-std-rustls", "sqlx-rt/runtime-async-std-rustls", "_rt-async-std" ], "runtime-tokio-native-tls": [ "sqlx-core/runtime-tokio-native-tls", "sqlx-rt/runtime-tokio-native-tls", "_rt-tokio" ], "runtime-tokio-rustls": [ "sqlx-core/runtime-tokio-rustls", "sqlx-rt/runtime-tokio-rustls", "_rt-tokio" ], "serde": [ "dep:serde" ], "serde_json": [ "dep:serde_json" ], "sha2": [ "dep:sha2" ], "sqlite": [ "sqlx-core/sqlite" ], "time": [ "sqlx-core/time" ], "uuid": [ "sqlx-core/uuid" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-macros-0.5.11/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Ryan Leckey ", "Austin Bonander ", "Chloe Ross ", "Daniel Akhterov " ], "categories": [], "keywords": [], "readme": null, "repository": "https://github.com/launchbadge/sqlx", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "sqlx-rt", "version": "0.5.11", "id": "sqlx-rt 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Runtime abstraction used by SQLx, the Rust SQL toolkit. Not intended to be used directly.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "actix-rt", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^2.0.0", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "async-native-tls", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.3", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "async-rustls", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "async-std", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.7.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [ "unstable" ], "target": null, "registry": null }, { "name": "native-tls", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.4", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "once_cell", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.4", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [ "std" ], "target": null, "registry": null }, { "name": "tokio", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [ "fs", "net", "rt", "rt-multi-thread", "time", "io-util" ], "target": null, "registry": null }, { "name": "tokio-native-tls", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tokio-rustls", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.22.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "sqlx-rt", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-rt-0.5.11/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": { "_rt-actix": [ "actix-rt", "tokio", "once_cell" ], "_rt-async-std": [ "async-std" ], "_rt-tokio": [ "tokio", "once_cell" ], "_tls-native-tls": [ "native-tls" ], "_tls-rustls": [], "actix-rt": [ "dep:actix-rt" ], "async-native-tls": [ "dep:async-native-tls" ], "async-rustls": [ "dep:async-rustls" ], "async-std": [ "dep:async-std" ], "native-tls": [ "dep:native-tls" ], "once_cell": [ "dep:once_cell" ], "runtime-actix-native-tls": [ "_rt-actix", "_tls-native-tls", "tokio-native-tls" ], "runtime-actix-rustls": [ "_rt-actix", "_tls-rustls", "tokio-rustls" ], "runtime-async-std-native-tls": [ "_rt-async-std", "_tls-native-tls", "async-native-tls" ], "runtime-async-std-rustls": [ "_rt-async-std", "_tls-rustls", "async-rustls" ], "runtime-tokio-native-tls": [ "_rt-tokio", "_tls-native-tls", "tokio-native-tls" ], "runtime-tokio-rustls": [ "_rt-tokio", "_tls-rustls", "tokio-rustls" ], "tokio": [ "dep:tokio" ], "tokio-native-tls": [ "dep:tokio-native-tls" ], "tokio-rustls": [ "dep:tokio-rustls" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/sqlx-rt-0.5.11/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Ryan Leckey ", "Austin Bonander " ], "categories": [], "keywords": [], "readme": null, "repository": "https://github.com/launchbadge/sqlx", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "stringprep", "version": "0.1.2", "id": "stringprep 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "An implementation of the stringprep algorithm", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "unicode-bidi", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "unicode-normalization", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "stringprep", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/stringprep-0.1.2/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "saslprep_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/stringprep-0.1.2/tests/saslprep_tests.rs", "edition": "2015", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "nodeprep_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/stringprep-0.1.2/tests/nodeprep_tests.rs", "edition": "2015", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "nameprep_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/stringprep-0.1.2/tests/nameprep_tests.rs", "edition": "2015", "doc": false, "doctest": false, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/stringprep-0.1.2/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Steven Fackler " ], "categories": [], "keywords": [], "readme": "README.md", "repository": "https://github.com/sfackler/rust-stringprep", "homepage": null, "documentation": "https://docs.rs/stringprep/0.1.2/stringprep", "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "syn", "version": "1.0.91", "id": "syn 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Parser for Rust source code", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "proc-macro2", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.32", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "quote", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "unicode-xid", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "anyhow", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "automod", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "flate2", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "insta", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rayon", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "ref-cast", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "regex", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "reqwest", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.11", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "blocking" ], "target": null, "registry": null }, { "name": "syn-test-suite", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tar", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.16", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "termcolor", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "walkdir", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^2.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "syn", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "regression", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/regression.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_pat", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_pat.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_precedence", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_precedence.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_grouping", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_grouping.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_round_trip", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_round_trip.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_should_parse", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_should_parse.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_parse_stream", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_parse_stream.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_shebang", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_shebang.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_size", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_size.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_generics", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_generics.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "zzz_stable", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/zzz_stable.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_meta", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_meta.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_asyncness", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_asyncness.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_token_trees", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_token_trees.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_ty", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_ty.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_parse_buffer", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_parse_buffer.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_visibility", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_visibility.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_iterators", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_iterators.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_lit", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_lit.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_expr", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_expr.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_path", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_path.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_receiver", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_receiver.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_derive_input", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_derive_input.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_ident", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_ident.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_item", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_item.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_stmt", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_stmt.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_attribute", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/tests/test_attribute.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "rust", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/benches/rust.rs", "edition": "2018", "required-features": [ "full", "parsing" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "file", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/benches/file.rs", "edition": "2018", "required-features": [ "full", "parsing" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "clone-impls": [], "default": [ "derive", "parsing", "printing", "clone-impls", "proc-macro" ], "derive": [], "extra-traits": [], "fold": [], "full": [], "parsing": [], "printing": [ "quote" ], "proc-macro": [ "proc-macro2/proc-macro", "quote/proc-macro" ], "quote": [ "dep:quote" ], "test": [ "syn-test-suite/all-features" ], "visit": [], "visit-mut": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/syn-1.0.91/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true, "targets": [ "x86_64-unknown-linux-gnu" ], "rustdoc-args": [ "--cfg", "doc_cfg" ] } }, "playground": { "features": [ "full", "visit", "visit-mut", "fold", "extra-traits" ] } }, "publish": null, "authors": [ "David Tolnay " ], "categories": [ "development-tools::procedural-macro-helpers" ], "keywords": [], "readme": "README.md", "repository": "https://github.com/dtolnay/syn", "homepage": null, "documentation": "https://docs.rs/syn", "edition": "2018", "links": null, "default_run": null, "rust_version": "1.31" }, { "name": "thiserror", "version": "1.0.30", "id": "thiserror 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "derive(Error)", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "thiserror-impl", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=1.0.30", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "anyhow", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "ref-cast", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustversion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "trybuild", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.49", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "diff" ], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "thiserror", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.30/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_display", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.30/tests/test_display.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_backtrace", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.30/tests/test_backtrace.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_transparent", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.30/tests/test_transparent.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_from", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.30/tests/test_from.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_error", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.30/tests/test_error.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_generics", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.30/tests/test_generics.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_source", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.30/tests/test_source.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "compiletest", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.30/tests/compiletest.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_lints", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.30/tests/test_lints.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_expr", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.30/tests/test_expr.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_path", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.30/tests/test_path.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_option", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.30/tests/test_option.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-1.0.30/Cargo.toml", "metadata": { "docs": { "rs": { "targets": [ "x86_64-unknown-linux-gnu" ] } } }, "publish": null, "authors": [ "David Tolnay " ], "categories": [ "rust-patterns" ], "keywords": [], "readme": "README.md", "repository": "https://github.com/dtolnay/thiserror", "homepage": null, "documentation": "https://docs.rs/thiserror", "edition": "2018", "links": null, "default_run": null, "rust_version": "1.31" }, { "name": "thiserror-impl", "version": "1.0.30", "id": "thiserror-impl 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Implementation detail of the `thiserror` crate", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "proc-macro2", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "quote", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "syn", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.45", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "proc-macro" ], "crate_types": [ "proc-macro" ], "name": "thiserror-impl", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-impl-1.0.30/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/thiserror-impl-1.0.30/Cargo.toml", "metadata": { "docs": { "rs": { "targets": [ "x86_64-unknown-linux-gnu" ] } } }, "publish": null, "authors": [ "David Tolnay " ], "categories": [], "keywords": [], "readme": null, "repository": "https://github.com/dtolnay/thiserror", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": "1.31" }, { "name": "tinyvec", "version": "1.5.1", "id": "tinyvec 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Zlib OR Apache-2.0 OR MIT", "license_file": null, "description": "`tinyvec` provides 100% safe vec-like data structures.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "arbitrary", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "tinyvec_macros", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "criterion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde_test", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "smallvec", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "tinyvec", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tinyvec-1.5.1/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "tinyvec", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tinyvec-1.5.1/tests/tinyvec.rs", "edition": "2018", "required-features": [ "alloc", "std" ], "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "arrayvec", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tinyvec-1.5.1/tests/arrayvec.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "macros", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tinyvec-1.5.1/benches/macros.rs", "edition": "2018", "required-features": [ "alloc" ], "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "smallvec", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tinyvec-1.5.1/benches/smallvec.rs", "edition": "2018", "required-features": [ "alloc", "real_blackbox" ], "doc": false, "doctest": false, "test": false } ], "features": { "alloc": [ "tinyvec_macros" ], "arbitrary": [ "dep:arbitrary" ], "default": [], "experimental_write_impl": [], "grab_spare_slice": [], "nightly_slice_partition_dedup": [], "real_blackbox": [ "criterion/real_blackbox" ], "rustc_1_40": [], "rustc_1_55": [ "rustc_1_40" ], "serde": [ "dep:serde" ], "std": [], "tinyvec_macros": [ "dep:tinyvec_macros" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tinyvec-1.5.1/Cargo.toml", "metadata": { "docs": { "rs": { "features": [ "alloc", "std", "grab_spare_slice", "rustc_1_40", "rustc_1_55", "serde" ], "rustdoc-args": [ "--cfg", "docs_rs" ] } }, "playground": { "features": [ "alloc", "std", "grab_spare_slice", "rustc_1_40", "rustc_1_55", "serde" ] } }, "publish": null, "authors": [ "Lokathor " ], "categories": [ "data-structures", "no-std" ], "keywords": [ "vec", "no_std", "no-std" ], "readme": "README.md", "repository": "https://github.com/Lokathor/tinyvec", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "tinyvec_macros", "version": "0.1.0", "id": "tinyvec_macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0 OR Zlib", "license_file": null, "description": "Some macros for tiny containers", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "tinyvec_macros", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tinyvec_macros-0.1.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tinyvec_macros-0.1.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Soveu " ], "categories": [], "keywords": [], "readme": null, "repository": "https://github.com/Soveu/tinyvec_macros", "homepage": null, "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "tokio", "version": "1.17.0", "id": "tokio 1.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT", "license_file": null, "description": "An event-driven, non-blocking I/O platform for writing asynchronous I/O\nbacked applications.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "bytes", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "memchr", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^2.2", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "mio", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "num_cpus", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.8.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "once_cell", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.5.2", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "parking_lot", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.12.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "pin-project-lite", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "socket2", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.4", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [ "all" ], "target": null, "registry": null }, { "name": "tokio-macros", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.7.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "async-stream", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "futures", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "async-await" ], "target": null, "registry": null }, { "name": "mockall", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.10.2", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tempfile", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^3.1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tokio-stream", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tokio-test", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "loom", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.5.2", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "futures", "checkpoint" ], "target": "cfg(loom)", "registry": null }, { "name": "proptest", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(not(target_arch = \"wasm32\"))", "registry": null }, { "name": "rand", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.8.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(not(target_arch = \"wasm32\"))", "registry": null }, { "name": "socket2", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(not(target_arch = \"wasm32\"))", "registry": null }, { "name": "wasm-bindgen-test", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(target_arch = \"wasm32\")", "registry": null }, { "name": "mio-aio", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.6.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "tokio" ], "target": "cfg(target_os = \"freebsd\")", "registry": null }, { "name": "tracing", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1.25", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [ "std" ], "target": "cfg(tokio_unstable)", "registry": null }, { "name": "libc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.42", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": "cfg(unix)", "registry": null }, { "name": "signal-hook-registry", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.1.1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": "cfg(unix)", "registry": null }, { "name": "libc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.42", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(unix)", "registry": null }, { "name": "nix", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.23", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(unix)", "registry": null }, { "name": "winapi", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.8", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": "cfg(windows)", "registry": null }, { "name": "ntapi", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.6", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(windows)", "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "tokio", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sync_semaphore", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/sync_semaphore.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "signal_drop_recv", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/signal_drop_recv.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "signal_ctrl_c", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/signal_ctrl_c.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "time_rt", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/time_rt.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_read_to_string", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_read_to_string.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "process_smoke", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/process_smoke.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "named_pipe", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/named_pipe.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_util_empty", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_util_empty.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "process_issue_2174", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/process_issue_2174.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test_clock", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/test_clock.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "task_local_set", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/task_local_set.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "join_handle_panic", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/join_handle_panic.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_copy_bidirectional", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_copy_bidirectional.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "tcp_into_split", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/tcp_into_split.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "process_raw_handle", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/process_raw_handle.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_read_buf", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_read_buf.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "task_blocking", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/task_blocking.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "tcp_into_std", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/tcp_into_std.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_split", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_split.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_async_fd", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_async_fd.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "tcp_echo", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/tcp_echo.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "async_send_sync", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/async_send_sync.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "tcp_socket", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/tcp_socket.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "signal_usr1", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/signal_usr1.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "task_builder", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/task_builder.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sync_broadcast", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/sync_broadcast.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "time_pause", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/time_pause.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sync_semaphore_owned", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/sync_semaphore_owned.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "time_interval", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/time_interval.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "signal_twice", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/signal_twice.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "buffered", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/buffered.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "net_bind_resource", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/net_bind_resource.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "time_timeout", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/time_timeout.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_copy", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_copy.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_read_until", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_read_until.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "fs_file", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/fs_file.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "net_lookup_host", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/net_lookup_host.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_read_to_end", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_read_to_end.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_write_int", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_write_int.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "uds_cred", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/uds_cred.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_buf_reader", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_buf_reader.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "macros_try_join", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/macros_try_join.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_poll_aio", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_poll_aio.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "macros_pin", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/macros_pin.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sync_mpsc", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/sync_mpsc.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "tcp_peek", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/tcp_peek.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "tcp_accept", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/tcp_accept.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sync_barrier", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/sync_barrier.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "time_sleep", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/time_sleep.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_mem_stream", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_mem_stream.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_chain", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_chain.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "rt_threaded", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/rt_threaded.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_fill_buf", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_fill_buf.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sync_errors", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/sync_errors.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "rt_common", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/rt_common.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "task_local", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/task_local.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "process_arg0", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/process_arg0.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "signal_notify_both", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/signal_notify_both.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "tcp_connect", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/tcp_connect.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_driver", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_driver.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "fs_dir", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/fs_dir.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "signal_no_rt", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/signal_no_rt.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "macros_join", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/macros_join.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sync_mutex", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/sync_mutex.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sync_mutex_owned", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/sync_mutex_owned.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_write_all_buf", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_write_all_buf.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_lines", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_lines.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "tcp_stream", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/tcp_stream.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "tcp_split", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/tcp_split.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_read_exact", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_read_exact.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "rt_basic", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/rt_basic.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "signal_drop_rt", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/signal_drop_rt.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "macros_test", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/macros_test.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sync_once_cell", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/sync_once_cell.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "task_join_set", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/task_join_set.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "task_abort", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/task_abort.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "udp", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/udp.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_write_buf", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_write_buf.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "rt_handle_block_on", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/rt_handle_block_on.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "signal_drop_signal", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/signal_drop_signal.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "macros_select", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/macros_select.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "unwindsafe", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/unwindsafe.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_async_read", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_async_read.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "uds_stream", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/uds_stream.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "_require_full", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/_require_full.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_read", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_read.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_read_line", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_read_line.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sync_oneshot", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/sync_oneshot.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sync_rwlock", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/sync_rwlock.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "process_issue_42", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/process_issue_42.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sync_notify", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/sync_notify.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "sync_watch", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/sync_watch.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "tcp_shutdown", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/tcp_shutdown.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "uds_split", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/uds_split.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "fs_link", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/fs_link.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "uds_datagram", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/uds_datagram.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "fs_copy", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/fs_copy.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_take", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_take.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_buf_writer", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_buf_writer.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "rt_metrics", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/rt_metrics.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "signal_multi_rt", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/signal_multi_rt.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_driver_drop", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_driver_drop.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_write", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_write.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "io_write_all", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/io_write_all.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "no_rt", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/no_rt.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "process_kill_on_drop", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/process_kill_on_drop.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "fs", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/tests/fs.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "bytes": [ "dep:bytes" ], "default": [], "fs": [], "full": [ "fs", "io-util", "io-std", "macros", "net", "parking_lot", "process", "rt", "rt-multi-thread", "signal", "sync", "time" ], "io-std": [], "io-util": [ "memchr", "bytes" ], "libc": [ "dep:libc" ], "macros": [ "tokio-macros" ], "memchr": [ "dep:memchr" ], "mio": [ "dep:mio" ], "net": [ "libc", "mio/os-poll", "mio/os-ext", "mio/net", "socket2", "winapi/namedpipeapi" ], "num_cpus": [ "dep:num_cpus" ], "once_cell": [ "dep:once_cell" ], "parking_lot": [ "dep:parking_lot" ], "process": [ "bytes", "once_cell", "libc", "mio/os-poll", "mio/os-ext", "mio/net", "signal-hook-registry", "winapi/threadpoollegacyapiset" ], "rt": [], "rt-multi-thread": [ "num_cpus", "rt" ], "signal": [ "once_cell", "libc", "mio/os-poll", "mio/net", "mio/os-ext", "signal-hook-registry", "winapi/consoleapi" ], "signal-hook-registry": [ "dep:signal-hook-registry" ], "socket2": [ "dep:socket2" ], "stats": [], "sync": [], "test-util": [ "rt", "sync", "time" ], "time": [], "tokio-macros": [ "dep:tokio-macros" ], "tracing": [ "dep:tracing" ], "winapi": [ "dep:winapi" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.17.0/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true, "rustc-args": [ "--cfg", "tokio_unstable" ], "rustdoc-args": [ "--cfg", "docsrs", "--cfg", "tokio_unstable" ] } }, "playground": { "features": [ "full", "test-util" ] } }, "publish": null, "authors": [ "Tokio Contributors " ], "categories": [ "asynchronous", "network-programming" ], "keywords": [ "io", "async", "non-blocking", "futures" ], "readme": "README.md", "repository": "https://github.com/tokio-rs/tokio", "homepage": "https://tokio.rs", "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": "1.49" }, { "name": "tokio-rustls", "version": "0.22.0", "id": "tokio-rustls 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "Asynchronous TLS/SSL streams for Tokio using Rustls.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "rustls", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.19", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tokio", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "webpki", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.21", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "futures-util", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "lazy_static", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tokio", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "full" ], "target": null, "registry": null }, { "name": "webpki-roots", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.21", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "tokio-rustls", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-rustls-0.22.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "early-data", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-rustls-0.22.0/tests/early-data.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "badssl", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-rustls-0.22.0/tests/badssl.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-rustls-0.22.0/tests/test.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "dangerous_configuration": [ "rustls/dangerous_configuration" ], "early-data": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-rustls-0.22.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "quininer kel " ], "categories": [ "asynchronous", "cryptography", "network-programming" ], "keywords": [], "readme": "README.md", "repository": "https://github.com/tokio-rs/tls", "homepage": "https://github.com/tokio-rs/tls", "documentation": "https://docs.rs/tokio-rustls", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "tokio-stream", "version": "0.1.8", "id": "tokio-stream 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT", "license_file": null, "description": "Utilities to work with `Stream` and `tokio`.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "futures-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "pin-project-lite", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tokio", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.8.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [ "sync" ], "target": null, "registry": null }, { "name": "tokio-util", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.6.3", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "async-stream", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "futures", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": false, "features": [], "target": null, "registry": null }, { "name": "proptest", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tokio", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.2.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "full", "test-util" ], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "tokio-stream", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-stream-0.1.8/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "time_throttle", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-stream-0.1.8/tests/time_throttle.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "stream_iter", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-stream-0.1.8/tests/stream_iter.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "stream_once", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-stream-0.1.8/tests/stream_once.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "async_send_sync", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-stream-0.1.8/tests/async_send_sync.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "stream_collect", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-stream-0.1.8/tests/stream_collect.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "stream_timeout", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-stream-0.1.8/tests/stream_timeout.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "stream_stream_map", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-stream-0.1.8/tests/stream_stream_map.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "stream_pending", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-stream-0.1.8/tests/stream_pending.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "stream_fuse", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-stream-0.1.8/tests/stream_fuse.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "stream_merge", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-stream-0.1.8/tests/stream_merge.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "watch", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-stream-0.1.8/tests/watch.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "stream_empty", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-stream-0.1.8/tests/stream_empty.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "stream_chain", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-stream-0.1.8/tests/stream_chain.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "default": [ "time" ], "fs": [ "tokio/fs" ], "io-util": [ "tokio/io-util" ], "net": [ "tokio/net" ], "signal": [ "tokio/signal" ], "sync": [ "tokio/sync", "tokio-util" ], "time": [ "tokio/time" ], "tokio-util": [ "dep:tokio-util" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/tokio-stream-0.1.8/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true, "rustc-args": [ "--cfg", "docsrs" ], "rustdoc-args": [ "--cfg", "docsrs" ] } } }, "publish": null, "authors": [ "Tokio Contributors " ], "categories": [ "asynchronous" ], "keywords": [], "readme": null, "repository": "https://github.com/tokio-rs/tokio", "homepage": "https://tokio.rs", "documentation": "https://docs.rs/tokio-stream/0.1.8/tokio_stream", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "typenum", "version": "1.15.0", "id": "typenum 1.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Typenum is a Rust library for type-level numbers evaluated at\n compile time. It currently supports bits, unsigned integers, and signed\n integers. It also provides a type-level array of type-level numbers, but its\n implementation is incomplete.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "scale-info", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "typenum", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/typenum-1.15.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "test", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/typenum-1.15.0/tests/test.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-main", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/typenum-1.15.0/build/main.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "force_unix_path_separator": [], "i128": [], "no_std": [], "scale-info": [ "dep:scale-info" ], "scale_info": [ "scale-info/derive" ], "strict": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/typenum-1.15.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Paho Lurie-Gregg ", "Andre Bogus " ], "categories": [ "no-std" ], "keywords": [], "readme": "README.md", "repository": "https://github.com/paholg/typenum", "homepage": null, "documentation": "https://docs.rs/typenum", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "unicode-bidi", "version": "0.3.7", "id": "unicode-bidi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT / Apache-2.0", "license_file": null, "description": "Implementation of the Unicode Bidirectional Algorithm", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "flame", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "flamer", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": ">=0.8, <2.0", "kind": null, "rename": null, "optional": true, "uses_default_features": false, "features": [ "derive" ], "target": null, "registry": null }, { "name": "serde_test", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": ">=0.8, <2.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "unicode_bidi", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/unicode-bidi-0.3.7/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": { "bench_it": [], "default": [ "std" ], "flame": [ "dep:flame" ], "flame_it": [ "flame", "flamer" ], "flamer": [ "dep:flamer" ], "serde": [ "dep:serde" ], "std": [], "unstable": [], "with_serde": [ "serde" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/unicode-bidi-0.3.7/Cargo.toml", "metadata": null, "publish": null, "authors": [ "The Servo Project Developers" ], "categories": [ "no-std", "encoding", "text-processing" ], "keywords": [ "rtl", "unicode", "text", "layout", "bidi" ], "readme": "README.md", "repository": "https://github.com/servo/unicode-bidi", "homepage": null, "documentation": "https://docs.rs/unicode-bidi/", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "unicode-normalization", "version": "0.1.19", "id": "unicode-normalization 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "This crate provides functions for normalization of\nUnicode strings, including Canonical and Compatible\nDecomposition and Recomposition, as described in\nUnicode Standard Annex #15.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "tinyvec", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [ "alloc" ], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "unicode-normalization", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/unicode-normalization-0.1.19/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "bench", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/unicode-normalization-0.1.19/benches/bench.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "default": [ "std" ], "std": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/unicode-normalization-0.1.19/Cargo.toml", "metadata": null, "publish": null, "authors": [ "kwantam ", "Manish Goregaokar " ], "categories": [], "keywords": [ "text", "unicode", "normalization", "decomposition", "recomposition" ], "readme": "README.md", "repository": "https://github.com/unicode-rs/unicode-normalization", "homepage": "https://github.com/unicode-rs/unicode-normalization", "documentation": "https://docs.rs/unicode-normalization/", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "unicode-segmentation", "version": "1.9.0", "id": "unicode-segmentation 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "This crate provides Grapheme Cluster, Word and Sentence boundaries\naccording to Unicode Standard Annex #29 rules.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "criterion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "quickcheck", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.7", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "unicode-segmentation", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/unicode-segmentation-1.9.0/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "graphemes", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/unicode-segmentation-1.9.0/benches/graphemes.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "unicode_words", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/unicode-segmentation-1.9.0/benches/unicode_words.rs", "edition": "2018", "doc": false, "doctest": false, "test": false }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "word_bounds", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/unicode-segmentation-1.9.0/benches/word_bounds.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "no_std": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/unicode-segmentation-1.9.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "kwantam ", "Manish Goregaokar " ], "categories": [], "keywords": [ "text", "unicode", "grapheme", "word", "boundary" ], "readme": "README.md", "repository": "https://github.com/unicode-rs/unicode-segmentation", "homepage": "https://github.com/unicode-rs/unicode-segmentation", "documentation": "https://unicode-rs.github.io/unicode-segmentation", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "unicode-xid", "version": "0.2.2", "id": "unicode-xid 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Determine whether characters have the XID_Start\nor XID_Continue properties according to\nUnicode Standard Annex #31.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "criterion", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "unicode-xid", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/unicode-xid-0.2.2/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "exhaustive_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/unicode-xid-0.2.2/tests/exhaustive_tests.rs", "edition": "2015", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "xid", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/unicode-xid-0.2.2/benches/xid.rs", "edition": "2015", "doc": false, "doctest": false, "test": false } ], "features": { "bench": [], "default": [], "no_std": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/unicode-xid-0.2.2/Cargo.toml", "metadata": null, "publish": null, "authors": [ "erick.tryzelaar ", "kwantam ", "Manish Goregaokar " ], "categories": [], "keywords": [ "text", "unicode", "xid" ], "readme": "README.md", "repository": "https://github.com/unicode-rs/unicode-xid", "homepage": "https://github.com/unicode-rs/unicode-xid", "documentation": "https://unicode-rs.github.io/unicode-xid", "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "unicode_categories", "version": "0.1.1", "id": "unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT OR Apache-2.0", "license_file": null, "description": "Query Unicode category membership for chars", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "unicode_categories", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/unicode_categories-0.1.1/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/unicode_categories-0.1.1/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Sean Gillespie " ], "categories": [], "keywords": [ "unicode" ], "readme": "README.md", "repository": "https://github.com/swgillespie/unicode-categories", "homepage": null, "documentation": "http://swgillespie.github.io/unicode-categories/unicode_categories/", "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "untrusted", "version": "0.7.1", "id": "untrusted 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "ISC", "license_file": null, "description": "Safe, fast, zero-panic, zero-crashing, zero-allocation parsing of untrusted inputs in Rust.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "untrusted", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/untrusted-0.7.1/src/untrusted.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/untrusted-0.7.1/tests/tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/untrusted-0.7.1/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Brian Smith " ], "categories": [], "keywords": [], "readme": "README.md", "repository": "https://github.com/briansmith/untrusted", "homepage": null, "documentation": "https://briansmith.org/rustdoc/untrusted/", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "url", "version": "2.2.2", "id": "url 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "URL library for Rust, based on the WHATWG URL Standard", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "form_urlencoded", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "idna", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "matches", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "percent-encoding", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^2.1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [ "derive" ], "target": null, "registry": null }, { "name": "bencher", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde_json", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "url", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/url-2.2.2/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "data", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/url-2.2.2/tests/data.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "unit", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/url-2.2.2/tests/unit.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "bench" ], "crate_types": [ "bin" ], "name": "parse_url", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/url-2.2.2/benches/parse_url.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "serde": [ "dep:serde" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/url-2.2.2/Cargo.toml", "metadata": null, "publish": null, "authors": [ "The rust-url developers" ], "categories": [ "parser-implementations", "web-programming", "encoding" ], "keywords": [ "url", "parser" ], "readme": "../README.md", "repository": "https://github.com/servo/rust-url", "homepage": null, "documentation": "https://docs.rs/url", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "vcpkg", "version": "0.2.15", "id": "vcpkg 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "A library to find native dependencies in a vcpkg tree at build\ntime in order to be used in Cargo build scripts.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "lazy_static", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "tempdir", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.7", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "vcpkg", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/vcpkg-0.2.15/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/vcpkg-0.2.15/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Jim McGrath " ], "categories": [ "development-tools::build-utils" ], "keywords": [ "build-dependencies", "windows", "macos", "linux" ], "readme": "README.md", "repository": "https://github.com/mcgoo/vcpkg-rs", "homepage": null, "documentation": "https://docs.rs/vcpkg", "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "version_check", "version": "0.9.4", "id": "version_check 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "Tiny crate to check the version of the installed/running rustc.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "version_check", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/version_check-0.9.4/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/version_check-0.9.4/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Sergio Benitez " ], "categories": [], "keywords": [ "version", "rustc", "minimum", "check" ], "readme": "README.md", "repository": "https://github.com/SergioBenitez/version_check", "homepage": null, "documentation": "https://docs.rs/version_check/", "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "wasi", "version": "0.10.2+wasi-snapshot-preview1", "id": "wasi 0.10.2+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", "license_file": null, "description": "Experimental WASI API bindings for Rust", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "compiler_builtins", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustc-std-workspace-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": "core", "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustc-std-workspace-alloc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "wasi", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasi-0.10.2+wasi-snapshot-preview1/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": { "compiler_builtins": [ "dep:compiler_builtins" ], "core": [ "dep:core" ], "default": [ "std" ], "rustc-dep-of-std": [ "compiler_builtins", "core", "rustc-std-workspace-alloc" ], "rustc-std-workspace-alloc": [ "dep:rustc-std-workspace-alloc" ], "std": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasi-0.10.2+wasi-snapshot-preview1/Cargo.toml", "metadata": null, "publish": null, "authors": [ "The Cranelift Project Developers" ], "categories": [ "no-std", "wasm" ], "keywords": [ "webassembly", "wasm" ], "readme": "README.md", "repository": "https://github.com/bytecodealliance/wasi", "homepage": null, "documentation": "https://docs.rs/wasi", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "wasi", "version": "0.11.0+wasi-snapshot-preview1", "id": "wasi 0.11.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT", "license_file": null, "description": "Experimental WASI API bindings for Rust", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "compiler_builtins", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustc-std-workspace-core", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": "core", "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "rustc-std-workspace-alloc", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "wasi", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasi-0.11.0+wasi-snapshot-preview1/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": { "compiler_builtins": [ "dep:compiler_builtins" ], "core": [ "dep:core" ], "default": [ "std" ], "rustc-dep-of-std": [ "compiler_builtins", "core", "rustc-std-workspace-alloc" ], "rustc-std-workspace-alloc": [ "dep:rustc-std-workspace-alloc" ], "std": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasi-0.11.0+wasi-snapshot-preview1/Cargo.toml", "metadata": null, "publish": null, "authors": [ "The Cranelift Project Developers" ], "categories": [ "no-std", "wasm" ], "keywords": [ "webassembly", "wasm" ], "readme": "README.md", "repository": "https://github.com/bytecodealliance/wasi", "homepage": null, "documentation": "https://docs.rs/wasi", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "wasm-bindgen", "version": "0.2.80", "id": "wasm-bindgen 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "Easy support for interacting between JS and Rust.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "cfg-if", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "serde_json", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": true, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "wasm-bindgen-macro", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=0.2.80", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "js-sys", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.57", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(target_arch = \"wasm32\")", "registry": null }, { "name": "serde_derive", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(target_arch = \"wasm32\")", "registry": null }, { "name": "wasm-bindgen-futures", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=0.4.30", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(target_arch = \"wasm32\")", "registry": null }, { "name": "wasm-bindgen-test", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=0.3.30", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(target_arch = \"wasm32\")", "registry": null }, { "name": "wasm-bindgen-test-crate-a", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(target_arch = \"wasm32\")", "registry": null }, { "name": "wasm-bindgen-test-crate-b", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(target_arch = \"wasm32\")", "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "wasm-bindgen", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-0.2.80/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": false }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "wasm", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-0.2.80/tests/wasm/main.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "must_use", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-0.2.80/tests/must_use.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "unwrap_throw", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-0.2.80/tests/unwrap_throw.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "std-crate-no-std-dep", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-0.2.80/tests/std-crate-no-std-dep.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "headless", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-0.2.80/tests/headless/main.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "non_wasm", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-0.2.80/tests/non_wasm.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-0.2.80/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": { "default": [ "spans", "std" ], "enable-interning": [ "std" ], "nightly": [], "serde": [ "dep:serde" ], "serde-serialize": [ "serde", "serde_json", "std" ], "serde_json": [ "dep:serde_json" ], "spans": [ "wasm-bindgen-macro/spans" ], "std": [], "strict-macro": [ "wasm-bindgen-macro/strict-macro" ], "xxx_debug_only_print_generated_code": [ "wasm-bindgen-macro/xxx_debug_only_print_generated_code" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-0.2.80/Cargo.toml", "metadata": { "docs": { "rs": { "features": [ "serde-serialize" ] } } }, "publish": null, "authors": [ "The wasm-bindgen Developers" ], "categories": [ "wasm" ], "keywords": [], "readme": "README.md", "repository": "https://github.com/rustwasm/wasm-bindgen", "homepage": "https://rustwasm.github.io/", "documentation": "https://docs.rs/wasm-bindgen", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "wasm-bindgen-backend", "version": "0.2.80", "id": "wasm-bindgen-backend 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "Backend code generation of the wasm-bindgen tool\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "bumpalo", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^3.0.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "lazy_static", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.2", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "log", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "proc-macro2", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "quote", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "syn", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [ "full" ], "target": null, "registry": null }, { "name": "wasm-bindgen-shared", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=0.2.80", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "wasm-bindgen-backend", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-backend-0.2.80/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": { "extra-traits": [ "syn/extra-traits" ], "spans": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-backend-0.2.80/Cargo.toml", "metadata": null, "publish": null, "authors": [ "The wasm-bindgen Developers" ], "categories": [], "keywords": [], "readme": null, "repository": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/backend", "homepage": "https://rustwasm.github.io/wasm-bindgen/", "documentation": "https://docs.rs/wasm-bindgen-backend", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "wasm-bindgen-macro", "version": "0.2.80", "id": "wasm-bindgen-macro 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "Definition of the `#[wasm_bindgen]` attribute, an internal dependency\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "quote", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "wasm-bindgen-macro-support", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=0.2.80", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "trybuild", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "wasm-bindgen", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.80", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [ "strict-macro" ], "target": null, "registry": null }, { "name": "wasm-bindgen-futures", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.30", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "proc-macro" ], "crate_types": [ "proc-macro" ], "name": "wasm-bindgen-macro", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-macro-0.2.80/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "ui", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-macro-0.2.80/tests/ui.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "spans": [ "wasm-bindgen-macro-support/spans" ], "strict-macro": [ "wasm-bindgen-macro-support/strict-macro" ], "xxx_debug_only_print_generated_code": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-macro-0.2.80/Cargo.toml", "metadata": null, "publish": null, "authors": [ "The wasm-bindgen Developers" ], "categories": [], "keywords": [], "readme": "README.md", "repository": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro", "homepage": "https://rustwasm.github.io/wasm-bindgen/", "documentation": "https://docs.rs/wasm-bindgen", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "wasm-bindgen-macro-support", "version": "0.2.80", "id": "wasm-bindgen-macro-support 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "The part of the implementation of the `#[wasm_bindgen]` attribute that is not in the shared backend crate\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "proc-macro2", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "quote", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "syn", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^1.0.67", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [ "visit", "full" ], "target": null, "registry": null }, { "name": "wasm-bindgen-backend", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=0.2.80", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "wasm-bindgen-shared", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "=0.2.80", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "wasm-bindgen-macro-support", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-macro-support-0.2.80/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true } ], "features": { "extra-traits": [ "syn/extra-traits" ], "spans": [ "wasm-bindgen-backend/spans" ], "strict-macro": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-macro-support-0.2.80/Cargo.toml", "metadata": null, "publish": null, "authors": [ "The wasm-bindgen Developers" ], "categories": [], "keywords": [], "readme": null, "repository": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro-support", "homepage": "https://rustwasm.github.io/wasm-bindgen/", "documentation": "https://docs.rs/wasm-bindgen", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "wasm-bindgen-shared", "version": "0.2.80", "id": "wasm-bindgen-shared 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "Shared support between wasm-bindgen and wasm-bindgen cli, an internal\ndependency.\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "wasm-bindgen-shared", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-shared-0.2.80/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-shared-0.2.80/build.rs", "edition": "2018", "doc": false, "doctest": false, "test": false } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-shared-0.2.80/Cargo.toml", "metadata": null, "publish": null, "authors": [ "The wasm-bindgen Developers" ], "categories": [], "keywords": [], "readme": null, "repository": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/shared", "homepage": "https://rustwasm.github.io/wasm-bindgen/", "documentation": "https://docs.rs/wasm-bindgen-shared", "edition": "2018", "links": "wasm_bindgen", "default_run": null, "rust_version": null }, { "name": "web-sys", "version": "0.3.57", "id": "web-sys 0.3.57 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "Bindings for all Web APIs, a procedurally generated crate from WebIDL\n", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "js-sys", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.57", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "wasm-bindgen", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.2.80", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "wasm-bindgen-futures", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4.30", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(target_arch = \"wasm32\")", "registry": null }, { "name": "wasm-bindgen-test", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.3.30", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "cfg(target_arch = \"wasm32\")", "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "web-sys", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/web-sys-0.3.57/src/lib.rs", "edition": "2018", "doc": true, "doctest": false, "test": false }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "wasm", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/web-sys-0.3.57/tests/wasm/main.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "AbortController": [], "AbortSignal": [ "EventTarget" ], "AddEventListenerOptions": [], "AesCbcParams": [], "AesCtrParams": [], "AesDerivedKeyParams": [], "AesGcmParams": [], "AesKeyAlgorithm": [], "AesKeyGenParams": [], "Algorithm": [], "AlignSetting": [], "AllowedBluetoothDevice": [], "AllowedUsbDevice": [], "AlphaOption": [], "AnalyserNode": [ "AudioNode", "EventTarget" ], "AnalyserOptions": [], "AngleInstancedArrays": [], "Animation": [ "EventTarget" ], "AnimationEffect": [], "AnimationEvent": [ "Event" ], "AnimationEventInit": [], "AnimationPlayState": [], "AnimationPlaybackEvent": [ "Event" ], "AnimationPlaybackEventInit": [], "AnimationPropertyDetails": [], "AnimationPropertyValueDetails": [], "AnimationTimeline": [], "AssignedNodesOptions": [], "AttestationConveyancePreference": [], "Attr": [ "EventTarget", "Node" ], "AttributeNameValue": [], "AudioBuffer": [], "AudioBufferOptions": [], "AudioBufferSourceNode": [ "AudioNode", "AudioScheduledSourceNode", "EventTarget" ], "AudioBufferSourceOptions": [], "AudioConfiguration": [], "AudioContext": [ "BaseAudioContext", "EventTarget" ], "AudioContextOptions": [], "AudioContextState": [], "AudioData": [], "AudioDataCopyToOptions": [], "AudioDataInit": [], "AudioDecoder": [], "AudioDecoderConfig": [], "AudioDecoderInit": [], "AudioDecoderSupport": [], "AudioDestinationNode": [ "AudioNode", "EventTarget" ], "AudioEncoder": [], "AudioEncoderConfig": [], "AudioEncoderInit": [], "AudioEncoderSupport": [], "AudioListener": [], "AudioNode": [ "EventTarget" ], "AudioNodeOptions": [], "AudioParam": [], "AudioParamMap": [], "AudioProcessingEvent": [ "Event" ], "AudioSampleFormat": [], "AudioScheduledSourceNode": [ "AudioNode", "EventTarget" ], "AudioStreamTrack": [ "EventTarget", "MediaStreamTrack" ], "AudioTrack": [], "AudioTrackList": [ "EventTarget" ], "AudioWorklet": [ "Worklet" ], "AudioWorkletGlobalScope": [ "WorkletGlobalScope" ], "AudioWorkletNode": [ "AudioNode", "EventTarget" ], "AudioWorkletNodeOptions": [], "AudioWorkletProcessor": [], "AuthenticationExtensionsClientInputs": [], "AuthenticationExtensionsClientOutputs": [], "AuthenticatorAssertionResponse": [ "AuthenticatorResponse" ], "AuthenticatorAttachment": [], "AuthenticatorAttestationResponse": [ "AuthenticatorResponse" ], "AuthenticatorResponse": [], "AuthenticatorSelectionCriteria": [], "AuthenticatorTransport": [], "AutoKeyword": [], "AutocompleteInfo": [], "BarProp": [], "BaseAudioContext": [ "EventTarget" ], "BaseComputedKeyframe": [], "BaseKeyframe": [], "BasePropertyIndexedKeyframe": [], "BasicCardRequest": [], "BasicCardResponse": [], "BasicCardType": [], "BatteryManager": [ "EventTarget" ], "BeforeUnloadEvent": [ "Event" ], "BinaryType": [], "BiquadFilterNode": [ "AudioNode", "EventTarget" ], "BiquadFilterOptions": [], "BiquadFilterType": [], "Blob": [], "BlobEvent": [ "Event" ], "BlobEventInit": [], "BlobPropertyBag": [], "BlockParsingOptions": [], "Bluetooth": [ "EventTarget" ], "BluetoothAdvertisingEvent": [ "Event" ], "BluetoothAdvertisingEventInit": [], "BluetoothCharacteristicProperties": [], "BluetoothDataFilterInit": [], "BluetoothDevice": [ "EventTarget" ], "BluetoothLeScanFilterInit": [], "BluetoothManufacturerDataMap": [], "BluetoothPermissionDescriptor": [], "BluetoothPermissionResult": [ "EventTarget", "PermissionStatus" ], "BluetoothPermissionStorage": [], "BluetoothRemoteGattCharacteristic": [ "EventTarget" ], "BluetoothRemoteGattDescriptor": [], "BluetoothRemoteGattServer": [], "BluetoothRemoteGattService": [ "EventTarget" ], "BluetoothServiceDataMap": [], "BluetoothUuid": [], "BoxQuadOptions": [], "BroadcastChannel": [ "EventTarget" ], "BrowserElementDownloadOptions": [], "BrowserElementExecuteScriptOptions": [], "BrowserFeedWriter": [], "BrowserFindCaseSensitivity": [], "BrowserFindDirection": [], "Cache": [], "CacheBatchOperation": [], "CacheQueryOptions": [], "CacheStorage": [], "CacheStorageNamespace": [], "CanvasCaptureMediaStream": [ "EventTarget", "MediaStream" ], "CanvasGradient": [], "CanvasPattern": [], "CanvasRenderingContext2d": [], "CanvasWindingRule": [], "CaretChangedReason": [], "CaretPosition": [], "CaretStateChangedEventInit": [], "CdataSection": [ "CharacterData", "EventTarget", "Node", "Text" ], "ChannelCountMode": [], "ChannelInterpretation": [], "ChannelMergerNode": [ "AudioNode", "EventTarget" ], "ChannelMergerOptions": [], "ChannelPixelLayout": [], "ChannelPixelLayoutDataType": [], "ChannelSplitterNode": [ "AudioNode", "EventTarget" ], "ChannelSplitterOptions": [], "CharacterData": [ "EventTarget", "Node" ], "CheckerboardReason": [], "CheckerboardReport": [], "CheckerboardReportService": [], "ChromeFilePropertyBag": [], "ChromeWorker": [ "EventTarget", "Worker" ], "Client": [], "ClientQueryOptions": [], "ClientRectsAndTexts": [], "ClientType": [], "Clients": [], "Clipboard": [ "EventTarget" ], "ClipboardEvent": [ "Event" ], "ClipboardEventInit": [], "ClipboardItem": [], "ClipboardItemOptions": [], "ClipboardPermissionDescriptor": [], "CloseEvent": [ "Event" ], "CloseEventInit": [], "CodecState": [], "CollectedClientData": [], "Comment": [ "CharacterData", "EventTarget", "Node" ], "CompositeOperation": [], "CompositionEvent": [ "Event", "UiEvent" ], "CompositionEventInit": [], "ComputedEffectTiming": [], "ConnStatusDict": [], "ConnectionType": [], "ConsoleCounter": [], "ConsoleCounterError": [], "ConsoleEvent": [], "ConsoleInstance": [], "ConsoleInstanceOptions": [], "ConsoleLevel": [], "ConsoleLogLevel": [], "ConsoleProfileEvent": [], "ConsoleStackEntry": [], "ConsoleTimerError": [], "ConsoleTimerLogOrEnd": [], "ConsoleTimerStart": [], "ConstantSourceNode": [ "AudioNode", "AudioScheduledSourceNode", "EventTarget" ], "ConstantSourceOptions": [], "ConstrainBooleanParameters": [], "ConstrainDomStringParameters": [], "ConstrainDoubleRange": [], "ConstrainLongRange": [], "ContextAttributes2d": [], "ConvertCoordinateOptions": [], "ConvolverNode": [ "AudioNode", "EventTarget" ], "ConvolverOptions": [], "Coordinates": [], "Credential": [], "CredentialCreationOptions": [], "CredentialRequestOptions": [], "CredentialsContainer": [], "Crypto": [], "CryptoKey": [], "CryptoKeyPair": [], "Csp": [], "CspPolicies": [], "CspReport": [], "CspReportProperties": [], "CssAnimation": [ "Animation", "EventTarget" ], "CssBoxType": [], "CssConditionRule": [ "CssGroupingRule", "CssRule" ], "CssCounterStyleRule": [ "CssRule" ], "CssFontFaceRule": [ "CssRule" ], "CssFontFeatureValuesRule": [ "CssRule" ], "CssGroupingRule": [ "CssRule" ], "CssImportRule": [ "CssRule" ], "CssKeyframeRule": [ "CssRule" ], "CssKeyframesRule": [ "CssRule" ], "CssMediaRule": [ "CssConditionRule", "CssGroupingRule", "CssRule" ], "CssNamespaceRule": [ "CssRule" ], "CssPageRule": [ "CssRule" ], "CssPseudoElement": [], "CssRule": [], "CssRuleList": [], "CssStyleDeclaration": [], "CssStyleRule": [ "CssRule" ], "CssStyleSheet": [ "StyleSheet" ], "CssStyleSheetParsingMode": [], "CssSupportsRule": [ "CssConditionRule", "CssGroupingRule", "CssRule" ], "CssTransition": [ "Animation", "EventTarget" ], "CustomElementRegistry": [], "CustomEvent": [ "Event" ], "CustomEventInit": [], "DataTransfer": [], "DataTransferItem": [], "DataTransferItemList": [], "DateTimeValue": [], "DecoderDoctorNotification": [], "DecoderDoctorNotificationType": [], "DedicatedWorkerGlobalScope": [ "EventTarget", "WorkerGlobalScope" ], "DelayNode": [ "AudioNode", "EventTarget" ], "DelayOptions": [], "DeviceAcceleration": [], "DeviceAccelerationInit": [], "DeviceLightEvent": [ "Event" ], "DeviceLightEventInit": [], "DeviceMotionEvent": [ "Event" ], "DeviceMotionEventInit": [], "DeviceOrientationEvent": [ "Event" ], "DeviceOrientationEventInit": [], "DeviceProximityEvent": [ "Event" ], "DeviceProximityEventInit": [], "DeviceRotationRate": [], "DeviceRotationRateInit": [], "DhKeyDeriveParams": [], "DirectionSetting": [], "Directory": [], "DisplayMediaStreamConstraints": [], "DisplayNameOptions": [], "DisplayNameResult": [], "DistanceModelType": [], "DnsCacheDict": [], "DnsCacheEntry": [], "DnsLookupDict": [], "Document": [ "EventTarget", "Node" ], "DocumentFragment": [ "EventTarget", "Node" ], "DocumentTimeline": [ "AnimationTimeline" ], "DocumentTimelineOptions": [], "DocumentType": [ "EventTarget", "Node" ], "DomError": [], "DomException": [], "DomImplementation": [], "DomMatrix": [ "DomMatrixReadOnly" ], "DomMatrixReadOnly": [], "DomParser": [], "DomPoint": [ "DomPointReadOnly" ], "DomPointInit": [], "DomPointReadOnly": [], "DomQuad": [], "DomQuadInit": [], "DomQuadJson": [], "DomRect": [ "DomRectReadOnly" ], "DomRectInit": [], "DomRectList": [], "DomRectReadOnly": [], "DomRequest": [ "EventTarget" ], "DomRequestReadyState": [], "DomStringList": [], "DomStringMap": [], "DomTokenList": [], "DomWindowResizeEventDetail": [], "DragEvent": [ "Event", "MouseEvent", "UiEvent" ], "DragEventInit": [], "DynamicsCompressorNode": [ "AudioNode", "EventTarget" ], "DynamicsCompressorOptions": [], "EcKeyAlgorithm": [], "EcKeyGenParams": [], "EcKeyImportParams": [], "EcdhKeyDeriveParams": [], "EcdsaParams": [], "EffectTiming": [], "Element": [ "EventTarget", "Node" ], "ElementCreationOptions": [], "ElementDefinitionOptions": [], "EncodedAudioChunk": [], "EncodedAudioChunkInit": [], "EncodedAudioChunkMetadata": [], "EncodedAudioChunkType": [], "EncodedVideoChunk": [], "EncodedVideoChunkInit": [], "EncodedVideoChunkMetadata": [], "EncodedVideoChunkType": [], "EndingTypes": [], "ErrorCallback": [], "ErrorEvent": [ "Event" ], "ErrorEventInit": [], "Event": [], "EventInit": [], "EventListener": [], "EventListenerOptions": [], "EventModifierInit": [], "EventSource": [ "EventTarget" ], "EventSourceInit": [], "EventTarget": [], "Exception": [], "ExtBlendMinmax": [], "ExtColorBufferFloat": [], "ExtColorBufferHalfFloat": [], "ExtDisjointTimerQuery": [], "ExtFragDepth": [], "ExtSRgb": [], "ExtShaderTextureLod": [], "ExtTextureFilterAnisotropic": [], "ExtendableEvent": [ "Event" ], "ExtendableEventInit": [], "ExtendableMessageEvent": [ "Event", "ExtendableEvent" ], "ExtendableMessageEventInit": [], "External": [], "FakePluginMimeEntry": [], "FakePluginTagInit": [], "FetchEvent": [ "Event", "ExtendableEvent" ], "FetchEventInit": [], "FetchObserver": [ "EventTarget" ], "FetchReadableStreamReadDataArray": [], "FetchReadableStreamReadDataDone": [], "FetchState": [], "File": [ "Blob" ], "FileCallback": [], "FileList": [], "FilePropertyBag": [], "FileReader": [ "EventTarget" ], "FileReaderSync": [], "FileSystem": [], "FileSystemDirectoryEntry": [ "FileSystemEntry" ], "FileSystemDirectoryReader": [], "FileSystemEntriesCallback": [], "FileSystemEntry": [], "FileSystemEntryCallback": [], "FileSystemFileEntry": [ "FileSystemEntry" ], "FileSystemFlags": [], "FillMode": [], "FlashClassification": [], "FlexLineGrowthState": [], "FocusEvent": [ "Event", "UiEvent" ], "FocusEventInit": [], "FontFace": [], "FontFaceDescriptors": [], "FontFaceLoadStatus": [], "FontFaceSet": [ "EventTarget" ], "FontFaceSetIterator": [], "FontFaceSetIteratorResult": [], "FontFaceSetLoadEvent": [ "Event" ], "FontFaceSetLoadEventInit": [], "FontFaceSetLoadStatus": [], "FormData": [], "FrameType": [], "FuzzingFunctions": [], "GainNode": [ "AudioNode", "EventTarget" ], "GainOptions": [], "Gamepad": [], "GamepadAxisMoveEvent": [ "Event", "GamepadEvent" ], "GamepadAxisMoveEventInit": [], "GamepadButton": [], "GamepadButtonEvent": [ "Event", "GamepadEvent" ], "GamepadButtonEventInit": [], "GamepadEvent": [ "Event" ], "GamepadEventInit": [], "GamepadHand": [], "GamepadHapticActuator": [], "GamepadHapticActuatorType": [], "GamepadMappingType": [], "GamepadPose": [], "GamepadServiceTest": [], "Geolocation": [], "GetNotificationOptions": [], "GetRootNodeOptions": [], "GetUserMediaRequest": [], "Gpu": [], "GpuAdapter": [], "GpuAddressMode": [], "GpuBindGroup": [], "GpuBindGroupDescriptor": [], "GpuBindGroupEntry": [], "GpuBindGroupLayout": [], "GpuBindGroupLayoutDescriptor": [], "GpuBindGroupLayoutEntry": [], "GpuBlendComponent": [], "GpuBlendFactor": [], "GpuBlendOperation": [], "GpuBlendState": [], "GpuBuffer": [], "GpuBufferBinding": [], "GpuBufferBindingLayout": [], "GpuBufferBindingType": [], "GpuBufferDescriptor": [], "GpuBufferUsage": [], "GpuCanvasCompositingAlphaMode": [], "GpuCanvasConfiguration": [], "GpuCanvasContext": [], "GpuColorDict": [], "GpuColorTargetState": [], "GpuColorWrite": [], "GpuCommandBuffer": [], "GpuCommandBufferDescriptor": [], "GpuCommandEncoder": [], "GpuCommandEncoderDescriptor": [], "GpuCompareFunction": [], "GpuCompilationInfo": [], "GpuCompilationMessage": [], "GpuCompilationMessageType": [], "GpuComputePassDescriptor": [], "GpuComputePassEncoder": [], "GpuComputePipeline": [], "GpuComputePipelineDescriptor": [], "GpuCullMode": [], "GpuDepthStencilState": [], "GpuDevice": [ "EventTarget" ], "GpuDeviceDescriptor": [], "GpuDeviceLostInfo": [], "GpuDeviceLostReason": [], "GpuErrorFilter": [], "GpuExtent3dDict": [], "GpuExternalTexture": [], "GpuExternalTextureBindingLayout": [], "GpuExternalTextureDescriptor": [], "GpuFeatureName": [], "GpuFilterMode": [], "GpuFragmentState": [], "GpuFrontFace": [], "GpuImageCopyBuffer": [], "GpuImageCopyExternalImage": [], "GpuImageCopyTexture": [], "GpuImageCopyTextureTagged": [], "GpuImageDataLayout": [], "GpuIndexFormat": [], "GpuLoadOp": [], "GpuMapMode": [], "GpuMultisampleState": [], "GpuObjectDescriptorBase": [], "GpuOrigin2dDict": [], "GpuOrigin3dDict": [], "GpuOutOfMemoryError": [], "GpuPipelineDescriptorBase": [], "GpuPipelineLayout": [], "GpuPipelineLayoutDescriptor": [], "GpuPipelineStatisticName": [], "GpuPowerPreference": [], "GpuPredefinedColorSpace": [], "GpuPrimitiveState": [], "GpuPrimitiveTopology": [], "GpuProgrammableStage": [], "GpuQuerySet": [], "GpuQuerySetDescriptor": [], "GpuQueryType": [], "GpuQueue": [], "GpuRenderBundle": [], "GpuRenderBundleDescriptor": [], "GpuRenderBundleEncoder": [], "GpuRenderBundleEncoderDescriptor": [], "GpuRenderPassColorAttachment": [], "GpuRenderPassDepthStencilAttachment": [], "GpuRenderPassDescriptor": [], "GpuRenderPassEncoder": [], "GpuRenderPassLayout": [], "GpuRenderPipeline": [], "GpuRenderPipelineDescriptor": [], "GpuRequestAdapterOptions": [], "GpuSampler": [], "GpuSamplerBindingLayout": [], "GpuSamplerBindingType": [], "GpuSamplerDescriptor": [], "GpuShaderModule": [], "GpuShaderModuleDescriptor": [], "GpuShaderStage": [], "GpuStencilFaceState": [], "GpuStencilOperation": [], "GpuStorageTextureAccess": [], "GpuStorageTextureBindingLayout": [], "GpuStoreOp": [], "GpuSupportedFeatures": [], "GpuSupportedLimits": [], "GpuTexture": [], "GpuTextureAspect": [], "GpuTextureBindingLayout": [], "GpuTextureDescriptor": [], "GpuTextureDimension": [], "GpuTextureFormat": [], "GpuTextureSampleType": [], "GpuTextureUsage": [], "GpuTextureView": [], "GpuTextureViewDescriptor": [], "GpuTextureViewDimension": [], "GpuUncapturedErrorEvent": [ "Event" ], "GpuUncapturedErrorEventInit": [], "GpuValidationError": [], "GpuVertexAttribute": [], "GpuVertexBufferLayout": [], "GpuVertexFormat": [], "GpuVertexState": [], "GpuVertexStepMode": [], "GridDeclaration": [], "GridTrackState": [], "GroupedHistoryEventInit": [], "HalfOpenInfoDict": [], "HardwareAcceleration": [], "HashChangeEvent": [ "Event" ], "HashChangeEventInit": [], "Headers": [], "HeadersGuardEnum": [], "Hid": [ "EventTarget" ], "HidCollectionInfo": [], "HidConnectionEvent": [ "Event" ], "HidConnectionEventInit": [], "HidDevice": [ "EventTarget" ], "HidDeviceFilter": [], "HidDeviceRequestOptions": [], "HidInputReportEvent": [ "Event" ], "HidInputReportEventInit": [], "HidReportInfo": [], "HidReportItem": [], "HidUnitSystem": [], "HiddenPluginEventInit": [], "History": [], "HitRegionOptions": [], "HkdfParams": [], "HmacDerivedKeyParams": [], "HmacImportParams": [], "HmacKeyAlgorithm": [], "HmacKeyGenParams": [], "HtmlAllCollection": [], "HtmlAnchorElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlAreaElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlAudioElement": [ "Element", "EventTarget", "HtmlElement", "HtmlMediaElement", "Node" ], "HtmlBaseElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlBodyElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlBrElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlButtonElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlCanvasElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlCollection": [], "HtmlDListElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlDataElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlDataListElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlDetailsElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlDialogElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlDirectoryElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlDivElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlDocument": [ "Document", "EventTarget", "Node" ], "HtmlElement": [ "Element", "EventTarget", "Node" ], "HtmlEmbedElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlFieldSetElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlFontElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlFormControlsCollection": [ "HtmlCollection" ], "HtmlFormElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlFrameElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlFrameSetElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlHeadElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlHeadingElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlHrElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlHtmlElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlIFrameElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlImageElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlInputElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlLabelElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlLegendElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlLiElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlLinkElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlMapElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlMediaElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlMenuElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlMenuItemElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlMetaElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlMeterElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlModElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlOListElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlObjectElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlOptGroupElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlOptionElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlOptionsCollection": [ "HtmlCollection" ], "HtmlOutputElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlParagraphElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlParamElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlPictureElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlPreElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlProgressElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlQuoteElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlScriptElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlSelectElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlSlotElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlSourceElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlSpanElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlStyleElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlTableCaptionElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlTableCellElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlTableColElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlTableElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlTableRowElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlTableSectionElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlTemplateElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlTextAreaElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlTimeElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlTitleElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlTrackElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlUListElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlUnknownElement": [ "Element", "EventTarget", "HtmlElement", "Node" ], "HtmlVideoElement": [ "Element", "EventTarget", "HtmlElement", "HtmlMediaElement", "Node" ], "HttpConnDict": [], "HttpConnInfo": [], "HttpConnectionElement": [], "IdbCursor": [], "IdbCursorDirection": [], "IdbCursorWithValue": [ "IdbCursor" ], "IdbDatabase": [ "EventTarget" ], "IdbFactory": [], "IdbFileHandle": [ "EventTarget" ], "IdbFileMetadataParameters": [], "IdbFileRequest": [ "DomRequest", "EventTarget" ], "IdbIndex": [], "IdbIndexParameters": [], "IdbKeyRange": [], "IdbLocaleAwareKeyRange": [ "IdbKeyRange" ], "IdbMutableFile": [ "EventTarget" ], "IdbObjectStore": [], "IdbObjectStoreParameters": [], "IdbOpenDbOptions": [], "IdbOpenDbRequest": [ "EventTarget", "IdbRequest" ], "IdbRequest": [ "EventTarget" ], "IdbRequestReadyState": [], "IdbTransaction": [ "EventTarget" ], "IdbTransactionMode": [], "IdbVersionChangeEvent": [ "Event" ], "IdbVersionChangeEventInit": [], "IdleDeadline": [], "IdleRequestOptions": [], "IirFilterNode": [ "AudioNode", "EventTarget" ], "IirFilterOptions": [], "ImageBitmap": [], "ImageBitmapFormat": [], "ImageBitmapRenderingContext": [], "ImageCapture": [], "ImageCaptureError": [], "ImageCaptureErrorEvent": [ "Event" ], "ImageCaptureErrorEventInit": [], "ImageData": [], "ImageDecodeOptions": [], "ImageDecodeResult": [], "ImageDecoder": [], "ImageDecoderInit": [], "ImageTrack": [ "EventTarget" ], "ImageTrackList": [], "InputEvent": [ "Event", "UiEvent" ], "InputEventInit": [], "InstallTriggerData": [], "IntersectionObserver": [], "IntersectionObserverEntry": [], "IntersectionObserverEntryInit": [], "IntersectionObserverInit": [], "IntlUtils": [], "IterableKeyAndValueResult": [], "IterableKeyOrValueResult": [], "IterationCompositeOperation": [], "JsonWebKey": [], "KeyAlgorithm": [], "KeyEvent": [], "KeyIdsInitData": [], "KeyboardEvent": [ "Event", "UiEvent" ], "KeyboardEventInit": [], "KeyframeEffect": [ "AnimationEffect" ], "KeyframeEffectOptions": [], "L10nElement": [], "L10nValue": [], "LatencyMode": [], "LifecycleCallbacks": [], "LineAlignSetting": [], "ListBoxObject": [], "LocalMediaStream": [ "EventTarget", "MediaStream" ], "LocaleInfo": [], "Location": [], "MediaCapabilities": [], "MediaCapabilitiesInfo": [], "MediaConfiguration": [], "MediaDecodingConfiguration": [], "MediaDecodingType": [], "MediaDeviceInfo": [], "MediaDeviceKind": [], "MediaDevices": [ "EventTarget" ], "MediaElementAudioSourceNode": [ "AudioNode", "EventTarget" ], "MediaElementAudioSourceOptions": [], "MediaEncodingConfiguration": [], "MediaEncodingType": [], "MediaEncryptedEvent": [ "Event" ], "MediaError": [], "MediaKeyError": [ "Event" ], "MediaKeyMessageEvent": [ "Event" ], "MediaKeyMessageEventInit": [], "MediaKeyMessageType": [], "MediaKeyNeededEventInit": [], "MediaKeySession": [ "EventTarget" ], "MediaKeySessionType": [], "MediaKeyStatus": [], "MediaKeyStatusMap": [], "MediaKeySystemAccess": [], "MediaKeySystemConfiguration": [], "MediaKeySystemMediaCapability": [], "MediaKeySystemStatus": [], "MediaKeys": [], "MediaKeysPolicy": [], "MediaKeysRequirement": [], "MediaList": [], "MediaQueryList": [ "EventTarget" ], "MediaQueryListEvent": [ "Event" ], "MediaQueryListEventInit": [], "MediaRecorder": [ "EventTarget" ], "MediaRecorderErrorEvent": [ "Event" ], "MediaRecorderErrorEventInit": [], "MediaRecorderOptions": [], "MediaSource": [ "EventTarget" ], "MediaSourceEndOfStreamError": [], "MediaSourceEnum": [], "MediaSourceReadyState": [], "MediaStream": [ "EventTarget" ], "MediaStreamAudioDestinationNode": [ "AudioNode", "EventTarget" ], "MediaStreamAudioSourceNode": [ "AudioNode", "EventTarget" ], "MediaStreamAudioSourceOptions": [], "MediaStreamConstraints": [], "MediaStreamError": [], "MediaStreamEvent": [ "Event" ], "MediaStreamEventInit": [], "MediaStreamTrack": [ "EventTarget" ], "MediaStreamTrackEvent": [ "Event" ], "MediaStreamTrackEventInit": [], "MediaStreamTrackState": [], "MediaTrackConstraintSet": [], "MediaTrackConstraints": [], "MediaTrackSettings": [], "MediaTrackSupportedConstraints": [], "MessageChannel": [], "MessageEvent": [ "Event" ], "MessageEventInit": [], "MessagePort": [ "EventTarget" ], "MidiAccess": [ "EventTarget" ], "MidiConnectionEvent": [ "Event" ], "MidiConnectionEventInit": [], "MidiInput": [ "EventTarget", "MidiPort" ], "MidiInputMap": [], "MidiMessageEvent": [ "Event" ], "MidiMessageEventInit": [], "MidiOptions": [], "MidiOutput": [ "EventTarget", "MidiPort" ], "MidiOutputMap": [], "MidiPort": [ "EventTarget" ], "MidiPortConnectionState": [], "MidiPortDeviceState": [], "MidiPortType": [], "MimeType": [], "MimeTypeArray": [], "MouseEvent": [ "Event", "UiEvent" ], "MouseEventInit": [], "MouseScrollEvent": [ "Event", "MouseEvent", "UiEvent" ], "MozDebug": [], "MutationEvent": [ "Event" ], "MutationObserver": [], "MutationObserverInit": [], "MutationObservingInfo": [], "MutationRecord": [], "NamedNodeMap": [], "NativeOsFileReadOptions": [], "NativeOsFileWriteAtomicOptions": [], "NavigationType": [], "Navigator": [], "NavigatorAutomationInformation": [], "NetworkCommandOptions": [], "NetworkInformation": [ "EventTarget" ], "NetworkResultOptions": [], "Node": [ "EventTarget" ], "NodeFilter": [], "NodeIterator": [], "NodeList": [], "Notification": [ "EventTarget" ], "NotificationBehavior": [], "NotificationDirection": [], "NotificationEvent": [ "Event", "ExtendableEvent" ], "NotificationEventInit": [], "NotificationOptions": [], "NotificationPermission": [], "ObserverCallback": [], "OesElementIndexUint": [], "OesStandardDerivatives": [], "OesTextureFloat": [], "OesTextureFloatLinear": [], "OesTextureHalfFloat": [], "OesTextureHalfFloatLinear": [], "OesVertexArrayObject": [], "OfflineAudioCompletionEvent": [ "Event" ], "OfflineAudioCompletionEventInit": [], "OfflineAudioContext": [ "BaseAudioContext", "EventTarget" ], "OfflineAudioContextOptions": [], "OfflineResourceList": [ "EventTarget" ], "OffscreenCanvas": [ "EventTarget" ], "OpenWindowEventDetail": [], "OptionalEffectTiming": [], "OrientationLockType": [], "OrientationType": [], "OscillatorNode": [ "AudioNode", "AudioScheduledSourceNode", "EventTarget" ], "OscillatorOptions": [], "OscillatorType": [], "OverSampleType": [], "PageTransitionEvent": [ "Event" ], "PageTransitionEventInit": [], "PaintRequest": [], "PaintRequestList": [], "PaintWorkletGlobalScope": [ "WorkletGlobalScope" ], "PannerNode": [ "AudioNode", "EventTarget" ], "PannerOptions": [], "PanningModelType": [], "Path2d": [], "PaymentAddress": [], "PaymentComplete": [], "PaymentMethodChangeEvent": [ "Event", "PaymentRequestUpdateEvent" ], "PaymentMethodChangeEventInit": [], "PaymentRequestUpdateEvent": [ "Event" ], "PaymentRequestUpdateEventInit": [], "PaymentResponse": [], "Pbkdf2Params": [], "PcImplIceConnectionState": [], "PcImplIceGatheringState": [], "PcImplSignalingState": [], "PcObserverStateType": [], "Performance": [ "EventTarget" ], "PerformanceEntry": [], "PerformanceEntryEventInit": [], "PerformanceEntryFilterOptions": [], "PerformanceMark": [ "PerformanceEntry" ], "PerformanceMeasure": [ "PerformanceEntry" ], "PerformanceNavigation": [], "PerformanceNavigationTiming": [ "PerformanceEntry", "PerformanceResourceTiming" ], "PerformanceObserver": [], "PerformanceObserverEntryList": [], "PerformanceObserverInit": [], "PerformanceResourceTiming": [ "PerformanceEntry" ], "PerformanceServerTiming": [], "PerformanceTiming": [], "PeriodicWave": [], "PeriodicWaveConstraints": [], "PeriodicWaveOptions": [], "PermissionDescriptor": [], "PermissionName": [], "PermissionState": [], "PermissionStatus": [ "EventTarget" ], "Permissions": [], "PlaneLayout": [], "PlaybackDirection": [], "Plugin": [], "PluginArray": [], "PluginCrashedEventInit": [], "PointerEvent": [ "Event", "MouseEvent", "UiEvent" ], "PointerEventInit": [], "PopStateEvent": [ "Event" ], "PopStateEventInit": [], "PopupBlockedEvent": [ "Event" ], "PopupBlockedEventInit": [], "Position": [], "PositionAlignSetting": [], "PositionError": [], "PositionOptions": [], "Presentation": [], "PresentationAvailability": [ "EventTarget" ], "PresentationConnection": [ "EventTarget" ], "PresentationConnectionAvailableEvent": [ "Event" ], "PresentationConnectionAvailableEventInit": [], "PresentationConnectionBinaryType": [], "PresentationConnectionCloseEvent": [ "Event" ], "PresentationConnectionCloseEventInit": [], "PresentationConnectionClosedReason": [], "PresentationConnectionList": [ "EventTarget" ], "PresentationConnectionState": [], "PresentationReceiver": [], "PresentationRequest": [ "EventTarget" ], "PresentationStyle": [], "ProcessingInstruction": [ "CharacterData", "EventTarget", "Node" ], "ProfileTimelineLayerRect": [], "ProfileTimelineMarker": [], "ProfileTimelineMessagePortOperationType": [], "ProfileTimelineStackFrame": [], "ProfileTimelineWorkerOperationType": [], "ProgressEvent": [ "Event" ], "ProgressEventInit": [], "PromiseNativeHandler": [], "PromiseRejectionEvent": [ "Event" ], "PromiseRejectionEventInit": [], "PublicKeyCredential": [ "Credential" ], "PublicKeyCredentialCreationOptions": [], "PublicKeyCredentialDescriptor": [], "PublicKeyCredentialEntity": [], "PublicKeyCredentialParameters": [], "PublicKeyCredentialRequestOptions": [], "PublicKeyCredentialRpEntity": [], "PublicKeyCredentialType": [], "PublicKeyCredentialUserEntity": [], "PushEncryptionKeyName": [], "PushEvent": [ "Event", "ExtendableEvent" ], "PushEventInit": [], "PushManager": [], "PushMessageData": [], "PushPermissionState": [], "PushSubscription": [], "PushSubscriptionInit": [], "PushSubscriptionJson": [], "PushSubscriptionKeys": [], "PushSubscriptionOptions": [], "PushSubscriptionOptionsInit": [], "QueuingStrategy": [], "RadioNodeList": [ "NodeList" ], "Range": [], "RcwnPerfStats": [], "RcwnStatus": [], "ReadableStream": [], "ReadableStreamByobReadResult": [], "ReadableStreamByobReader": [], "ReadableStreamDefaultReadResult": [], "ReadableStreamDefaultReader": [], "ReadableStreamGetReaderOptions": [], "ReadableStreamIteratorOptions": [], "ReadableStreamReaderMode": [], "ReadableWritablePair": [], "RecordingState": [], "ReferrerPolicy": [], "RegisterRequest": [], "RegisterResponse": [], "RegisteredKey": [], "RegistrationOptions": [], "Request": [], "RequestCache": [], "RequestCredentials": [], "RequestDestination": [], "RequestDeviceOptions": [], "RequestInit": [], "RequestMediaKeySystemAccessNotification": [], "RequestMode": [], "RequestRedirect": [], "ResizeObserver": [], "ResizeObserverBoxOptions": [], "ResizeObserverEntry": [], "ResizeObserverOptions": [], "ResizeObserverSize": [], "Response": [], "ResponseInit": [], "ResponseType": [], "RsaHashedImportParams": [], "RsaOaepParams": [], "RsaOtherPrimesInfo": [], "RsaPssParams": [], "RtcAnswerOptions": [], "RtcBundlePolicy": [], "RtcCertificate": [], "RtcCertificateExpiration": [], "RtcCodecStats": [], "RtcConfiguration": [], "RtcDataChannel": [ "EventTarget" ], "RtcDataChannelEvent": [ "Event" ], "RtcDataChannelEventInit": [], "RtcDataChannelInit": [], "RtcDataChannelState": [], "RtcDataChannelType": [], "RtcDegradationPreference": [], "RtcFecParameters": [], "RtcIceCandidate": [], "RtcIceCandidateInit": [], "RtcIceCandidatePairStats": [], "RtcIceCandidateStats": [], "RtcIceComponentStats": [], "RtcIceConnectionState": [], "RtcIceCredentialType": [], "RtcIceGatheringState": [], "RtcIceServer": [], "RtcIceTransportPolicy": [], "RtcIdentityAssertion": [], "RtcIdentityAssertionResult": [], "RtcIdentityProvider": [], "RtcIdentityProviderDetails": [], "RtcIdentityProviderOptions": [], "RtcIdentityProviderRegistrar": [], "RtcIdentityValidationResult": [], "RtcInboundRtpStreamStats": [], "RtcLifecycleEvent": [], "RtcMediaStreamStats": [], "RtcMediaStreamTrackStats": [], "RtcOfferAnswerOptions": [], "RtcOfferOptions": [], "RtcOutboundRtpStreamStats": [], "RtcPeerConnection": [ "EventTarget" ], "RtcPeerConnectionIceEvent": [ "Event" ], "RtcPeerConnectionIceEventInit": [], "RtcPriorityType": [], "RtcRtcpParameters": [], "RtcRtpCodecParameters": [], "RtcRtpContributingSource": [], "RtcRtpEncodingParameters": [], "RtcRtpHeaderExtensionParameters": [], "RtcRtpParameters": [], "RtcRtpReceiver": [], "RtcRtpSender": [], "RtcRtpSourceEntry": [], "RtcRtpSourceEntryType": [], "RtcRtpSynchronizationSource": [], "RtcRtpTransceiver": [], "RtcRtpTransceiverDirection": [], "RtcRtpTransceiverInit": [], "RtcRtxParameters": [], "RtcSdpType": [], "RtcSessionDescription": [], "RtcSessionDescriptionInit": [], "RtcSignalingState": [], "RtcStats": [], "RtcStatsIceCandidatePairState": [], "RtcStatsIceCandidateType": [], "RtcStatsReport": [], "RtcStatsReportInternal": [], "RtcStatsType": [], "RtcTrackEvent": [ "Event" ], "RtcTrackEventInit": [], "RtcTransportStats": [], "RtcdtmfSender": [ "EventTarget" ], "RtcdtmfToneChangeEvent": [ "Event" ], "RtcdtmfToneChangeEventInit": [], "RtcrtpContributingSourceStats": [], "RtcrtpStreamStats": [], "Screen": [ "EventTarget" ], "ScreenColorGamut": [], "ScreenLuminance": [], "ScreenOrientation": [ "EventTarget" ], "ScriptProcessorNode": [ "AudioNode", "EventTarget" ], "ScrollAreaEvent": [ "Event", "UiEvent" ], "ScrollBehavior": [], "ScrollBoxObject": [], "ScrollIntoViewOptions": [], "ScrollLogicalPosition": [], "ScrollOptions": [], "ScrollRestoration": [], "ScrollSetting": [], "ScrollState": [], "ScrollToOptions": [], "ScrollViewChangeEventInit": [], "SecurityPolicyViolationEvent": [ "Event" ], "SecurityPolicyViolationEventDisposition": [], "SecurityPolicyViolationEventInit": [], "Selection": [], "ServerSocketOptions": [], "ServiceWorker": [ "EventTarget" ], "ServiceWorkerContainer": [ "EventTarget" ], "ServiceWorkerGlobalScope": [ "EventTarget", "WorkerGlobalScope" ], "ServiceWorkerRegistration": [ "EventTarget" ], "ServiceWorkerState": [], "ServiceWorkerUpdateViaCache": [], "ShadowRoot": [ "DocumentFragment", "EventTarget", "Node" ], "ShadowRootInit": [], "ShadowRootMode": [], "ShareData": [], "SharedWorker": [ "EventTarget" ], "SharedWorkerGlobalScope": [ "EventTarget", "WorkerGlobalScope" ], "SignResponse": [], "SocketElement": [], "SocketOptions": [], "SocketReadyState": [], "SocketsDict": [], "SourceBuffer": [ "EventTarget" ], "SourceBufferAppendMode": [], "SourceBufferList": [ "EventTarget" ], "SpeechGrammar": [], "SpeechGrammarList": [], "SpeechRecognition": [ "EventTarget" ], "SpeechRecognitionAlternative": [], "SpeechRecognitionError": [ "Event" ], "SpeechRecognitionErrorCode": [], "SpeechRecognitionErrorInit": [], "SpeechRecognitionEvent": [ "Event" ], "SpeechRecognitionEventInit": [], "SpeechRecognitionResult": [], "SpeechRecognitionResultList": [], "SpeechSynthesis": [ "EventTarget" ], "SpeechSynthesisErrorCode": [], "SpeechSynthesisErrorEvent": [ "Event", "SpeechSynthesisEvent" ], "SpeechSynthesisErrorEventInit": [], "SpeechSynthesisEvent": [ "Event" ], "SpeechSynthesisEventInit": [], "SpeechSynthesisUtterance": [ "EventTarget" ], "SpeechSynthesisVoice": [], "StereoPannerNode": [ "AudioNode", "EventTarget" ], "StereoPannerOptions": [], "Storage": [], "StorageEstimate": [], "StorageEvent": [ "Event" ], "StorageEventInit": [], "StorageManager": [], "StorageType": [], "StreamPipeOptions": [], "StyleRuleChangeEventInit": [], "StyleSheet": [], "StyleSheetApplicableStateChangeEventInit": [], "StyleSheetChangeEventInit": [], "StyleSheetList": [], "SubtleCrypto": [], "SupportedType": [], "SvcOutputMetadata": [], "SvgAngle": [], "SvgAnimateElement": [ "Element", "EventTarget", "Node", "SvgAnimationElement", "SvgElement" ], "SvgAnimateMotionElement": [ "Element", "EventTarget", "Node", "SvgAnimationElement", "SvgElement" ], "SvgAnimateTransformElement": [ "Element", "EventTarget", "Node", "SvgAnimationElement", "SvgElement" ], "SvgAnimatedAngle": [], "SvgAnimatedBoolean": [], "SvgAnimatedEnumeration": [], "SvgAnimatedInteger": [], "SvgAnimatedLength": [], "SvgAnimatedLengthList": [], "SvgAnimatedNumber": [], "SvgAnimatedNumberList": [], "SvgAnimatedPreserveAspectRatio": [], "SvgAnimatedRect": [], "SvgAnimatedString": [], "SvgAnimatedTransformList": [], "SvgAnimationElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgBoundingBoxOptions": [], "SvgCircleElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGeometryElement", "SvgGraphicsElement" ], "SvgClipPathElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgComponentTransferFunctionElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgDefsElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGraphicsElement" ], "SvgDescElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgElement": [ "Element", "EventTarget", "Node" ], "SvgEllipseElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGeometryElement", "SvgGraphicsElement" ], "SvgFilterElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgForeignObjectElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGraphicsElement" ], "SvgGeometryElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGraphicsElement" ], "SvgGradientElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgGraphicsElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgImageElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGraphicsElement" ], "SvgLength": [], "SvgLengthList": [], "SvgLineElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGeometryElement", "SvgGraphicsElement" ], "SvgLinearGradientElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGradientElement" ], "SvgMarkerElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgMaskElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgMatrix": [], "SvgMetadataElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgNumber": [], "SvgNumberList": [], "SvgPathElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGeometryElement", "SvgGraphicsElement" ], "SvgPathSeg": [], "SvgPathSegArcAbs": [ "SvgPathSeg" ], "SvgPathSegArcRel": [ "SvgPathSeg" ], "SvgPathSegClosePath": [ "SvgPathSeg" ], "SvgPathSegCurvetoCubicAbs": [ "SvgPathSeg" ], "SvgPathSegCurvetoCubicRel": [ "SvgPathSeg" ], "SvgPathSegCurvetoCubicSmoothAbs": [ "SvgPathSeg" ], "SvgPathSegCurvetoCubicSmoothRel": [ "SvgPathSeg" ], "SvgPathSegCurvetoQuadraticAbs": [ "SvgPathSeg" ], "SvgPathSegCurvetoQuadraticRel": [ "SvgPathSeg" ], "SvgPathSegCurvetoQuadraticSmoothAbs": [ "SvgPathSeg" ], "SvgPathSegCurvetoQuadraticSmoothRel": [ "SvgPathSeg" ], "SvgPathSegLinetoAbs": [ "SvgPathSeg" ], "SvgPathSegLinetoHorizontalAbs": [ "SvgPathSeg" ], "SvgPathSegLinetoHorizontalRel": [ "SvgPathSeg" ], "SvgPathSegLinetoRel": [ "SvgPathSeg" ], "SvgPathSegLinetoVerticalAbs": [ "SvgPathSeg" ], "SvgPathSegLinetoVerticalRel": [ "SvgPathSeg" ], "SvgPathSegList": [], "SvgPathSegMovetoAbs": [ "SvgPathSeg" ], "SvgPathSegMovetoRel": [ "SvgPathSeg" ], "SvgPatternElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgPoint": [], "SvgPointList": [], "SvgPolygonElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGeometryElement", "SvgGraphicsElement" ], "SvgPolylineElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGeometryElement", "SvgGraphicsElement" ], "SvgPreserveAspectRatio": [], "SvgRadialGradientElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGradientElement" ], "SvgRect": [], "SvgRectElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGeometryElement", "SvgGraphicsElement" ], "SvgScriptElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgSetElement": [ "Element", "EventTarget", "Node", "SvgAnimationElement", "SvgElement" ], "SvgStopElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgStringList": [], "SvgStyleElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgSwitchElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGraphicsElement" ], "SvgSymbolElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgTextContentElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGraphicsElement" ], "SvgTextElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGraphicsElement", "SvgTextContentElement", "SvgTextPositioningElement" ], "SvgTextPathElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGraphicsElement", "SvgTextContentElement" ], "SvgTextPositioningElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGraphicsElement", "SvgTextContentElement" ], "SvgTitleElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgTransform": [], "SvgTransformList": [], "SvgUnitTypes": [], "SvgUseElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGraphicsElement" ], "SvgViewElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgZoomAndPan": [], "SvgaElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGraphicsElement" ], "SvgfeBlendElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeColorMatrixElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeComponentTransferElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeCompositeElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeConvolveMatrixElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeDiffuseLightingElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeDisplacementMapElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeDistantLightElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeDropShadowElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeFloodElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeFuncAElement": [ "Element", "EventTarget", "Node", "SvgComponentTransferFunctionElement", "SvgElement" ], "SvgfeFuncBElement": [ "Element", "EventTarget", "Node", "SvgComponentTransferFunctionElement", "SvgElement" ], "SvgfeFuncGElement": [ "Element", "EventTarget", "Node", "SvgComponentTransferFunctionElement", "SvgElement" ], "SvgfeFuncRElement": [ "Element", "EventTarget", "Node", "SvgComponentTransferFunctionElement", "SvgElement" ], "SvgfeGaussianBlurElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeImageElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeMergeElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeMergeNodeElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeMorphologyElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeOffsetElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfePointLightElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeSpecularLightingElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeSpotLightElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeTileElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgfeTurbulenceElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvggElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGraphicsElement" ], "SvgmPathElement": [ "Element", "EventTarget", "Node", "SvgElement" ], "SvgsvgElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGraphicsElement" ], "SvgtSpanElement": [ "Element", "EventTarget", "Node", "SvgElement", "SvgGraphicsElement", "SvgTextContentElement", "SvgTextPositioningElement" ], "TcpReadyState": [], "TcpServerSocket": [ "EventTarget" ], "TcpServerSocketEvent": [ "Event" ], "TcpServerSocketEventInit": [], "TcpSocket": [ "EventTarget" ], "TcpSocketBinaryType": [], "TcpSocketErrorEvent": [ "Event" ], "TcpSocketErrorEventInit": [], "TcpSocketEvent": [ "Event" ], "TcpSocketEventInit": [], "Text": [ "CharacterData", "EventTarget", "Node" ], "TextDecodeOptions": [], "TextDecoder": [], "TextDecoderOptions": [], "TextEncoder": [], "TextMetrics": [], "TextTrack": [ "EventTarget" ], "TextTrackCue": [ "EventTarget" ], "TextTrackCueList": [], "TextTrackKind": [], "TextTrackList": [ "EventTarget" ], "TextTrackMode": [], "TimeEvent": [ "Event" ], "TimeRanges": [], "Touch": [], "TouchEvent": [ "Event", "UiEvent" ], "TouchEventInit": [], "TouchInit": [], "TouchList": [], "TrackEvent": [ "Event" ], "TrackEventInit": [], "TransformStream": [], "TransitionEvent": [ "Event" ], "TransitionEventInit": [], "Transport": [], "TreeBoxObject": [], "TreeCellInfo": [], "TreeView": [], "TreeWalker": [], "U2f": [], "U2fClientData": [], "UdpMessageEventInit": [], "UdpOptions": [], "UiEvent": [ "Event" ], "UiEventInit": [], "Url": [], "UrlSearchParams": [], "Usb": [ "EventTarget" ], "UsbAlternateInterface": [], "UsbConfiguration": [], "UsbConnectionEvent": [ "Event" ], "UsbConnectionEventInit": [], "UsbControlTransferParameters": [], "UsbDevice": [], "UsbDeviceFilter": [], "UsbDeviceRequestOptions": [], "UsbDirection": [], "UsbEndpoint": [], "UsbEndpointType": [], "UsbInTransferResult": [], "UsbInterface": [], "UsbIsochronousInTransferPacket": [], "UsbIsochronousInTransferResult": [], "UsbIsochronousOutTransferPacket": [], "UsbIsochronousOutTransferResult": [], "UsbOutTransferResult": [], "UsbPermissionDescriptor": [], "UsbPermissionResult": [ "EventTarget", "PermissionStatus" ], "UsbPermissionStorage": [], "UsbRecipient": [], "UsbRequestType": [], "UsbTransferStatus": [], "UserProximityEvent": [ "Event" ], "UserProximityEventInit": [], "UserVerificationRequirement": [], "ValidityState": [], "ValueEvent": [ "Event" ], "ValueEventInit": [], "VideoColorPrimaries": [], "VideoColorSpace": [], "VideoColorSpaceInit": [], "VideoConfiguration": [], "VideoDecoder": [], "VideoDecoderConfig": [], "VideoDecoderInit": [], "VideoDecoderSupport": [], "VideoEncoder": [], "VideoEncoderConfig": [], "VideoEncoderEncodeOptions": [], "VideoEncoderInit": [], "VideoEncoderSupport": [], "VideoFacingModeEnum": [], "VideoFrame": [], "VideoFrameBufferInit": [], "VideoFrameCopyToOptions": [], "VideoFrameInit": [], "VideoMatrixCoefficients": [], "VideoPixelFormat": [], "VideoPlaybackQuality": [], "VideoStreamTrack": [ "EventTarget", "MediaStreamTrack" ], "VideoTrack": [], "VideoTrackList": [ "EventTarget" ], "VideoTransferCharacteristics": [], "VisibilityState": [], "VoidCallback": [], "VrDisplay": [ "EventTarget" ], "VrDisplayCapabilities": [], "VrEye": [], "VrEyeParameters": [], "VrFieldOfView": [], "VrFrameData": [], "VrLayer": [], "VrMockController": [], "VrMockDisplay": [], "VrPose": [], "VrServiceTest": [], "VrStageParameters": [], "VrSubmitFrameResult": [], "VttCue": [ "EventTarget", "TextTrackCue" ], "VttRegion": [], "WakeLock": [], "WakeLockSentinel": [ "EventTarget" ], "WakeLockType": [], "WatchAdvertisementsOptions": [], "WaveShaperNode": [ "AudioNode", "EventTarget" ], "WaveShaperOptions": [], "WebGl2RenderingContext": [], "WebGlActiveInfo": [], "WebGlBuffer": [], "WebGlContextAttributes": [], "WebGlContextEvent": [ "Event" ], "WebGlContextEventInit": [], "WebGlFramebuffer": [], "WebGlPowerPreference": [], "WebGlProgram": [], "WebGlQuery": [], "WebGlRenderbuffer": [], "WebGlRenderingContext": [], "WebGlSampler": [], "WebGlShader": [], "WebGlShaderPrecisionFormat": [], "WebGlSync": [], "WebGlTexture": [], "WebGlTransformFeedback": [], "WebGlUniformLocation": [], "WebGlVertexArrayObject": [], "WebKitCssMatrix": [ "DomMatrix", "DomMatrixReadOnly" ], "WebSocket": [ "EventTarget" ], "WebSocketDict": [], "WebSocketElement": [], "WebglColorBufferFloat": [], "WebglCompressedTextureAstc": [], "WebglCompressedTextureAtc": [], "WebglCompressedTextureEtc": [], "WebglCompressedTextureEtc1": [], "WebglCompressedTexturePvrtc": [], "WebglCompressedTextureS3tc": [], "WebglCompressedTextureS3tcSrgb": [], "WebglDebugRendererInfo": [], "WebglDebugShaders": [], "WebglDepthTexture": [], "WebglDrawBuffers": [], "WebglLoseContext": [], "WebrtcGlobalStatisticsReport": [], "WheelEvent": [ "Event", "MouseEvent", "UiEvent" ], "WheelEventInit": [], "WidevineCdmManifest": [], "Window": [ "EventTarget" ], "WindowClient": [ "Client" ], "Worker": [ "EventTarget" ], "WorkerDebuggerGlobalScope": [ "EventTarget" ], "WorkerGlobalScope": [ "EventTarget" ], "WorkerLocation": [], "WorkerNavigator": [], "WorkerOptions": [], "WorkerType": [], "Worklet": [], "WorkletGlobalScope": [], "WorkletOptions": [], "WritableStream": [], "WritableStreamDefaultWriter": [], "XPathExpression": [], "XPathNsResolver": [], "XPathResult": [], "XmlDocument": [ "Document", "EventTarget", "Node" ], "XmlHttpRequest": [ "EventTarget", "XmlHttpRequestEventTarget" ], "XmlHttpRequestEventTarget": [ "EventTarget" ], "XmlHttpRequestResponseType": [], "XmlHttpRequestUpload": [ "EventTarget", "XmlHttpRequestEventTarget" ], "XmlSerializer": [], "Xr": [ "EventTarget" ], "XrBoundedReferenceSpace": [ "EventTarget", "XrReferenceSpace", "XrSpace" ], "XrEye": [], "XrFrame": [], "XrHandedness": [], "XrInputSource": [], "XrInputSourceArray": [], "XrInputSourceEvent": [ "Event" ], "XrInputSourceEventInit": [], "XrInputSourcesChangeEvent": [ "Event" ], "XrInputSourcesChangeEventInit": [], "XrPose": [], "XrReferenceSpace": [ "EventTarget", "XrSpace" ], "XrReferenceSpaceEvent": [ "Event" ], "XrReferenceSpaceEventInit": [], "XrReferenceSpaceType": [], "XrRenderState": [], "XrRenderStateInit": [], "XrRigidTransform": [], "XrSession": [ "EventTarget" ], "XrSessionEvent": [ "Event" ], "XrSessionEventInit": [], "XrSessionInit": [], "XrSessionMode": [], "XrSpace": [ "EventTarget" ], "XrTargetRayMode": [], "XrView": [], "XrViewerPose": [ "XrPose" ], "XrViewport": [], "XrVisibilityState": [], "XrWebGlLayer": [], "XrWebGlLayerInit": [], "XsltProcessor": [], "console": [], "css": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/web-sys-0.3.57/Cargo.toml", "metadata": { "docs": { "rs": { "all-features": true, "rustdoc-args": [ "--cfg=web_sys_unstable_apis" ] } } }, "publish": null, "authors": [ "The wasm-bindgen Developers" ], "categories": [], "keywords": [], "readme": "./README.md", "repository": "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/web-sys", "homepage": "https://rustwasm.github.io/wasm-bindgen/web-sys/index.html", "documentation": "https://rustwasm.github.io/wasm-bindgen/api/web_sys/", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "webpki", "version": "0.21.4", "id": "webpki 0.21.4 (registry+https://github.com/rust-lang/crates.io-index)", "license": null, "license_file": "LICENSE", "description": "Web PKI X.509 Certificate Verification.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "ring", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.16.19", "kind": null, "rename": null, "optional": false, "uses_default_features": false, "features": [ "alloc" ], "target": null, "registry": null }, { "name": "untrusted", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.7.1", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null }, { "name": "base64", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.9.1", "kind": "dev", "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "webpki", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/webpki-0.21.4/src/webpki.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "integration", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/webpki-0.21.4/tests/integration.rs", "edition": "2018", "doc": false, "doctest": false, "test": true }, { "kind": [ "test" ], "crate_types": [ "bin" ], "name": "dns_name_tests", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/webpki-0.21.4/tests/dns_name_tests.rs", "edition": "2018", "doc": false, "doctest": false, "test": true } ], "features": { "default": [ "std", "trust_anchor_util" ], "std": [], "trust_anchor_util": [ "std" ] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/webpki-0.21.4/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Brian Smith " ], "categories": [ "cryptography", "no-std" ], "keywords": [], "readme": "README.md", "repository": "https://github.com/briansmith/webpki", "homepage": null, "documentation": "https://briansmith.org/rustdoc/webpki/", "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "webpki-roots", "version": "0.21.1", "id": "webpki-roots 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MPL-2.0", "license_file": null, "description": "Mozilla's CA root certificates for use with webpki", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "webpki", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.21.0", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "webpki-roots", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/webpki-roots-0.21.1/src/lib.rs", "edition": "2018", "doc": true, "doctest": true, "test": true }, { "kind": [ "bin" ], "crate_types": [ "bin" ], "name": "process_cert", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/webpki-roots-0.21.1/src/bin/process_cert.rs", "edition": "2018", "doc": true, "doctest": false, "test": true } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/webpki-roots-0.21.1/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Joseph Birr-Pixton " ], "categories": [], "keywords": [], "readme": "README.md", "repository": "https://github.com/ctz/webpki-roots", "homepage": "https://github.com/ctz/webpki-roots", "documentation": null, "edition": "2018", "links": null, "default_run": null, "rust_version": null }, { "name": "winapi", "version": "0.3.9", "id": "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "Raw FFI bindings for all of Windows API.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [ { "name": "winapi-i686-pc-windows-gnu", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "i686-pc-windows-gnu", "registry": null }, { "name": "winapi-x86_64-pc-windows-gnu", "source": "registry+https://github.com/rust-lang/crates.io-index", "req": "^0.4", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": "x86_64-pc-windows-gnu", "registry": null } ], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "winapi", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/winapi-0.3.9/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/winapi-0.3.9/build.rs", "edition": "2015", "doc": false, "doctest": false, "test": false } ], "features": { "accctrl": [], "aclapi": [], "activation": [], "adhoc": [], "appmgmt": [], "audioclient": [], "audiosessiontypes": [], "avrt": [], "basetsd": [], "bcrypt": [], "bits": [], "bits10_1": [], "bits1_5": [], "bits2_0": [], "bits2_5": [], "bits3_0": [], "bits4_0": [], "bits5_0": [], "bitscfg": [], "bitsmsg": [], "bluetoothapis": [], "bluetoothleapis": [], "bthdef": [], "bthioctl": [], "bthledef": [], "bthsdpdef": [], "bugcodes": [], "cderr": [], "cfg": [], "cfgmgr32": [], "cguid": [], "combaseapi": [], "coml2api": [], "commapi": [], "commctrl": [], "commdlg": [], "commoncontrols": [], "consoleapi": [], "corecrt": [], "corsym": [], "d2d1": [], "d2d1_1": [], "d2d1_2": [], "d2d1_3": [], "d2d1effectauthor": [], "d2d1effects": [], "d2d1effects_1": [], "d2d1effects_2": [], "d2d1svg": [], "d2dbasetypes": [], "d3d": [], "d3d10": [], "d3d10_1": [], "d3d10_1shader": [], "d3d10effect": [], "d3d10misc": [], "d3d10sdklayers": [], "d3d10shader": [], "d3d11": [], "d3d11_1": [], "d3d11_2": [], "d3d11_3": [], "d3d11_4": [], "d3d11on12": [], "d3d11sdklayers": [], "d3d11shader": [], "d3d11tokenizedprogramformat": [], "d3d12": [], "d3d12sdklayers": [], "d3d12shader": [], "d3d9": [], "d3d9caps": [], "d3d9types": [], "d3dcommon": [], "d3dcompiler": [], "d3dcsx": [], "d3dkmdt": [], "d3dkmthk": [], "d3dukmdt": [], "d3dx10core": [], "d3dx10math": [], "d3dx10mesh": [], "datetimeapi": [], "davclnt": [], "dbghelp": [], "dbt": [], "dcommon": [], "dcomp": [], "dcompanimation": [], "dcomptypes": [], "dde": [], "ddraw": [], "ddrawi": [], "ddrawint": [], "debug": [ "impl-debug" ], "debugapi": [], "devguid": [], "devicetopology": [], "devpkey": [], "devpropdef": [], "dinput": [], "dinputd": [], "dispex": [], "dmksctl": [], "dmusicc": [], "docobj": [], "documenttarget": [], "dot1x": [], "dpa_dsa": [], "dpapi": [], "dsgetdc": [], "dsound": [], "dsrole": [], "dvp": [], "dwmapi": [], "dwrite": [], "dwrite_1": [], "dwrite_2": [], "dwrite_3": [], "dxdiag": [], "dxfile": [], "dxgi": [], "dxgi1_2": [], "dxgi1_3": [], "dxgi1_4": [], "dxgi1_5": [], "dxgi1_6": [], "dxgidebug": [], "dxgiformat": [], "dxgitype": [], "dxva2api": [], "dxvahd": [], "eaptypes": [], "enclaveapi": [], "endpointvolume": [], "errhandlingapi": [], "everything": [], "evntcons": [], "evntprov": [], "evntrace": [], "excpt": [], "exdisp": [], "fibersapi": [], "fileapi": [], "functiondiscoverykeys_devpkey": [], "gl-gl": [], "guiddef": [], "handleapi": [], "heapapi": [], "hidclass": [], "hidpi": [], "hidsdi": [], "hidusage": [], "highlevelmonitorconfigurationapi": [], "hstring": [], "http": [], "ifdef": [], "ifmib": [], "imm": [], "impl-debug": [], "impl-default": [], "in6addr": [], "inaddr": [], "inspectable": [], "interlockedapi": [], "intsafe": [], "ioapiset": [], "ipexport": [], "iphlpapi": [], "ipifcons": [], "ipmib": [], "iprtrmib": [], "iptypes": [], "jobapi": [], "jobapi2": [], "knownfolders": [], "ks": [], "ksmedia": [], "ktmtypes": [], "ktmw32": [], "l2cmn": [], "libloaderapi": [], "limits": [], "lmaccess": [], "lmalert": [], "lmapibuf": [], "lmat": [], "lmcons": [], "lmdfs": [], "lmerrlog": [], "lmjoin": [], "lmmsg": [], "lmremutl": [], "lmrepl": [], "lmserver": [], "lmshare": [], "lmstats": [], "lmsvc": [], "lmuse": [], "lmwksta": [], "lowlevelmonitorconfigurationapi": [], "lsalookup": [], "memoryapi": [], "minschannel": [], "minwinbase": [], "minwindef": [], "mmdeviceapi": [], "mmeapi": [], "mmreg": [], "mmsystem": [], "mprapidef": [], "msaatext": [], "mscat": [], "mschapp": [], "mssip": [], "mstcpip": [], "mswsock": [], "mswsockdef": [], "namedpipeapi": [], "namespaceapi": [], "nb30": [], "ncrypt": [], "netioapi": [], "nldef": [], "ntddndis": [], "ntddscsi": [], "ntddser": [], "ntdef": [], "ntlsa": [], "ntsecapi": [], "ntstatus": [], "oaidl": [], "objbase": [], "objidl": [], "objidlbase": [], "ocidl": [], "ole2": [], "oleauto": [], "olectl": [], "oleidl": [], "opmapi": [], "pdh": [], "perflib": [], "physicalmonitorenumerationapi": [], "playsoundapi": [], "portabledevice": [], "portabledeviceapi": [], "portabledevicetypes": [], "powerbase": [], "powersetting": [], "powrprof": [], "processenv": [], "processsnapshot": [], "processthreadsapi": [], "processtopologyapi": [], "profileapi": [], "propidl": [], "propkey": [], "propkeydef": [], "propsys": [], "prsht": [], "psapi": [], "qos": [], "realtimeapiset": [], "reason": [], "restartmanager": [], "restrictederrorinfo": [], "rmxfguid": [], "roapi": [], "robuffer": [], "roerrorapi": [], "rpc": [], "rpcdce": [], "rpcndr": [], "rtinfo": [], "sapi": [], "sapi51": [], "sapi53": [], "sapiddk": [], "sapiddk51": [], "schannel": [], "sddl": [], "securityappcontainer": [], "securitybaseapi": [], "servprov": [], "setupapi": [], "shellapi": [], "shellscalingapi": [], "shlobj": [], "shobjidl": [], "shobjidl_core": [], "shtypes": [], "softpub": [], "spapidef": [], "spellcheck": [], "sporder": [], "sql": [], "sqlext": [], "sqltypes": [], "sqlucode": [], "sspi": [], "std": [], "stralign": [], "stringapiset": [], "strmif": [], "subauth": [], "synchapi": [], "sysinfoapi": [], "systemtopologyapi": [], "taskschd": [], "tcpestats": [], "tcpmib": [], "textstor": [], "threadpoolapiset": [], "threadpoollegacyapiset": [], "timeapi": [], "timezoneapi": [], "tlhelp32": [], "transportsettingcommon": [], "tvout": [], "udpmib": [], "unknwnbase": [], "urlhist": [], "urlmon": [], "usb": [], "usbioctl": [], "usbiodef": [], "usbscan": [], "usbspec": [], "userenv": [], "usp10": [], "utilapiset": [], "uxtheme": [], "vadefs": [], "vcruntime": [], "vsbackup": [], "vss": [], "vsserror": [], "vswriter": [], "wbemads": [], "wbemcli": [], "wbemdisp": [], "wbemprov": [], "wbemtran": [], "wct": [], "werapi": [], "winbase": [], "wincodec": [], "wincodecsdk": [], "wincon": [], "wincontypes": [], "wincred": [], "wincrypt": [], "windef": [], "windot11": [], "windowsceip": [], "windowsx": [], "winefs": [], "winerror": [], "winevt": [], "wingdi": [], "winhttp": [], "wininet": [], "winineti": [], "winioctl": [], "winnetwk": [], "winnls": [], "winnt": [], "winreg": [], "winsafer": [], "winscard": [], "winsmcrd": [], "winsock2": [], "winspool": [], "winstring": [], "winsvc": [], "wintrust": [], "winusb": [], "winusbio": [], "winuser": [], "winver": [], "wlanapi": [], "wlanihv": [], "wlanihvtypes": [], "wlantypes": [], "wlclient": [], "wmistr": [], "wnnc": [], "wow64apiset": [], "wpdmtpextensions": [], "ws2bth": [], "ws2def": [], "ws2ipdef": [], "ws2spi": [], "ws2tcpip": [], "wtsapi32": [], "wtypes": [], "wtypesbase": [], "xinput": [] }, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/winapi-0.3.9/Cargo.toml", "metadata": { "docs": { "rs": { "default-target": "x86_64-pc-windows-msvc", "features": [ "everything", "impl-debug", "impl-default" ], "targets": [ "aarch64-pc-windows-msvc", "i686-pc-windows-msvc", "x86_64-pc-windows-msvc" ] } } }, "publish": null, "authors": [ "Peter Atashian " ], "categories": [ "external-ffi-bindings", "no-std", "os::windows-apis" ], "keywords": [ "windows", "ffi", "win32", "com", "directx" ], "readme": "README.md", "repository": "https://github.com/retep998/winapi-rs", "homepage": null, "documentation": "https://docs.rs/winapi/", "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "winapi-i686-pc-windows-gnu", "version": "0.4.0", "id": "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "Import libraries for the i686-pc-windows-gnu target. Please don't use this crate directly, depend on winapi instead.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "winapi-i686-pc-windows-gnu", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/winapi-i686-pc-windows-gnu-0.4.0/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/winapi-i686-pc-windows-gnu-0.4.0/build.rs", "edition": "2015", "doc": false, "doctest": false, "test": false } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/winapi-i686-pc-windows-gnu-0.4.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Peter Atashian " ], "categories": [], "keywords": [ "windows" ], "readme": null, "repository": "https://github.com/retep998/winapi-rs", "homepage": null, "documentation": null, "edition": "2015", "links": null, "default_run": null, "rust_version": null }, { "name": "winapi-x86_64-pc-windows-gnu", "version": "0.4.0", "id": "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "license": "MIT/Apache-2.0", "license_file": null, "description": "Import libraries for the x86_64-pc-windows-gnu target. Please don't use this crate directly, depend on winapi instead.", "source": "registry+https://github.com/rust-lang/crates.io-index", "dependencies": [], "targets": [ { "kind": [ "lib" ], "crate_types": [ "lib" ], "name": "winapi-x86_64-pc-windows-gnu", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/winapi-x86_64-pc-windows-gnu-0.4.0/src/lib.rs", "edition": "2015", "doc": true, "doctest": true, "test": true }, { "kind": [ "custom-build" ], "crate_types": [ "bin" ], "name": "build-script-build", "src_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/winapi-x86_64-pc-windows-gnu-0.4.0/build.rs", "edition": "2015", "doc": false, "doctest": false, "test": false } ], "features": {}, "manifest_path": "/home/user/.data/cargo/registry/src/github.com-1ecc6299db9ec823/winapi-x86_64-pc-windows-gnu-0.4.0/Cargo.toml", "metadata": null, "publish": null, "authors": [ "Peter Atashian " ], "categories": [], "keywords": [ "windows" ], "readme": null, "repository": "https://github.com/retep998/winapi-rs", "homepage": null, "documentation": null, "edition": "2015", "links": null, "default_run": null, "rust_version": null } ], "workspace_members": [ "b_in_workspace_lib 0.1.0 (path+file:///home/user/problematic/workspace/b_in_workspace_lib)", "c_in_workspace_bin 0.1.0 (path+file:///home/user/problematic/workspace/c_in_workspace_bin)" ], "resolve": { "nodes": [ { "id": "ahash 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "getrandom 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "version_check 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "getrandom", "pkg": "getrandom 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(any(target_os = \"linux\", target_os = \"android\", target_os = \"windows\", target_os = \"macos\", target_os = \"ios\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"dragonfly\", target_os = \"solaris\", target_os = \"illumos\", target_os = \"fuchsia\", target_os = \"redox\", target_os = \"cloudabi\", target_os = \"haiku\", target_os = \"vxworks\", target_os = \"emscripten\", target_os = \"wasi\"))" } ] }, { "name": "once_cell", "pkg": "once_cell 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(not(all(target_arch = \"arm\", target_os = \"none\")))" } ] }, { "name": "version_check", "pkg": "version_check 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": "build", "target": null } ] } ], "features": [ "default", "std" ] }, { "id": "atoi 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "num_traits", "pkg": "num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "autocfg 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "b_in_workspace_lib 0.1.0 (path+file:///home/user/problematic/workspace/b_in_workspace_lib)", "dependencies": [ "sqlx 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "sqlx", "pkg": "sqlx 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "base64 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [ "default", "std" ] }, { "id": "bitflags 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [ "default" ] }, { "id": "block-buffer 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "generic-array 0.14.5 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "generic_array", "pkg": "generic-array 0.14.5 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "bumpalo 3.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [ "default" ] }, { "id": "byteorder 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [ "std" ] }, { "id": "bytes 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [ "default", "std" ] }, { "id": "c_in_workspace_bin 0.1.0 (path+file:///home/user/problematic/workspace/c_in_workspace_bin)", "dependencies": [ "b_in_workspace_lib 0.1.0 (path+file:///home/user/problematic/workspace/b_in_workspace_lib)", "sqlx 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "b_in_workspace_lib", "pkg": "b_in_workspace_lib 0.1.0 (path+file:///home/user/problematic/workspace/b_in_workspace_lib)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "sqlx", "pkg": "sqlx 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "cc 1.0.73 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "cpufeatures 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "libc", "pkg": "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "aarch64-apple-darwin" }, { "kind": null, "target": "aarch64-linux-android" }, { "kind": null, "target": "cfg(all(target_arch = \"aarch64\", target_os = \"linux\"))" } ] } ], "features": [] }, { "id": "crc 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "crc-catalog 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "crc_catalog", "pkg": "crc-catalog 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "crc-catalog 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "crossbeam-queue 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-utils 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "cfg_if", "pkg": "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "crossbeam_utils", "pkg": "crossbeam-utils 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "alloc", "default", "std" ] }, { "id": "crossbeam-utils 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "cfg_if", "pkg": "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "lazy_static", "pkg": "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "lazy_static", "std" ] }, { "id": "digest 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "generic-array 0.14.5 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "generic_array", "pkg": "generic-array 0.14.5 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "alloc", "std" ] }, { "id": "dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "serde 1.0.136 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "serde", "pkg": "serde 1.0.136 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "default", "serde", "use_std" ] }, { "id": "flume 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "futures-core 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "futures-sink 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "pin-project 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", "spin 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "futures_core", "pkg": "futures-core 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "futures_sink", "pkg": "futures-sink 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "pin_project", "pkg": "pin-project 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "spin1", "pkg": "spin 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "async", "futures-core", "futures-sink", "pin-project" ] }, { "id": "form_urlencoded 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "matches 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "matches", "pkg": "matches 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "percent_encoding", "pkg": "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "futures-channel 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "futures-core 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "futures-sink 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "futures_core", "pkg": "futures-core 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "futures_sink", "pkg": "futures-sink 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "alloc", "futures-sink", "sink", "std" ] }, { "id": "futures-core 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [ "alloc", "default", "std" ] }, { "id": "futures-executor 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "futures-core 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "futures-task 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "futures-util 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "futures_core", "pkg": "futures-core 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "futures_task", "pkg": "futures-task 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "futures_util", "pkg": "futures-util 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "default", "std" ] }, { "id": "futures-intrusive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "futures-core 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "lock_api 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "futures_core", "pkg": "futures-core 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "lock_api", "pkg": "lock_api 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "parking_lot", "pkg": "parking_lot 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "alloc", "default", "parking_lot", "std" ] }, { "id": "futures-sink 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "futures-task 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [ "alloc", "std" ] }, { "id": "futures-util 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "futures-core 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "futures-sink 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "futures-task 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "pin-project-lite 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "futures_core", "pkg": "futures-core 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "futures_sink", "pkg": "futures-sink 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "futures_task", "pkg": "futures-task 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "pin_project_lite", "pkg": "pin-project-lite 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "pin_utils", "pkg": "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "slab", "pkg": "slab 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "alloc", "futures-sink", "sink", "slab", "std" ] }, { "id": "generic-array 0.14.5 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "typenum 1.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "version_check 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "typenum", "pkg": "typenum 1.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "version_check", "pkg": "version_check 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": "build", "target": null } ] } ], "features": [] }, { "id": "getrandom 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "wasi 0.10.2+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "cfg_if", "pkg": "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "libc", "pkg": "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(unix)" } ] }, { "name": "wasi", "pkg": "wasi 0.10.2+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(target_os = \"wasi\")" } ] } ], "features": [] }, { "id": "hashbrown 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "ahash 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "ahash", "pkg": "ahash 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "ahash", "default", "inline-more", "raw" ] }, { "id": "hashlink 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "hashbrown 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "hashbrown", "pkg": "hashbrown 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "heck 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "unicode-segmentation 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "unicode_segmentation", "pkg": "unicode-segmentation 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "hermit-abi 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "libc", "pkg": "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "default" ] }, { "id": "hex 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [ "alloc", "default", "std" ] }, { "id": "idna 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "matches 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-bidi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-normalization 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "matches", "pkg": "matches 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "unicode_bidi", "pkg": "unicode-bidi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "unicode_normalization", "pkg": "unicode-normalization 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "indexmap 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "autocfg 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "hashbrown 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "autocfg", "pkg": "autocfg 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": "build", "target": null } ] }, { "name": "hashbrown", "pkg": "hashbrown 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "instant 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "cfg_if", "pkg": "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "itertools 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "either", "pkg": "either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "default", "use_alloc", "use_std" ] }, { "id": "itoa 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "js-sys 0.3.57 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "wasm-bindgen 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "wasm_bindgen", "pkg": "wasm-bindgen 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [ "default", "std" ] }, { "id": "libsqlite3-sys 0.23.2 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "cc 1.0.73 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.25 (registry+https://github.com/rust-lang/crates.io-index)", "vcpkg 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "cc", "pkg": "cc 1.0.73 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": "build", "target": null } ] }, { "name": "pkg_config", "pkg": "pkg-config 0.3.25 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": "build", "target": null } ] }, { "name": "vcpkg", "pkg": "vcpkg 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": "build", "target": null } ] } ], "features": [ "bundled", "bundled_bindings", "cc", "pkg-config", "unlock_notify", "vcpkg" ] }, { "id": "lock_api 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "autocfg 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "autocfg", "pkg": "autocfg 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": "build", "target": null } ] }, { "name": "scopeguard", "pkg": "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "log 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "cfg_if", "pkg": "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "matches 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "memchr 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [ "default", "std" ] }, { "id": "minimal-lexical 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [ "std" ] }, { "id": "mio 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "ntapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "wasi 0.11.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "libc", "pkg": "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(unix)" }, { "kind": null, "target": "cfg(target_os = \"wasi\")" } ] }, { "name": "log", "pkg": "log 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "miow", "pkg": "miow 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(windows)" } ] }, { "name": "ntapi", "pkg": "ntapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(windows)" } ] }, { "name": "wasi", "pkg": "wasi 0.11.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(target_os = \"wasi\")" } ] }, { "name": "winapi", "pkg": "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(windows)" } ] } ], "features": [ "default", "net", "os-ext", "os-poll" ] }, { "id": "miow 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "winapi", "pkg": "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "nom 7.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "memchr 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "minimal-lexical 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "memchr", "pkg": "memchr 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "minimal_lexical", "pkg": "minimal-lexical 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "alloc", "default", "std" ] }, { "id": "ntapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "winapi", "pkg": "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "default", "user" ] }, { "id": "num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "autocfg 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "autocfg", "pkg": "autocfg 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": "build", "target": null } ] } ], "features": [ "default", "std" ] }, { "id": "num_cpus 1.13.1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "hermit-abi 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "hermit_abi", "pkg": "hermit-abi 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(all(any(target_arch = \"x86_64\", target_arch = \"aarch64\"), target_os = \"hermit\"))" } ] }, { "name": "libc", "pkg": "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(not(windows))" } ] } ], "features": [] }, { "id": "once_cell 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [ "alloc", "default", "race", "std" ] }, { "id": "opaque-debug 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "parking_lot 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "instant 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "lock_api 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot_core 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "instant", "pkg": "instant 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "lock_api", "pkg": "lock_api 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "parking_lot_core", "pkg": "parking_lot_core 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "default" ] }, { "id": "parking_lot_core 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "instant 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "cfg_if", "pkg": "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "instant", "pkg": "instant 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "libc", "pkg": "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(unix)" } ] }, { "name": "syscall", "pkg": "redox_syscall 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(target_os = \"redox\")" } ] }, { "name": "smallvec", "pkg": "smallvec 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "winapi", "pkg": "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(windows)" } ] } ], "features": [] }, { "id": "paste 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "pin-project 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "pin-project-internal 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "pin_project_internal", "pkg": "pin-project-internal 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "pin-project-internal 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "proc_macro2", "pkg": "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "quote", "pkg": "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "syn", "pkg": "syn 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "pin-project-lite 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "pkg-config 0.3.25 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "unicode-xid 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "unicode_xid", "pkg": "unicode-xid 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "default", "proc-macro" ] }, { "id": "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "proc_macro2", "pkg": "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "default", "proc-macro" ] }, { "id": "redox_syscall 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "bitflags 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "bitflags", "pkg": "bitflags 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "ring 0.16.20 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "cc 1.0.73 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "untrusted 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "web-sys 0.3.57 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "cc", "pkg": "cc 1.0.73 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": "build", "target": null } ] }, { "name": "libc", "pkg": "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(any(target_os = \"android\", target_os = \"linux\"))" } ] }, { "name": "once_cell", "pkg": "once_cell 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(any(target_os = \"android\", target_os = \"linux\"))" }, { "kind": null, "target": "cfg(any(target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"illumos\", target_os = \"netbsd\", target_os = \"openbsd\", target_os = \"solaris\"))" } ] }, { "name": "spin", "pkg": "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(any(target_arch = \"x86\", target_arch = \"x86_64\", all(any(target_arch = \"aarch64\", target_arch = \"arm\"), any(target_os = \"android\", target_os = \"fuchsia\", target_os = \"linux\"))))" } ] }, { "name": "untrusted", "pkg": "untrusted 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "web_sys", "pkg": "web-sys 0.3.57 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(all(target_arch = \"wasm32\", target_vendor = \"unknown\", target_os = \"unknown\", target_env = \"\"))" } ] }, { "name": "winapi", "pkg": "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(target_os = \"windows\")" } ] } ], "features": [ "alloc", "default", "dev_urandom_fallback", "once_cell" ] }, { "id": "rustls 0.19.1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "base64 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", "ring 0.16.20 (registry+https://github.com/rust-lang/crates.io-index)", "sct 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "webpki 0.21.4 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "base64", "pkg": "base64 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "log", "pkg": "log 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "ring", "pkg": "ring 0.16.20 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "sct", "pkg": "sct 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "webpki", "pkg": "webpki 0.21.4 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "dangerous_configuration", "default", "log", "logging" ] }, { "id": "ryu 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "sct 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "ring 0.16.20 (registry+https://github.com/rust-lang/crates.io-index)", "untrusted 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "ring", "pkg": "ring 0.16.20 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "untrusted", "pkg": "untrusted 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "serde 1.0.136 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "serde_derive 1.0.136 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "serde_derive", "pkg": "serde_derive 1.0.136 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "default", "derive", "rc", "serde_derive", "std" ] }, { "id": "serde_derive 1.0.136 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "proc_macro2", "pkg": "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "quote", "pkg": "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "syn", "pkg": "syn 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "default" ] }, { "id": "serde_json 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "itoa 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "ryu 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.136 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "itoa", "pkg": "itoa 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "ryu", "pkg": "ryu 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "serde", "pkg": "serde 1.0.136 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "default", "std" ] }, { "id": "sha2 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "block-buffer 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "cpufeatures 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "digest 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "opaque-debug 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "block_buffer", "pkg": "block-buffer 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "cfg_if", "pkg": "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "cpufeatures", "pkg": "cpufeatures 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(any(target_arch = \"aarch64\", target_arch = \"x86_64\", target_arch = \"x86\"))" } ] }, { "name": "digest", "pkg": "digest 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "opaque_debug", "pkg": "opaque-debug 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "default", "std" ] }, { "id": "slab 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [ "default", "std" ] }, { "id": "smallvec 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "socket2 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "libc", "pkg": "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(unix)" } ] }, { "name": "winapi", "pkg": "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(windows)" } ] } ], "features": [ "all" ] }, { "id": "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "spin 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "lock_api 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "lock_api_crate", "pkg": "lock_api 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "barrier", "default", "lazy", "lock_api", "lock_api_crate", "mutex", "once", "rwlock", "spin_mutex" ] }, { "id": "sqlformat 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "itertools 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)", "nom 7.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "itertools", "pkg": "itertools 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "nom", "pkg": "nom 7.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "unicode_categories", "pkg": "unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "sqlx 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "sqlx-core 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "sqlx-macros 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "sqlx_core", "pkg": "sqlx-core 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "sqlx_macros", "pkg": "sqlx-macros 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "_rt-tokio", "default", "macros", "migrate", "offline", "runtime-tokio-rustls", "sqlite", "sqlx-macros" ] }, { "id": "sqlx-core 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "ahash 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", "atoi 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "crc 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-queue 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "flume 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)", "futures-channel 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "futures-core 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "futures-executor 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "futures-intrusive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "futures-util 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "hashlink 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "itoa 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "libsqlite3-sys 0.23.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "paste 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustls 0.19.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.136 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "sqlformat 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "sqlx-rt 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "stringprep 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "thiserror 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-stream 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "url 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "webpki 0.21.4 (registry+https://github.com/rust-lang/crates.io-index)", "webpki-roots 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "ahash", "pkg": "ahash 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "atoi", "pkg": "atoi 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "bitflags", "pkg": "bitflags 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "byteorder", "pkg": "byteorder 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "bytes", "pkg": "bytes 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "crc", "pkg": "crc 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "crossbeam_queue", "pkg": "crossbeam-queue 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "either", "pkg": "either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "flume", "pkg": "flume 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "futures_channel", "pkg": "futures-channel 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "futures_core", "pkg": "futures-core 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "futures_executor", "pkg": "futures-executor 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "futures_intrusive", "pkg": "futures-intrusive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "futures_util", "pkg": "futures-util 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "hashlink", "pkg": "hashlink 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "hex", "pkg": "hex 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "indexmap", "pkg": "indexmap 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "itoa", "pkg": "itoa 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "libc", "pkg": "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "libsqlite3_sys", "pkg": "libsqlite3-sys 0.23.2 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "log", "pkg": "log 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "memchr", "pkg": "memchr 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "once_cell", "pkg": "once_cell 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "paste", "pkg": "paste 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "percent_encoding", "pkg": "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "rustls", "pkg": "rustls 0.19.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "serde", "pkg": "serde 1.0.136 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "sha2", "pkg": "sha2 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "smallvec", "pkg": "smallvec 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "sqlformat", "pkg": "sqlformat 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "sqlx_rt", "pkg": "sqlx-rt 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "stringprep", "pkg": "stringprep 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "thiserror", "pkg": "thiserror 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "tokio_stream", "pkg": "tokio-stream 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "url", "pkg": "url 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "webpki", "pkg": "webpki 0.21.4 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "webpki_roots", "pkg": "webpki-roots 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "_rt-tokio", "_tls-rustls", "crc", "flume", "futures-executor", "libsqlite3-sys", "migrate", "offline", "runtime-tokio-rustls", "rustls", "serde", "sha2", "sqlite", "tokio-stream", "webpki", "webpki-roots" ] }, { "id": "sqlx-macros 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "heck 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "once_cell 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.136 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", "sqlx-core 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "sqlx-rt 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "url 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "dotenv", "pkg": "dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "either", "pkg": "either 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "heck", "pkg": "heck 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "hex", "pkg": "hex 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "once_cell", "pkg": "once_cell 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "proc_macro2", "pkg": "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "quote", "pkg": "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "serde", "pkg": "serde 1.0.136 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "serde_json", "pkg": "serde_json 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "sha2", "pkg": "sha2 0.9.9 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "sqlx_core", "pkg": "sqlx-core 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "sqlx_rt", "pkg": "sqlx-rt 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "syn", "pkg": "syn 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "url", "pkg": "url 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "_rt-tokio", "hex", "migrate", "offline", "runtime-tokio-rustls", "serde", "serde_json", "sha2", "sqlite" ] }, { "id": "sqlx-rt 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "once_cell 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 1.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-rustls 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "once_cell", "pkg": "once_cell 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "tokio", "pkg": "tokio 1.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "tokio_rustls", "pkg": "tokio-rustls 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "_rt-tokio", "_tls-rustls", "once_cell", "runtime-tokio-rustls", "tokio", "tokio-rustls" ] }, { "id": "stringprep 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "unicode-bidi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-normalization 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "unicode_bidi", "pkg": "unicode-bidi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "unicode_normalization", "pkg": "unicode-normalization 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "syn 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "proc_macro2", "pkg": "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "quote", "pkg": "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "unicode_xid", "pkg": "unicode-xid 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "clone-impls", "default", "derive", "full", "parsing", "printing", "proc-macro", "quote", "visit", "visit-mut" ] }, { "id": "thiserror 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "thiserror-impl 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "thiserror_impl", "pkg": "thiserror-impl 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "thiserror-impl 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "proc_macro2", "pkg": "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "quote", "pkg": "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "syn", "pkg": "syn 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "tinyvec 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "tinyvec_macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "tinyvec_macros", "pkg": "tinyvec_macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "alloc", "default", "tinyvec_macros" ] }, { "id": "tinyvec_macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "tokio 1.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "bytes 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.13.1 (registry+https://github.com/rust-lang/crates.io-index)", "pin-project-lite 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "socket2 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "bytes", "pkg": "bytes 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "libc", "pkg": "libc 0.2.122 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(unix)" } ] }, { "name": "memchr", "pkg": "memchr 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "mio", "pkg": "mio 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "num_cpus", "pkg": "num_cpus 1.13.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "pin_project_lite", "pkg": "pin-project-lite 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "socket2", "pkg": "socket2 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "winapi", "pkg": "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "cfg(windows)" } ] } ], "features": [ "bytes", "default", "fs", "io-util", "libc", "memchr", "mio", "net", "num_cpus", "rt", "rt-multi-thread", "socket2", "sync", "time", "winapi" ] }, { "id": "tokio-rustls 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "rustls 0.19.1 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 1.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "webpki 0.21.4 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "rustls", "pkg": "rustls 0.19.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "tokio", "pkg": "tokio 1.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "webpki", "pkg": "webpki 0.21.4 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "tokio-stream 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "futures-core 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "pin-project-lite 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 1.17.0 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "futures_core", "pkg": "futures-core 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "pin_project_lite", "pkg": "pin-project-lite 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "tokio", "pkg": "tokio 1.17.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "default", "fs", "time" ] }, { "id": "typenum 1.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "unicode-bidi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [ "default", "std" ] }, { "id": "unicode-normalization 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "tinyvec 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "tinyvec", "pkg": "tinyvec 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "default", "std" ] }, { "id": "unicode-segmentation 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "unicode-xid 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [ "default" ] }, { "id": "unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "untrusted 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "url 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "form_urlencoded 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "idna 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "matches 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "form_urlencoded", "pkg": "form_urlencoded 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "idna", "pkg": "idna 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "matches", "pkg": "matches 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "percent_encoding", "pkg": "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "vcpkg 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "version_check 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "wasi 0.10.2+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [ "default", "std" ] }, { "id": "wasi 0.11.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [ "default", "std" ] }, { "id": "wasm-bindgen 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-macro 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "cfg_if", "pkg": "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "wasm_bindgen_macro", "pkg": "wasm-bindgen-macro 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "default", "spans", "std" ] }, { "id": "wasm-bindgen-backend 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "bumpalo 3.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-shared 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "bumpalo", "pkg": "bumpalo 3.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "lazy_static", "pkg": "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "log", "pkg": "log 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "proc_macro2", "pkg": "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "quote", "pkg": "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "syn", "pkg": "syn 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "wasm_bindgen_shared", "pkg": "wasm-bindgen-shared 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "spans" ] }, { "id": "wasm-bindgen-macro 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-macro-support 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "quote", "pkg": "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "wasm_bindgen_macro_support", "pkg": "wasm-bindgen-macro-support 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "spans" ] }, { "id": "wasm-bindgen-macro-support 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "syn 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-backend 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-shared 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "proc_macro2", "pkg": "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "quote", "pkg": "quote 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "syn", "pkg": "syn 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "wasm_bindgen_backend", "pkg": "wasm-bindgen-backend 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "wasm_bindgen_shared", "pkg": "wasm-bindgen-shared 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "spans" ] }, { "id": "wasm-bindgen-shared 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "web-sys 0.3.57 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "js-sys 0.3.57 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "js_sys", "pkg": "js-sys 0.3.57 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "wasm_bindgen", "pkg": "wasm-bindgen 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "Crypto", "EventTarget", "Window" ] }, { "id": "webpki 0.21.4 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "ring 0.16.20 (registry+https://github.com/rust-lang/crates.io-index)", "untrusted 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "ring", "pkg": "ring 0.16.20 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] }, { "name": "untrusted", "pkg": "untrusted 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [ "default", "std", "trust_anchor_util" ] }, { "id": "webpki-roots 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "webpki 0.21.4 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "webpki", "pkg": "webpki 0.21.4 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": null } ] } ], "features": [] }, { "id": "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" ], "deps": [ { "name": "winapi_i686_pc_windows_gnu", "pkg": "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "i686-pc-windows-gnu" } ] }, { "name": "winapi_x86_64_pc_windows_gnu", "pkg": "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "dep_kinds": [ { "kind": null, "target": "x86_64-pc-windows-gnu" } ] } ], "features": [ "cfg", "errhandlingapi", "evntrace", "fileapi", "handleapi", "in6addr", "inaddr", "ioapiset", "minwinbase", "minwindef", "mswsock", "namedpipeapi", "ntdef", "ntsecapi", "ntstatus", "std", "synchapi", "winbase", "windef", "winerror", "winioctl", "winnt", "winsock2", "ws2def", "ws2ipdef", "ws2tcpip", "wtypesbase" ] }, { "id": "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] }, { "id": "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "dependencies": [], "deps": [], "features": [] } ], "root": "b_in_workspace_lib 0.1.0 (path+file:///home/user/problematic/workspace/b_in_workspace_lib)" }, "target_directory": "/home/user/problematic/workspace/target", "version": 1, "workspace_root": "/home/user/problematic/workspace", "metadata": null } ================================================ FILE: sqlx-cli/tests/common/mod.rs ================================================ use assert_cmd::{assert::Assert, Command}; use sqlx::_unstable::config::Config; use sqlx::{migrate::Migrate, Connection, SqliteConnection}; use std::{ env, fs, path::{Path, PathBuf}, }; pub struct TestDatabase { file_path: PathBuf, migrations_path: PathBuf, pub config_path: Option, } impl TestDatabase { pub fn new(name: &str, migrations: &str) -> Self { // Note: only set when _building_ let temp_dir = option_env!("CARGO_TARGET_TMPDIR").map_or_else(env::temp_dir, PathBuf::from); let test_dir = temp_dir.join("migrate"); fs::create_dir_all(&test_dir) .unwrap_or_else(|e| panic!("error creating directory: {test_dir:?}: {e}")); let file_path = test_dir.join(format!("test-{name}.db")); if file_path.exists() { fs::remove_file(&file_path) .unwrap_or_else(|e| panic!("error deleting test database {file_path:?}: {e}")); } let this = Self { file_path, migrations_path: Path::new("tests").join(migrations), config_path: None, }; Command::cargo_bin("cargo-sqlx") .unwrap() .args([ "sqlx", "database", "create", "--database-url", &this.connection_string(), ]) .assert() .success(); this } pub fn set_migrations(&mut self, migrations: &str) { self.migrations_path = Path::new("tests").join(migrations); } pub fn connection_string(&self) -> String { format!("sqlite://{}", self.file_path.display()) } pub fn run_migration(&self, revert: bool, version: Option, dry_run: bool) -> Assert { let mut command = Command::cargo_bin("sqlx").unwrap(); command .args([ "migrate", match revert { true => "revert", false => "run", }, "--database-url", &self.connection_string(), "--source", ]) .arg(&self.migrations_path); if let Some(config_path) = &self.config_path { command.arg("--config").arg(config_path); } if let Some(version) = version { command.arg("--target-version").arg(version.to_string()); } if dry_run { command.arg("--dry-run"); } command.assert() } pub async fn applied_migrations(&self) -> Vec { let mut conn = SqliteConnection::connect(&self.connection_string()) .await .unwrap(); let config = Config::default(); conn.list_applied_migrations(config.migrate.table_name()) .await .unwrap() .iter() .map(|m| m.version) .collect() } pub fn migrate_info(&self) -> Assert { let mut command = Command::cargo_bin("sqlx").unwrap(); command .args([ "migrate", "info", "--database-url", &self.connection_string(), "--source", ]) .arg(&self.migrations_path); if let Some(config_path) = &self.config_path { command.arg("--config").arg(config_path); } command.assert() } } impl Drop for TestDatabase { fn drop(&mut self) { // Only remove the database if there isn't a failure. if !std::thread::panicking() { fs::remove_file(&self.file_path).unwrap_or_else(|e| { panic!("error deleting test database {:?}: {e}", self.file_path) }); } } } ================================================ FILE: sqlx-cli/tests/ignored-chars/BOM/.gitattributes ================================================ *.sql text eol=lf ================================================ FILE: sqlx-cli/tests/ignored-chars/BOM/1_user.sql ================================================ create table user ( -- integer primary keys are the most efficient in SQLite user_id integer primary key, username text unique not null ); ================================================ FILE: sqlx-cli/tests/ignored-chars/BOM/2_post.sql ================================================ create table post ( post_id integer primary key, user_id integer not null references user (user_id), content text not null, -- Defaults have to be wrapped in parenthesis created_at datetime default (datetime('now')) ); create index post_created_at on post (created_at desc); ================================================ FILE: sqlx-cli/tests/ignored-chars/BOM/3_comment.sql ================================================ create table comment ( comment_id integer primary key, post_id integer not null references post (post_id), user_id integer not null references "user" (user_id), content text not null, created_at datetime default (datetime('now')) ); create index comment_created_at on comment (created_at desc); ================================================ FILE: sqlx-cli/tests/ignored-chars/CRLF/.gitattributes ================================================ *.sql text eol=crlf ================================================ FILE: sqlx-cli/tests/ignored-chars/CRLF/1_user.sql ================================================ create table user ( -- integer primary keys are the most efficient in SQLite user_id integer primary key, username text unique not null ); ================================================ FILE: sqlx-cli/tests/ignored-chars/CRLF/2_post.sql ================================================ create table post ( post_id integer primary key, user_id integer not null references user (user_id), content text not null, -- Defaults have to be wrapped in parenthesis created_at datetime default (datetime('now')) ); create index post_created_at on post (created_at desc); ================================================ FILE: sqlx-cli/tests/ignored-chars/CRLF/3_comment.sql ================================================ create table comment ( comment_id integer primary key, post_id integer not null references post (post_id), user_id integer not null references "user" (user_id), content text not null, created_at datetime default (datetime('now')) ); create index comment_created_at on comment (created_at desc); ================================================ FILE: sqlx-cli/tests/ignored-chars/LF/.gitattributes ================================================ *.sql text eol=lf ================================================ FILE: sqlx-cli/tests/ignored-chars/LF/1_user.sql ================================================ create table user ( -- integer primary keys are the most efficient in SQLite user_id integer primary key, username text unique not null ); ================================================ FILE: sqlx-cli/tests/ignored-chars/LF/2_post.sql ================================================ create table post ( post_id integer primary key, user_id integer not null references user (user_id), content text not null, -- Defaults have to be wrapped in parenthesis created_at datetime default (datetime('now')) ); create index post_created_at on post (created_at desc); ================================================ FILE: sqlx-cli/tests/ignored-chars/LF/3_comment.sql ================================================ create table comment ( comment_id integer primary key, post_id integer not null references post (post_id), user_id integer not null references "user" (user_id), content text not null, created_at datetime default (datetime('now')) ); create index comment_created_at on comment (created_at desc); ================================================ FILE: sqlx-cli/tests/ignored-chars/oops-all-tabs/.gitattributes ================================================ *.sql text eol=lf ================================================ FILE: sqlx-cli/tests/ignored-chars/oops-all-tabs/1_user.sql ================================================ create table user ( -- integer primary keys are the most efficient in SQLite user_id integer primary key, username text unique not null ); ================================================ FILE: sqlx-cli/tests/ignored-chars/oops-all-tabs/2_post.sql ================================================ create table post ( post_id integer primary key, user_id integer not null references user (user_id), content text not null, -- Defaults have to be wrapped in parenthesis created_at datetime default (datetime('now')) ); create index post_created_at on post (created_at desc); ================================================ FILE: sqlx-cli/tests/ignored-chars/oops-all-tabs/3_comment.sql ================================================ create table comment ( comment_id integer primary key, post_id integer not null references post (post_id), user_id integer not null references "user" (user_id), content text not null, created_at datetime default (datetime('now')) ); create index comment_created_at on comment (created_at desc); ================================================ FILE: sqlx-cli/tests/ignored-chars/sqlx.toml ================================================ [migrate] # Ignore common whitespace characters (beware syntactically significant whitespace!) # Space, tab, CR, LF, zero-width non-breaking space (U+FEFF) # # U+FEFF is added by some editors as a magic number at the beginning of a text file indicating it is UTF-8 encoded, # where it is known as a byte-order mark (BOM): https://en.wikipedia.org/wiki/Byte_order_mark ignored-chars = [" ", "\t", "\r", "\n", "\uFEFF"] ================================================ FILE: sqlx-cli/tests/migrate.rs ================================================ mod common; use common::TestDatabase; #[tokio::test] async fn run_reversible_migrations() { let all_migrations: Vec = vec![ 20230101000000, 20230201000000, 20230301000000, 20230401000000, 20230501000000, ]; // Without --target-version specified.k { let db = TestDatabase::new("run_reversible_latest", "migrations_reversible"); db.run_migration(false, None, false).success(); assert_eq!(db.applied_migrations().await, all_migrations); } // With --target-version specified. { let db = TestDatabase::new("run_reversible_latest_explicit", "migrations_reversible"); // Move to latest, explicitly specified. db.run_migration(false, Some(20230501000000), false) .success(); assert_eq!(db.applied_migrations().await, all_migrations); // Move to latest when we're already at the latest. db.run_migration(false, Some(20230501000000), false) .success(); assert_eq!(db.applied_migrations().await, all_migrations); // Upgrade to an old version. db.run_migration(false, Some(20230301000000), false) .failure(); assert_eq!(db.applied_migrations().await, all_migrations); } // With --target-version, incrementally upgrade. { let db = TestDatabase::new("run_reversible_incremental", "migrations_reversible"); // First version db.run_migration(false, Some(20230101000000), false) .success(); assert_eq!(db.applied_migrations().await, vec![20230101000000]); // Dry run upgrade to latest. db.run_migration(false, None, true).success(); assert_eq!(db.applied_migrations().await, vec![20230101000000]); // Dry run upgrade + 2 db.run_migration(false, Some(20230301000000), true) .success(); assert_eq!(db.applied_migrations().await, vec![20230101000000]); // Upgrade to non-existent version. db.run_migration(false, Some(20230901000000999), false) .failure(); assert_eq!(db.applied_migrations().await, vec![20230101000000]); // Upgrade + 1 db.run_migration(false, Some(20230201000000), false) .success(); assert_eq!( db.applied_migrations().await, vec![20230101000000, 20230201000000] ); // Upgrade + 2 db.run_migration(false, Some(20230401000000), false) .success(); assert_eq!(db.applied_migrations().await, all_migrations[..4]); } } #[tokio::test] async fn revert_migrations() { let all_migrations: Vec = vec![ 20230101000000, 20230201000000, 20230301000000, 20230401000000, 20230501000000, ]; // Without --target-version { let db = TestDatabase::new("revert_incremental", "migrations_reversible"); db.run_migration(false, None, false).success(); // Dry-run db.run_migration(true, None, true).success(); assert_eq!(db.applied_migrations().await, all_migrations); // Downgrade one db.run_migration(true, None, false).success(); assert_eq!(db.applied_migrations().await, all_migrations[..4]); // Downgrade one db.run_migration(true, None, false).success(); assert_eq!(db.applied_migrations().await, all_migrations[..3]); } // With --target-version { let db = TestDatabase::new("revert_incremental", "migrations_reversible"); db.run_migration(false, None, false).success(); // Dry-run downgrade to version 3. db.run_migration(true, Some(20230301000000), true).success(); assert_eq!(db.applied_migrations().await, all_migrations); // Downgrade to version 3. db.run_migration(true, Some(20230301000000), false) .success(); assert_eq!(db.applied_migrations().await, all_migrations[..3]); // Try downgrading to the same version. db.run_migration(true, Some(20230301000000), false) .success(); assert_eq!(db.applied_migrations().await, all_migrations[..3]); // Try downgrading to a newer version. db.run_migration(true, Some(20230401000000), false) .failure(); assert_eq!(db.applied_migrations().await, all_migrations[..3]); // Try downgrading to a non-existent version. db.run_migration(true, Some(9999), false).failure(); assert_eq!(db.applied_migrations().await, all_migrations[..3]); // Ensure we can still upgrade db.run_migration(false, Some(20230401000000), false) .success(); assert_eq!(db.applied_migrations().await, all_migrations[..4]); // Downgrade to zero. db.run_migration(true, Some(0), false).success(); assert_eq!(db.applied_migrations().await, Vec::::new()); } } #[tokio::test] async fn ignored_chars() { let mut db = TestDatabase::new("ignored-chars", "ignored-chars/LF"); db.config_path = Some("tests/ignored-chars/sqlx.toml".into()); db.run_migration(false, None, false).success(); db.set_migrations("ignored-chars/CRLF"); let expected_info = "1/installed user\n2/installed post\n3/installed comment\n"; // `ignored-chars` should produce the same migration checksum here db.migrate_info().success().stdout(expected_info); // Running migration should be a no-op db.run_migration(false, None, false).success().stdout(""); db.set_migrations("ignored-chars/BOM"); db.migrate_info().success().stdout(expected_info); db.run_migration(false, None, false).success().stdout(""); db.set_migrations("ignored-chars/oops-all-tabs"); db.migrate_info().success().stdout(expected_info); db.run_migration(false, None, false).success().stdout(""); } ================================================ FILE: sqlx-cli/tests/migrations_reversible/20230101000000_test1.down.sql ================================================ DROP TABLE test1; ================================================ FILE: sqlx-cli/tests/migrations_reversible/20230101000000_test1.up.sql ================================================ CREATE TABLE test1(x INTEGER PRIMARY KEY); ================================================ FILE: sqlx-cli/tests/migrations_reversible/20230201000000_test2.down.sql ================================================ DROP TABLE test2; ================================================ FILE: sqlx-cli/tests/migrations_reversible/20230201000000_test2.up.sql ================================================ CREATE TABLE test2(x INTEGER PRIMARY KEY); ================================================ FILE: sqlx-cli/tests/migrations_reversible/20230301000000_test3.down.sql ================================================ DROP TABLE test3; ================================================ FILE: sqlx-cli/tests/migrations_reversible/20230301000000_test3.up.sql ================================================ CREATE TABLE test3(x INTEGER PRIMARY KEY); ================================================ FILE: sqlx-cli/tests/migrations_reversible/20230401000000_test4.down.sql ================================================ DROP TABLE test4; ================================================ FILE: sqlx-cli/tests/migrations_reversible/20230401000000_test4.up.sql ================================================ CREATE TABLE test4(x INTEGER PRIMARY KEY); ================================================ FILE: sqlx-cli/tests/migrations_reversible/20230501000000_test5.down.sql ================================================ DROP TABLE test5; ================================================ FILE: sqlx-cli/tests/migrations_reversible/20230501000000_test5.up.sql ================================================ CREATE TABLE test5(x INTEGER PRIMARY KEY); ================================================ FILE: sqlx-core/Cargo.toml ================================================ [package] name = "sqlx-core" description = "Core of SQLx, the rust SQL toolkit. Not intended to be used directly." version.workspace = true license.workspace = true edition.workspace = true authors.workspace = true repository.workspace = true rust-version.workspace = true [package.metadata.docs.rs] features = ["offline"] [features] default = [] migrate = ["sha2", "crc"] any = [] json = ["serde", "serde_json"] # for conditional compilation _rt-async-global-executor = ["async-global-executor", "_rt-async-io", "_rt-async-task"] _rt-async-io = ["async-io", "async-fs"] # see note at async-fs declaration _rt-async-std = ["async-std", "_rt-async-io"] _rt-async-task = ["async-task"] _rt-smol = ["smol", "_rt-async-io", "_rt-async-task"] _rt-tokio = ["tokio", "tokio-stream"] _tls-native-tls = ["native-tls"] _tls-rustls-aws-lc-rs = ["_tls-rustls", "rustls/aws-lc-rs", "webpki-roots"] _tls-rustls-ring-webpki = ["_tls-rustls", "rustls/ring", "webpki-roots"] _tls-rustls-ring-native-roots = ["_tls-rustls", "rustls/ring", "rustls-native-certs"] _tls-rustls = ["rustls"] _tls-none = [] # support offline/decoupled building (enables serialization of `Describe`) offline = ["serde", "either/serde"] # Enable parsing of `sqlx.toml`. # For simplicity, the `config` module is always enabled, # but disabling this disables the `serde` derives and the `toml` crate, # which is a good bit less code to compile if the feature isn't being used. sqlx-toml = ["serde", "toml/parse"] _unstable-doc = ["sqlx-toml"] [dependencies] # Runtimes async-global-executor = { workspace = true, optional = true } async-std = { workspace = true, optional = true } smol = { workspace = true, optional = true } tokio = { workspace = true, optional = true } # TLS native-tls = { version = "0.2.10", optional = true } rustls = { version = "0.23.24", default-features = false, features = ["std", "tls12"], optional = true } webpki-roots = { version = "1", optional = true } rustls-native-certs = { version = "0.8.0", optional = true } # Type Integrations bit-vec = { workspace = true, optional = true } bigdecimal = { workspace = true, optional = true } chrono = { workspace = true, optional = true } rust_decimal = { workspace = true, optional = true } time = { workspace = true, optional = true } ipnet = { workspace = true, optional = true } ipnetwork = { workspace = true, optional = true } mac_address = { workspace = true, optional = true } uuid = { workspace = true, optional = true } # work around bug in async-fs 2.0.0, which references futures-lite dependency wrongly, see https://github.com/launchbadge/sqlx/pull/3791#issuecomment-3043363281 async-fs = { version = "2.1", optional = true } async-io = { version = "2.4.1", optional = true } async-task = { version = "4.7.1", optional = true } base64 = { version = "0.22.0", default-features = false, features = ["std"] } bytes = "1.1.0" cfg-if = { workspace = true } crc = { version = "3", optional = true } crossbeam-queue = "0.3.2" either = "1.6.1" futures-core = { version = "0.3.19", default-features = false } futures-io = "0.3.24" futures-intrusive = "0.5.0" futures-util = { version = "0.3.19", default-features = false, features = ["alloc", "sink", "io"] } log = { version = "0.4.18", default-features = false } memchr = { version = "2.4.1", default-features = false } percent-encoding = "2.1.0" serde = { version = "1.0.132", features = ["derive", "rc"], optional = true } serde_json = { version = "1.0.73", features = ["raw_value"], optional = true } toml = { version = "0.8.16", optional = true } sha2 = { version = "0.10.0", default-features = false, optional = true } #sqlformat = "0.2.0" tokio-stream = { version = "0.1.8", features = ["fs"], optional = true } tracing = { version = "0.1.37", features = ["log"] } smallvec = "1.7.0" url = { version = "2.2.2" } bstr = { version = "1.0", default-features = false, features = ["std"], optional = true } hashlink = "0.11.0" indexmap = "2.0" event-listener = "5.2.0" hashbrown = "0.16.0" thiserror.workspace = true [dev-dependencies] tokio = { version = "1", features = ["rt"] } [dev-dependencies.sqlx] # FIXME: https://github.com/rust-lang/cargo/issues/15622 # workspace = true path = ".." default-features = false features = ["postgres", "sqlite", "mysql", "migrate", "macros", "time", "uuid"] [lints] workspace = true ================================================ FILE: sqlx-core/src/acquire.rs ================================================ use crate::database::Database; use crate::error::Error; use crate::pool::{MaybePoolConnection, Pool, PoolConnection}; use crate::transaction::Transaction; use futures_core::future::BoxFuture; use std::ops::{Deref, DerefMut}; /// Acquire connections or transactions from a database in a generic way. /// /// If you want to accept generic database connections that implement /// [`Acquire`] which then allows you to [`acquire`][`Acquire::acquire`] a /// connection or [`begin`][`Acquire::begin`] a transaction, then you can do it /// like that: /// /// ```rust,ignore /// # use sqlx::{Acquire, postgres::Postgres, error::BoxDynError}; /// async fn run_query<'a, A>(conn: A) -> Result<(), BoxDynError> /// where /// A: Acquire<'a, Database = Postgres>, /// { /// let mut conn = conn.acquire().await?; /// /// sqlx::query!("SELECT 1 as v").fetch_one(&mut *conn).await?; /// sqlx::query!("SELECT 2 as v").fetch_one(&mut *conn).await?; /// /// Ok(()) /// } /// ``` /// /// If you run into a lifetime error about "implementation of `sqlx::Acquire` is /// not general enough", the [workaround] looks like this: /// /// ```rust,ignore /// # use std::future::Future; /// # use sqlx::{Acquire, postgres::Postgres, error::BoxDynError}; /// fn run_query<'a, 'c, A>(conn: A) -> impl Future> + Send + 'a /// where /// A: Acquire<'c, Database = Postgres> + Send + 'a, /// { /// async move { /// let mut conn = conn.acquire().await?; /// /// sqlx::query!("SELECT 1 as v").fetch_one(&mut *conn).await?; /// sqlx::query!("SELECT 2 as v").fetch_one(&mut *conn).await?; /// /// Ok(()) /// } /// } /// ``` /// /// However, if you really just want to accept both, a transaction or a /// connection as an argument to a function, then it's easier to just accept a /// mutable reference to a database connection like so: /// /// ```rust,ignore /// # use sqlx::{postgres::PgConnection, error::BoxDynError}; /// async fn run_query(conn: &mut PgConnection) -> Result<(), BoxDynError> { /// sqlx::query!("SELECT 1 as v").fetch_one(&mut *conn).await?; /// sqlx::query!("SELECT 2 as v").fetch_one(&mut *conn).await?; /// /// Ok(()) /// } /// ``` /// /// The downside of this approach is that you have to `acquire` a connection /// from a pool first and can't directly pass the pool as argument. /// /// [workaround]: https://github.com/launchbadge/sqlx/issues/1015#issuecomment-767787777 pub trait Acquire<'c> { type Database: Database; type Connection: Deref::Connection> + DerefMut + Send; fn acquire(self) -> BoxFuture<'c, Result>; fn begin(self) -> BoxFuture<'c, Result, Error>>; } impl<'a, DB: Database> Acquire<'a> for &'_ Pool { type Database = DB; type Connection = PoolConnection; fn acquire(self) -> BoxFuture<'static, Result> { Box::pin(self.acquire()) } fn begin(self) -> BoxFuture<'static, Result, Error>> { let conn = self.acquire(); Box::pin(async move { Transaction::begin(MaybePoolConnection::PoolConnection(conn.await?), None).await }) } } #[macro_export] macro_rules! impl_acquire { ($DB:ident, $C:ident) => { impl<'c> $crate::acquire::Acquire<'c> for &'c mut $C { type Database = $DB; type Connection = &'c mut <$DB as $crate::database::Database>::Connection; #[inline] fn acquire( self, ) -> futures_core::future::BoxFuture<'c, Result> { Box::pin(std::future::ready(Ok(self))) } #[inline] fn begin( self, ) -> futures_core::future::BoxFuture< 'c, Result<$crate::transaction::Transaction<'c, $DB>, $crate::error::Error>, > { $crate::transaction::Transaction::begin(self, None) } } }; } ================================================ FILE: sqlx-core/src/any/arguments.rs ================================================ use crate::any::value::AnyValueKind; use crate::any::{Any, AnyTypeInfoKind}; use crate::arguments::Arguments; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use std::sync::Arc; #[derive(Default)] pub struct AnyArguments { #[doc(hidden)] pub values: AnyArgumentBuffer, } impl Arguments for AnyArguments { type Database = Any; fn reserve(&mut self, additional: usize, _size: usize) { self.values.0.reserve(additional); } fn add<'t, T>(&mut self, value: T) -> Result<(), BoxDynError> where T: Encode<'t, Self::Database> + Type, { let _: IsNull = value.encode(&mut self.values)?; Ok(()) } fn len(&self) -> usize { self.values.0.len() } } #[derive(Default)] pub struct AnyArgumentBuffer(#[doc(hidden)] pub Vec); impl AnyArguments { #[doc(hidden)] pub fn convert_into<'a, A: Arguments>(self) -> Result where Option: Type + Encode<'a, A::Database>, Option: Type + Encode<'a, A::Database>, Option: Type + Encode<'a, A::Database>, Option: Type + Encode<'a, A::Database>, Option: Type + Encode<'a, A::Database>, Option: Type + Encode<'a, A::Database>, Option: Type + Encode<'a, A::Database>, Option: Type + Encode<'a, A::Database>, Option>: Type + Encode<'a, A::Database>, bool: Type + Encode<'a, A::Database>, i16: Type + Encode<'a, A::Database>, i32: Type + Encode<'a, A::Database>, i64: Type + Encode<'a, A::Database>, f32: Type + Encode<'a, A::Database>, f64: Type + Encode<'a, A::Database>, Arc: Type + Encode<'a, A::Database>, Arc: Type + Encode<'a, A::Database>, Arc>: Type + Encode<'a, A::Database>, { let mut out = A::default(); for arg in self.values.0 { match arg { AnyValueKind::Null(AnyTypeInfoKind::Null) => out.add(Option::::None), AnyValueKind::Null(AnyTypeInfoKind::Bool) => out.add(Option::::None), AnyValueKind::Null(AnyTypeInfoKind::SmallInt) => out.add(Option::::None), AnyValueKind::Null(AnyTypeInfoKind::Integer) => out.add(Option::::None), AnyValueKind::Null(AnyTypeInfoKind::BigInt) => out.add(Option::::None), AnyValueKind::Null(AnyTypeInfoKind::Real) => out.add(Option::::None), AnyValueKind::Null(AnyTypeInfoKind::Double) => out.add(Option::::None), AnyValueKind::Null(AnyTypeInfoKind::Text) => out.add(Option::::None), AnyValueKind::Null(AnyTypeInfoKind::Blob) => out.add(Option::>::None), AnyValueKind::Bool(b) => out.add(b), AnyValueKind::SmallInt(i) => out.add(i), AnyValueKind::Integer(i) => out.add(i), AnyValueKind::BigInt(i) => out.add(i), AnyValueKind::Real(r) => out.add(r), AnyValueKind::Double(d) => out.add(d), AnyValueKind::Text(t) => out.add(t), AnyValueKind::TextSlice(t) => out.add(t), AnyValueKind::Blob(b) => out.add(b), }? } Ok(out) } } ================================================ FILE: sqlx-core/src/any/column.rs ================================================ use crate::any::{Any, AnyTypeInfo}; use crate::column::Column; use crate::ext::ustr::UStr; #[derive(Debug, Clone)] pub struct AnyColumn { // NOTE: these fields are semver-exempt. See crate root docs for details. #[doc(hidden)] pub ordinal: usize, #[doc(hidden)] pub name: UStr, #[doc(hidden)] pub type_info: AnyTypeInfo, } impl Column for AnyColumn { type Database = Any; fn ordinal(&self) -> usize { self.ordinal } fn name(&self) -> &str { &self.name } fn type_info(&self) -> &AnyTypeInfo { &self.type_info } } ================================================ FILE: sqlx-core/src/any/connection/backend.rs ================================================ use crate::any::{AnyArguments, AnyQueryResult, AnyRow, AnyStatement, AnyTypeInfo}; use crate::sql_str::SqlStr; use either::Either; use futures_core::future::BoxFuture; use futures_core::stream::BoxStream; use std::fmt::Debug; pub trait AnyConnectionBackend: std::any::Any + Debug + Send + 'static { /// The backend name. fn name(&self) -> &str; /// Explicitly close this database connection. /// /// This method is **not required** for safe and consistent operation. However, it is /// recommended to call it instead of letting a connection `drop` as the database backend /// will be faster at cleaning up resources. fn close(self: Box) -> BoxFuture<'static, crate::Result<()>>; /// Immediately close the connection without sending a graceful shutdown. /// /// This should still at least send a TCP `FIN` frame to let the server know we're dying. #[doc(hidden)] fn close_hard(self: Box) -> BoxFuture<'static, crate::Result<()>>; /// Checks if a connection to the database is still valid. fn ping(&mut self) -> BoxFuture<'_, crate::Result<()>>; /// Begin a new transaction or establish a savepoint within the active transaction. /// /// If this is a new transaction, `statement` may be used instead of the /// default "BEGIN" statement. /// /// If we are already inside a transaction and `statement.is_some()`, then /// `Error::InvalidSavePoint` is returned without running any statements. fn begin(&mut self, statement: Option) -> BoxFuture<'_, crate::Result<()>>; fn commit(&mut self) -> BoxFuture<'_, crate::Result<()>>; fn rollback(&mut self) -> BoxFuture<'_, crate::Result<()>>; fn start_rollback(&mut self); /// Returns the current transaction depth. /// /// Transaction depth indicates the level of nested transactions: /// - Level 0: No active transaction. /// - Level 1: A transaction is active. /// - Level 2 or higher: A transaction is active and one or more SAVEPOINTs have been created within it. fn get_transaction_depth(&self) -> usize { unimplemented!("get_transaction_depth() is not implemented for this backend. This is a provided method to avoid a breaking change, but it will become a required method in version 0.9 and later."); } /// Checks if the connection is currently in a transaction. /// /// This method returns `true` if the current transaction depth is greater than 0, /// indicating that a transaction is active. It returns `false` if the transaction depth is 0, /// meaning no transaction is active. #[inline] fn is_in_transaction(&self) -> bool { self.get_transaction_depth() != 0 } /// The number of statements currently cached in the connection. fn cached_statements_size(&self) -> usize { 0 } /// Removes all statements from the cache, closing them on the server if /// needed. fn clear_cached_statements(&mut self) -> BoxFuture<'_, crate::Result<()>> { Box::pin(async move { Ok(()) }) } /// Forward to [`Connection::shrink_buffers()`]. /// /// [`Connection::shrink_buffers()`]: method@crate::connection::Connection::shrink_buffers fn shrink_buffers(&mut self); #[doc(hidden)] fn flush(&mut self) -> BoxFuture<'_, crate::Result<()>>; #[doc(hidden)] fn should_flush(&self) -> bool; #[cfg(feature = "migrate")] fn as_migrate(&mut self) -> crate::Result<&mut (dyn crate::migrate::Migrate + Send + 'static)> { Err(crate::Error::Configuration( format!( "{} driver does not support migrations or `migrate` feature was not enabled", self.name() ) .into(), )) } fn fetch_many( &mut self, query: SqlStr, persistent: bool, arguments: Option, ) -> BoxStream<'_, crate::Result>>; fn fetch_optional( &mut self, query: SqlStr, persistent: bool, arguments: Option, ) -> BoxFuture<'_, crate::Result>>; fn prepare_with<'c, 'q: 'c>( &'c mut self, sql: SqlStr, parameters: &[AnyTypeInfo], ) -> BoxFuture<'c, crate::Result>; #[cfg(feature = "offline")] fn describe( &mut self, sql: SqlStr, ) -> BoxFuture<'_, crate::Result>>; } ================================================ FILE: sqlx-core/src/any/connection/executor.rs ================================================ use crate::any::{Any, AnyConnection, AnyQueryResult, AnyRow, AnyStatement, AnyTypeInfo}; use crate::error::Error; use crate::executor::{Execute, Executor}; use crate::sql_str::SqlStr; use either::Either; use futures_core::future::BoxFuture; use futures_core::stream::BoxStream; use futures_util::{stream, FutureExt, StreamExt}; use std::future; impl<'c> Executor<'c> for &'c mut AnyConnection { type Database = Any; fn fetch_many<'e, 'q: 'e, E>( self, mut query: E, ) -> BoxStream<'e, Result, Error>> where 'c: 'e, E: 'q + Execute<'q, Any>, { let arguments = match query.take_arguments().map_err(Error::Encode) { Ok(arguments) => arguments, Err(error) => return stream::once(future::ready(Err(error))).boxed(), }; let persistent = query.persistent(); self.backend.fetch_many(query.sql(), persistent, arguments) } fn fetch_optional<'e, 'q: 'e, E>( self, mut query: E, ) -> BoxFuture<'e, Result, Error>> where 'c: 'e, E: 'q + Execute<'q, Self::Database>, { let arguments = match query.take_arguments().map_err(Error::Encode) { Ok(arguments) => arguments, Err(error) => return future::ready(Err(error)).boxed(), }; let persistent = query.persistent(); self.backend .fetch_optional(query.sql(), persistent, arguments) } fn prepare_with<'e>( self, sql: SqlStr, parameters: &[AnyTypeInfo], ) -> BoxFuture<'e, Result> where 'c: 'e, { self.backend.prepare_with(sql, parameters) } #[cfg(feature = "offline")] fn describe<'e>( self, sql: SqlStr, ) -> BoxFuture<'e, Result, Error>> where 'c: 'e, { self.backend.describe(sql) } } ================================================ FILE: sqlx-core/src/any/connection/mod.rs ================================================ use futures_core::future::BoxFuture; use std::future::Future; use crate::any::{Any, AnyConnectOptions}; use crate::connection::{ConnectOptions, Connection}; use crate::error::Error; use crate::config; use crate::database::Database; use crate::sql_str::SqlSafeStr; use crate::transaction::Transaction; pub use backend::AnyConnectionBackend; mod backend; mod executor; /// A connection to _any_ SQLx database. /// /// The database driver used is determined by the scheme /// of the connection url. /// /// ```text /// postgres://postgres@localhost/test /// sqlite://a.sqlite /// ``` #[derive(Debug)] pub struct AnyConnection { pub(crate) backend: Box, } impl AnyConnection { /// Returns the name of the database backend in use (e.g. PostgreSQL, MySQL, SQLite, etc.) pub fn backend_name(&self) -> &str { self.backend.name() } pub(crate) fn connect(options: &AnyConnectOptions) -> BoxFuture<'_, crate::Result> { Box::pin(async { let driver = crate::any::driver::from_url(&options.database_url)?; (*driver.connect)(options, None).await }) } /// UNSTABLE: for use with `sqlx-cli` /// /// Connect to the database, and instruct the nested driver to /// read options from the sqlx.toml file as appropriate. #[doc(hidden)] pub async fn connect_with_driver_config( url: &str, driver_config: &config::drivers::Config, ) -> Result where Self: Sized, { let options: AnyConnectOptions = url.parse()?; let driver = crate::any::driver::from_url(&options.database_url)?; (*driver.connect)(&options, Some(driver_config)).await } pub(crate) fn connect_with_db<'a, DB: Database>( options: &'a AnyConnectOptions, driver_config: Option<&'a config::drivers::Config>, ) -> BoxFuture<'a, crate::Result> where DB::Connection: AnyConnectionBackend, ::Options: for<'b> TryFrom<&'b AnyConnectOptions, Error = Error>, { let res = TryFrom::try_from(options); Box::pin(async move { let mut options: ::Options = res?; if let Some(config) = driver_config { options = options.__unstable_apply_driver_config(config)?; } Ok(AnyConnection { backend: Box::new(options.connect().await?), }) }) } #[cfg(feature = "migrate")] pub(crate) fn get_migrate( &mut self, ) -> crate::Result<&mut (dyn crate::migrate::Migrate + Send + 'static)> { self.backend.as_migrate() } } impl Connection for AnyConnection { type Database = Any; type Options = AnyConnectOptions; fn close(self) -> impl Future> + Send + 'static { self.backend.close() } fn close_hard(self) -> impl Future> + Send + 'static { self.backend.close() } fn ping(&mut self) -> impl Future> + Send + '_ { self.backend.ping() } fn begin( &mut self, ) -> impl Future, Error>> + Send + '_ where Self: Sized, { Transaction::begin(self, None) } fn begin_with( &mut self, statement: impl SqlSafeStr, ) -> impl Future, Error>> + Send + '_ where Self: Sized, { Transaction::begin(self, Some(statement.into_sql_str())) } fn cached_statements_size(&self) -> usize { self.backend.cached_statements_size() } fn clear_cached_statements(&mut self) -> impl Future> + Send + '_ { self.backend.clear_cached_statements() } fn shrink_buffers(&mut self) { self.backend.shrink_buffers() } #[doc(hidden)] fn flush(&mut self) -> impl Future> + Send + '_ { self.backend.flush() } #[doc(hidden)] fn should_flush(&self) -> bool { self.backend.should_flush() } } ================================================ FILE: sqlx-core/src/any/database.rs ================================================ use crate::any::{ AnyArgumentBuffer, AnyArguments, AnyColumn, AnyConnection, AnyQueryResult, AnyRow, AnyStatement, AnyTransactionManager, AnyTypeInfo, AnyValue, AnyValueRef, }; use crate::database::{Database, HasStatementCache}; /// Opaque database driver. Capable of being used in place of any SQLx database driver. The actual /// driver used will be selected at runtime, from the connection url. #[derive(Debug)] pub struct Any; impl Database for Any { type Connection = AnyConnection; type TransactionManager = AnyTransactionManager; type Row = AnyRow; type QueryResult = AnyQueryResult; type Column = AnyColumn; type TypeInfo = AnyTypeInfo; type Value = AnyValue; type ValueRef<'r> = AnyValueRef<'r>; type Arguments = AnyArguments; type ArgumentBuffer = AnyArgumentBuffer; type Statement = AnyStatement; const NAME: &'static str = "Any"; const URL_SCHEMES: &'static [&'static str] = &[]; } // This _may_ be true, depending on the selected database impl HasStatementCache for Any {} ================================================ FILE: sqlx-core/src/any/driver.rs ================================================ use crate::any::connection::AnyConnectionBackend; use crate::any::{AnyConnectOptions, AnyConnection}; use crate::common::DebugFn; use crate::connection::Connection; use crate::database::Database; use crate::{config, Error}; use futures_core::future::BoxFuture; use std::fmt::{Debug, Formatter}; use std::sync::OnceLock; use url::Url; static DRIVERS: OnceLock<&'static [AnyDriver]> = OnceLock::new(); #[macro_export] macro_rules! declare_driver_with_optional_migrate { ($name:ident = $db:path) => { #[cfg(feature = "migrate")] pub const $name: $crate::any::driver::AnyDriver = $crate::any::driver::AnyDriver::with_migrate::<$db>(); #[cfg(not(feature = "migrate"))] pub const $name: $crate::any::driver::AnyDriver = $crate::any::driver::AnyDriver::without_migrate::<$db>(); }; } #[non_exhaustive] pub struct AnyDriver { pub(crate) name: &'static str, pub(crate) url_schemes: &'static [&'static str], pub(crate) connect: DebugFn< for<'a> fn( &'a AnyConnectOptions, Option<&'a config::drivers::Config>, ) -> BoxFuture<'a, crate::Result>, >, pub(crate) migrate_database: Option, } impl AnyDriver { pub const fn without_migrate() -> Self where DB::Connection: AnyConnectionBackend, ::Options: for<'a> TryFrom<&'a AnyConnectOptions, Error = Error>, { Self { name: DB::NAME, url_schemes: DB::URL_SCHEMES, connect: DebugFn(AnyConnection::connect_with_db::), migrate_database: None, } } #[cfg(not(feature = "migrate"))] pub const fn with_migrate() -> Self where DB::Connection: AnyConnectionBackend, ::Options: for<'a> TryFrom<&'a AnyConnectOptions, Error = Error>, { Self::without_migrate::() } #[cfg(feature = "migrate")] pub const fn with_migrate() -> Self where DB::Connection: AnyConnectionBackend, ::Options: for<'a> TryFrom<&'a AnyConnectOptions, Error = Error>, { Self { migrate_database: Some(AnyMigrateDatabase { create_database: DebugFn(|url| Box::pin(DB::create_database(url))), database_exists: DebugFn(|url| Box::pin(DB::database_exists(url))), drop_database: DebugFn(|url| Box::pin(DB::drop_database(url))), force_drop_database: DebugFn(|url| Box::pin(DB::force_drop_database(url))), }), ..Self::without_migrate::() } } pub fn get_migrate_database(&self) -> crate::Result<&AnyMigrateDatabase> { self.migrate_database.as_ref() .ok_or_else(|| Error::Configuration(format!("{} driver does not support migrations or the `migrate` feature was not enabled for it", self.name).into())) } } impl Debug for AnyDriver { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("AnyDriver") .field("name", &self.name) .field("url_schemes", &self.url_schemes) .finish() } } pub struct AnyMigrateDatabase { create_database: DebugFn BoxFuture<'_, crate::Result<()>>>, database_exists: DebugFn BoxFuture<'_, crate::Result>>, drop_database: DebugFn BoxFuture<'_, crate::Result<()>>>, force_drop_database: DebugFn BoxFuture<'_, crate::Result<()>>>, } impl AnyMigrateDatabase { pub fn create_database<'a>(&self, url: &'a str) -> BoxFuture<'a, crate::Result<()>> { (self.create_database)(url) } pub fn database_exists<'a>(&self, url: &'a str) -> BoxFuture<'a, crate::Result> { (self.database_exists)(url) } pub fn drop_database<'a>(&self, url: &'a str) -> BoxFuture<'a, crate::Result<()>> { (self.drop_database)(url) } pub fn force_drop_database<'a>(&self, url: &'a str) -> BoxFuture<'a, crate::Result<()>> { (self.force_drop_database)(url) } } /// Install the list of drivers for [`AnyConnection`] to use. /// /// Must be called before an `AnyConnection` or `AnyPool` can be connected. /// /// ### Errors /// If called more than once. pub fn install_drivers( drivers: &'static [AnyDriver], ) -> Result<(), Box> { DRIVERS .set(drivers) .map_err(|_| "drivers already installed".into()) } #[cfg(feature = "migrate")] pub(crate) fn from_url_str(url: &str) -> crate::Result<&'static AnyDriver> { from_url(&url.parse().map_err(Error::config)?) } pub(crate) fn from_url(url: &Url) -> crate::Result<&'static AnyDriver> { let scheme = url.scheme(); let drivers: &[AnyDriver] = DRIVERS .get() .expect("No drivers installed. Please see the documentation in `sqlx::any` for details."); drivers .iter() .find(|driver| driver.url_schemes.contains(&url.scheme())) .ok_or_else(|| { Error::Configuration(format!("no driver found for URL scheme {scheme:?}").into()) }) } ================================================ FILE: sqlx-core/src/any/error.rs ================================================ use std::any::type_name; use crate::any::type_info::AnyTypeInfo; use crate::any::Any; use crate::error::BoxDynError; use crate::type_info::TypeInfo; use crate::types::Type; pub(super) fn mismatched_types>(ty: &AnyTypeInfo) -> BoxDynError { format!( "mismatched types; Rust type `{}` is not compatible with SQL type `{}`", type_name::(), ty.name() ) .into() } ================================================ FILE: sqlx-core/src/any/kind.rs ================================================ // Annoying how deprecation warnings trigger in the same module as the deprecated item. #![allow(deprecated)] // Cargo features are broken in this file. // `AnyKind` may return at some point but it won't be a simple enum. #![allow(unexpected_cfgs)] use crate::error::Error; use std::str::FromStr; #[deprecated = "not used or returned by any API"] #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum AnyKind { #[cfg(feature = "postgres")] Postgres, #[cfg(feature = "mysql")] MySql, #[cfg(feature = "_sqlite")] Sqlite, #[cfg(feature = "mssql")] Mssql, } impl FromStr for AnyKind { type Err = Error; fn from_str(url: &str) -> Result { match url { #[cfg(feature = "postgres")] _ if url.starts_with("postgres:") || url.starts_with("postgresql:") => { Ok(AnyKind::Postgres) } #[cfg(not(feature = "postgres"))] _ if url.starts_with("postgres:") || url.starts_with("postgresql:") => { Err(Error::Configuration("database URL has the scheme of a PostgreSQL database but the `postgres` feature is not enabled".into())) } #[cfg(feature = "mysql")] _ if url.starts_with("mysql:") || url.starts_with("mariadb:") => { Ok(AnyKind::MySql) } #[cfg(not(feature = "mysql"))] _ if url.starts_with("mysql:") || url.starts_with("mariadb:") => { Err(Error::Configuration("database URL has the scheme of a MySQL database but the `mysql` feature is not enabled".into())) } #[cfg(feature = "_sqlite")] _ if url.starts_with("sqlite:") => { Ok(AnyKind::Sqlite) } #[cfg(not(feature = "_sqlite"))] _ if url.starts_with("sqlite:") => { Err(Error::Configuration("database URL has the scheme of a SQLite database but the `sqlite` feature is not enabled".into())) } #[cfg(feature = "mssql")] _ if url.starts_with("mssql:") || url.starts_with("sqlserver:") => { Ok(AnyKind::Mssql) } #[cfg(not(feature = "mssql"))] _ if url.starts_with("mssql:") || url.starts_with("sqlserver:") => { Err(Error::Configuration("database URL has the scheme of a MSSQL database but the `mssql` feature is not enabled".into())) } _ => Err(Error::Configuration(format!("unrecognized database url: {url:?}").into())) } } } ================================================ FILE: sqlx-core/src/any/migrate.rs ================================================ use crate::any::driver; use crate::any::{Any, AnyConnection}; use crate::error::Error; use crate::migrate::{AppliedMigration, Migrate, MigrateDatabase, MigrateError, Migration}; use futures_core::future::BoxFuture; use std::time::Duration; impl MigrateDatabase for Any { async fn create_database(url: &str) -> Result<(), Error> { driver::from_url_str(url)? .get_migrate_database()? .create_database(url) .await } async fn database_exists(url: &str) -> Result { driver::from_url_str(url)? .get_migrate_database()? .database_exists(url) .await } async fn drop_database(url: &str) -> Result<(), Error> { driver::from_url_str(url)? .get_migrate_database()? .drop_database(url) .await } async fn force_drop_database(url: &str) -> Result<(), Error> { driver::from_url_str(url)? .get_migrate_database()? .force_drop_database(url) .await } } impl Migrate for AnyConnection { fn create_schema_if_not_exists<'e>( &'e mut self, schema_name: &'e str, ) -> BoxFuture<'e, Result<(), MigrateError>> { Box::pin(async { self.get_migrate()? .create_schema_if_not_exists(schema_name) .await }) } fn ensure_migrations_table<'e>( &'e mut self, table_name: &'e str, ) -> BoxFuture<'e, Result<(), MigrateError>> { Box::pin(async { self.get_migrate()? .ensure_migrations_table(table_name) .await }) } fn dirty_version<'e>( &'e mut self, table_name: &'e str, ) -> BoxFuture<'e, Result, MigrateError>> { Box::pin(async { self.get_migrate()?.dirty_version(table_name).await }) } fn list_applied_migrations<'e>( &'e mut self, table_name: &'e str, ) -> BoxFuture<'e, Result, MigrateError>> { Box::pin(async { self.get_migrate()? .list_applied_migrations(table_name) .await }) } fn lock(&mut self) -> BoxFuture<'_, Result<(), MigrateError>> { Box::pin(async { self.get_migrate()?.lock().await }) } fn unlock(&mut self) -> BoxFuture<'_, Result<(), MigrateError>> { Box::pin(async { self.get_migrate()?.unlock().await }) } fn apply<'e>( &'e mut self, table_name: &'e str, migration: &'e Migration, ) -> BoxFuture<'e, Result> { Box::pin(async { self.get_migrate()?.apply(table_name, migration).await }) } fn revert<'e>( &'e mut self, table_name: &'e str, migration: &'e Migration, ) -> BoxFuture<'e, Result> { Box::pin(async { self.get_migrate()?.revert(table_name, migration).await }) } } ================================================ FILE: sqlx-core/src/any/mod.rs ================================================ //! **SEE DOCUMENTATION BEFORE USE**. Generic database driver with the specific driver selected at runtime. //! //! The underlying database drivers are chosen at runtime from the list set via //! [`install_drivers`][self::driver::install_drivers]. Any use of `AnyConnection` or `AnyPool` //! without this will panic. use crate::executor::Executor; mod arguments; pub(crate) mod column; mod connection; mod database; mod error; mod kind; mod options; mod query_result; pub(crate) mod row; mod statement; mod transaction; pub(crate) mod type_info; pub mod types; pub(crate) mod value; pub mod driver; #[cfg(feature = "migrate")] mod migrate; pub use arguments::{AnyArgumentBuffer, AnyArguments}; pub use column::AnyColumn; pub use connection::AnyConnection; // Used internally in `sqlx-macros` use crate::encode::Encode; pub use connection::AnyConnectionBackend; pub use database::Any; #[allow(deprecated)] pub use kind::AnyKind; pub use options::AnyConnectOptions; pub use query_result::AnyQueryResult; pub use row::AnyRow; pub use statement::AnyStatement; pub use transaction::AnyTransactionManager; pub use type_info::{AnyTypeInfo, AnyTypeInfoKind}; pub use value::{AnyValue, AnyValueRef}; use crate::types::Type; #[doc(hidden)] pub use value::AnyValueKind; pub type AnyPool = crate::pool::Pool; pub type AnyPoolOptions = crate::pool::PoolOptions; /// An alias for [`Executor<'_, Database = Any>`][Executor]. pub trait AnyExecutor<'c>: Executor<'c, Database = Any> {} impl<'c, T: Executor<'c, Database = Any>> AnyExecutor<'c> for T {} // NOTE: required due to the lack of lazy normalization impl_into_arguments_for_arguments!(AnyArguments); // impl_executor_for_pool_connection!(Any, AnyConnection, AnyRow); // impl_executor_for_transaction!(Any, AnyRow); impl_acquire!(Any, AnyConnection); impl_column_index_for_row!(AnyRow); impl_column_index_for_statement!(AnyStatement); // impl_into_maybe_pool!(Any, AnyConnection); // required because some databases have a different handling of NULL impl<'q, T> Encode<'q, Any> for Option where T: Encode<'q, Any> + 'q + Type, { fn encode_by_ref( &self, buf: &mut AnyArgumentBuffer, ) -> Result { if let Some(value) = self { value.encode_by_ref(buf) } else { buf.0.push(AnyValueKind::Null(T::type_info().kind)); Ok(crate::encode::IsNull::Yes) } } } ================================================ FILE: sqlx-core/src/any/options.rs ================================================ use crate::any::AnyConnection; use crate::connection::{ConnectOptions, LogSettings}; use crate::error::Error; use log::LevelFilter; use std::future::Future; use std::str::FromStr; use std::time::Duration; use url::Url; /// Opaque options for connecting to a database. These may only be constructed by parsing from /// a connection url. /// /// ```text /// postgres://postgres:password@localhost/database /// mysql://root:password@localhost/database /// ``` #[derive(Debug, Clone)] #[non_exhaustive] pub struct AnyConnectOptions { pub database_url: Url, pub log_settings: LogSettings, } impl FromStr for AnyConnectOptions { type Err = Error; fn from_str(url: &str) -> Result { Ok(AnyConnectOptions { database_url: url .parse::() .map_err(|e| Error::Configuration(e.into()))?, log_settings: LogSettings::default(), }) } } impl ConnectOptions for AnyConnectOptions { type Connection = AnyConnection; fn from_url(url: &Url) -> Result { Ok(AnyConnectOptions { database_url: url.clone(), log_settings: LogSettings::default(), }) } fn to_url_lossy(&self) -> Url { self.database_url.clone() } #[inline] fn connect(&self) -> impl Future> + Send + '_ { AnyConnection::connect(self) } fn log_statements(mut self, level: LevelFilter) -> Self { self.log_settings.statements_level = level; self } fn log_slow_statements(mut self, level: LevelFilter, duration: Duration) -> Self { self.log_settings.slow_statements_level = level; self.log_settings.slow_statements_duration = duration; self } } impl AnyConnectOptions {} ================================================ FILE: sqlx-core/src/any/query_result.rs ================================================ use std::iter::{Extend, IntoIterator}; #[derive(Debug, Default)] pub struct AnyQueryResult { #[doc(hidden)] pub rows_affected: u64, #[doc(hidden)] pub last_insert_id: Option, } impl AnyQueryResult { pub fn rows_affected(&self) -> u64 { self.rows_affected } pub fn last_insert_id(&self) -> Option { self.last_insert_id } } impl Extend for AnyQueryResult { fn extend>(&mut self, iter: T) { for elem in iter { self.rows_affected += elem.rows_affected; self.last_insert_id = elem.last_insert_id; } } } ================================================ FILE: sqlx-core/src/any/row.rs ================================================ use crate::any::error::mismatched_types; use crate::any::{Any, AnyColumn, AnyTypeInfo, AnyTypeInfoKind, AnyValue, AnyValueKind}; use crate::column::{Column, ColumnIndex}; use crate::database::Database; use crate::decode::Decode; use crate::error::Error; use crate::ext::ustr::UStr; use crate::row::Row; use crate::type_info::TypeInfo; use crate::types::Type; use crate::value::{Value, ValueRef}; use std::sync::Arc; #[derive(Clone)] pub struct AnyRow { #[doc(hidden)] pub column_names: Arc>, #[doc(hidden)] pub columns: Vec, #[doc(hidden)] pub values: Vec, } impl Row for AnyRow { type Database = Any; fn columns(&self) -> &[AnyColumn] { &self.columns } fn try_get_raw(&self, index: I) -> Result<::ValueRef<'_>, Error> where I: ColumnIndex, { let index = index.index(self)?; Ok(self .values .get(index) .ok_or_else(|| Error::ColumnIndexOutOfBounds { index, len: self.columns.len(), })? .as_ref()) } fn try_get<'r, T, I>(&'r self, index: I) -> Result where I: ColumnIndex, T: Decode<'r, Self::Database> + Type, { let value = self.try_get_raw(&index)?; let ty = value.type_info(); if !value.is_null() && !ty.is_null() && !T::compatible(&ty) { Err(mismatched_types::(&ty)) } else { T::decode(value) } .map_err(|source| Error::ColumnDecode { index: format!("{index:?}"), source, }) } } impl ColumnIndex for &'_ str { fn index(&self, row: &AnyRow) -> Result { row.column_names .get(*self) .copied() .ok_or_else(|| Error::ColumnNotFound(self.to_string())) } } impl AnyRow { // This is not a `TryFrom` impl because trait impls are easy for users to accidentally // become reliant upon, even if hidden, but we want to be able to change the bounds // on this function as the `Any` driver gains support for more types. // // Also `column_names` needs to be passed by the driver to avoid making deep copies. #[doc(hidden)] pub fn map_from<'a, R: Row>( row: &'a R, column_names: Arc>, ) -> Result where usize: ColumnIndex, AnyTypeInfo: for<'b> TryFrom<&'b ::TypeInfo, Error = Error>, AnyColumn: for<'b> TryFrom<&'b ::Column, Error = Error>, bool: Type + Decode<'a, R::Database>, i16: Type + Decode<'a, R::Database>, i32: Type + Decode<'a, R::Database>, i64: Type + Decode<'a, R::Database>, f32: Type + Decode<'a, R::Database>, f64: Type + Decode<'a, R::Database>, String: Type + Decode<'a, R::Database>, Vec: Type + Decode<'a, R::Database>, { let mut row_out = AnyRow { column_names, columns: Vec::with_capacity(row.columns().len()), values: Vec::with_capacity(row.columns().len()), }; for col in row.columns() { let i = col.ordinal(); let any_col = AnyColumn::try_from(col)?; let value = row.try_get_raw(i)?; // Map based on the _value_ type info, not the column type info. let type_info = AnyTypeInfo::try_from(&value.type_info()).map_err(|e| Error::ColumnDecode { index: col.ordinal().to_string(), source: e.into(), })?; let value_kind = match type_info.kind { k if value.is_null() => AnyValueKind::Null(k), AnyTypeInfoKind::Null => AnyValueKind::Null(AnyTypeInfoKind::Null), AnyTypeInfoKind::Bool => AnyValueKind::Bool(decode(value)?), AnyTypeInfoKind::SmallInt => AnyValueKind::SmallInt(decode(value)?), AnyTypeInfoKind::Integer => AnyValueKind::Integer(decode(value)?), AnyTypeInfoKind::BigInt => AnyValueKind::BigInt(decode(value)?), AnyTypeInfoKind::Real => AnyValueKind::Real(decode(value)?), AnyTypeInfoKind::Double => AnyValueKind::Double(decode(value)?), AnyTypeInfoKind::Blob => AnyValueKind::Blob(decode::<_, Vec>(value)?.into()), AnyTypeInfoKind::Text => AnyValueKind::Text(decode::<_, String>(value)?.into()), }; row_out.columns.push(any_col); row_out.values.push(AnyValue { kind: value_kind }); } Ok(row_out) } } fn decode<'r, DB: Database, T: Decode<'r, DB>>( valueref: ::ValueRef<'r>, ) -> crate::Result { Decode::decode(valueref).map_err(Error::decode) } ================================================ FILE: sqlx-core/src/any/statement.rs ================================================ use crate::any::{Any, AnyArguments, AnyColumn, AnyTypeInfo}; use crate::column::ColumnIndex; use crate::database::Database; use crate::error::Error; use crate::ext::ustr::UStr; use crate::sql_str::SqlStr; use crate::statement::Statement; use crate::HashMap; use either::Either; use std::sync::Arc; #[derive(Clone)] pub struct AnyStatement { #[doc(hidden)] pub sql: SqlStr, #[doc(hidden)] pub parameters: Option, usize>>, #[doc(hidden)] pub column_names: Arc>, #[doc(hidden)] pub columns: Vec, } impl Statement for AnyStatement { type Database = Any; fn into_sql(self) -> SqlStr { self.sql } fn sql(&self) -> &SqlStr { &self.sql } fn parameters(&self) -> Option> { match &self.parameters { Some(Either::Left(types)) => Some(Either::Left(types)), Some(Either::Right(count)) => Some(Either::Right(*count)), None => None, } } fn columns(&self) -> &[AnyColumn] { &self.columns } impl_statement_query!(AnyArguments); } impl ColumnIndex for &'_ str { fn index(&self, statement: &AnyStatement) -> Result { statement .column_names .get(*self) .ok_or_else(|| Error::ColumnNotFound((*self).into())) .copied() } } impl AnyStatement { #[doc(hidden)] pub fn try_from_statement( statement: S, column_names: Arc>, ) -> crate::Result where S: Statement, AnyTypeInfo: for<'a> TryFrom<&'a ::TypeInfo, Error = Error>, AnyColumn: for<'a> TryFrom<&'a ::Column, Error = Error>, { let parameters = match statement.parameters() { Some(Either::Left(parameters)) => Some(Either::Left( parameters .iter() .map(AnyTypeInfo::try_from) .collect::, _>>()?, )), Some(Either::Right(count)) => Some(Either::Right(count)), None => None, }; let columns = statement .columns() .iter() .map(AnyColumn::try_from) .collect::, _>>()?; Ok(Self { sql: statement.into_sql(), columns, column_names, parameters, }) } } ================================================ FILE: sqlx-core/src/any/transaction.rs ================================================ use std::future::Future; use crate::any::{Any, AnyConnection}; use crate::database::Database; use crate::error::Error; use crate::sql_str::SqlStr; use crate::transaction::TransactionManager; pub struct AnyTransactionManager; impl TransactionManager for AnyTransactionManager { type Database = Any; fn begin( conn: &mut AnyConnection, statement: Option, ) -> impl Future> + Send + '_ { conn.backend.begin(statement) } fn commit(conn: &mut AnyConnection) -> impl Future> + Send + '_ { conn.backend.commit() } fn rollback(conn: &mut AnyConnection) -> impl Future> + Send + '_ { conn.backend.rollback() } fn start_rollback(conn: &mut AnyConnection) { conn.backend.start_rollback() } fn get_transaction_depth(conn: &::Connection) -> usize { conn.backend.get_transaction_depth() } } ================================================ FILE: sqlx-core/src/any/type_info.rs ================================================ use std::fmt::{self, Display, Formatter}; use crate::type_info::TypeInfo; use AnyTypeInfoKind::*; #[derive(Debug, Clone, PartialEq)] pub struct AnyTypeInfo { #[doc(hidden)] pub kind: AnyTypeInfoKind, } impl AnyTypeInfo { pub fn kind(&self) -> AnyTypeInfoKind { self.kind } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AnyTypeInfoKind { Null, Bool, SmallInt, Integer, BigInt, Real, Double, Text, Blob, } impl TypeInfo for AnyTypeInfo { fn is_null(&self) -> bool { self.kind == Null } fn name(&self) -> &str { use AnyTypeInfoKind::*; match self.kind { Bool => "BOOLEAN", SmallInt => "SMALLINT", Integer => "INTEGER", BigInt => "BIGINT", Real => "REAL", Double => "DOUBLE", Text => "TEXT", Blob => "BLOB", Null => "NULL", } } } impl Display for AnyTypeInfo { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str(self.name()) } } impl AnyTypeInfoKind { pub fn is_integer(&self) -> bool { matches!(self, SmallInt | Integer | BigInt) } } ================================================ FILE: sqlx-core/src/any/types/blob.rs ================================================ use crate::any::{Any, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind}; use crate::database::Database; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use std::sync::Arc; impl Type for [u8] { fn type_info() -> AnyTypeInfo { AnyTypeInfo { kind: AnyTypeInfoKind::Blob, } } } impl<'q> Encode<'q, Any> for &'q [u8] { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { buf.0.push(AnyValueKind::Blob(Arc::new(self.to_vec()))); Ok(IsNull::No) } } impl<'r> Decode<'r, Any> for &'r [u8] { fn decode(value: ::ValueRef<'r>) -> Result { match value.kind { AnyValueKind::Blob(blob) => Ok(blob.as_slice()), other => other.unexpected(), } } } impl Type for Vec { fn type_info() -> AnyTypeInfo { <[u8] as Type>::type_info() } } impl Encode<'_, Any> for Vec { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { buf.0.push(AnyValueKind::Blob(Arc::new(self.clone()))); Ok(IsNull::No) } } impl<'r> Decode<'r, Any> for Vec { fn decode(value: ::ValueRef<'r>) -> Result { match value.kind { AnyValueKind::Blob(blob) => Ok(blob.as_ref().clone()), other => other.unexpected(), } } } ================================================ FILE: sqlx-core/src/any/types/bool.rs ================================================ use crate::any::{Any, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind}; use crate::database::Database; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; impl Type for bool { fn type_info() -> AnyTypeInfo { AnyTypeInfo { kind: AnyTypeInfoKind::Bool, } } } impl Encode<'_, Any> for bool { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { buf.0.push(AnyValueKind::Bool(*self)); Ok(IsNull::No) } } impl<'r> Decode<'r, Any> for bool { fn decode(value: ::ValueRef<'r>) -> Result { match value.kind { AnyValueKind::Bool(b) => Ok(*b), other => other.unexpected(), } } } ================================================ FILE: sqlx-core/src/any/types/float.rs ================================================ use crate::any::{Any, AnyArgumentBuffer, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind, AnyValueRef}; use crate::database::Database; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; impl Type for f32 { fn type_info() -> AnyTypeInfo { AnyTypeInfo { kind: AnyTypeInfoKind::Real, } } } impl Encode<'_, Any> for f32 { fn encode_by_ref(&self, buf: &mut AnyArgumentBuffer) -> Result { buf.0.push(AnyValueKind::Real(*self)); Ok(IsNull::No) } } impl<'r> Decode<'r, Any> for f32 { fn decode(value: AnyValueRef<'r>) -> Result { match value.kind { AnyValueKind::Real(r) => Ok(*r), other => other.unexpected(), } } } impl Type for f64 { fn type_info() -> AnyTypeInfo { AnyTypeInfo { kind: AnyTypeInfoKind::Double, } } } impl Encode<'_, Any> for f64 { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { buf.0.push(AnyValueKind::Double(*self)); Ok(IsNull::No) } } impl<'r> Decode<'r, Any> for f64 { fn decode(value: ::ValueRef<'r>) -> Result { match value.kind { // Widening is safe AnyValueKind::Real(r) => Ok(*r as f64), AnyValueKind::Double(d) => Ok(*d), other => other.unexpected(), } } } ================================================ FILE: sqlx-core/src/any/types/int.rs ================================================ use crate::any::{Any, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind}; use crate::database::Database; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; impl Type for i16 { fn type_info() -> AnyTypeInfo { AnyTypeInfo { kind: AnyTypeInfoKind::SmallInt, } } fn compatible(ty: &AnyTypeInfo) -> bool { ty.kind().is_integer() } } impl Encode<'_, Any> for i16 { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { buf.0.push(AnyValueKind::SmallInt(*self)); Ok(IsNull::No) } } impl<'r> Decode<'r, Any> for i16 { fn decode(value: ::ValueRef<'r>) -> Result { value.kind.try_integer() } } impl Type for i32 { fn type_info() -> AnyTypeInfo { AnyTypeInfo { kind: AnyTypeInfoKind::Integer, } } fn compatible(ty: &AnyTypeInfo) -> bool { ty.kind().is_integer() } } impl Encode<'_, Any> for i32 { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { buf.0.push(AnyValueKind::Integer(*self)); Ok(IsNull::No) } } impl<'r> Decode<'r, Any> for i32 { fn decode(value: ::ValueRef<'r>) -> Result { value.kind.try_integer() } } impl Type for i64 { fn type_info() -> AnyTypeInfo { AnyTypeInfo { kind: AnyTypeInfoKind::BigInt, } } fn compatible(ty: &AnyTypeInfo) -> bool { ty.kind().is_integer() } } impl Encode<'_, Any> for i64 { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { buf.0.push(AnyValueKind::BigInt(*self)); Ok(IsNull::No) } } impl<'r> Decode<'r, Any> for i64 { fn decode(value: ::ValueRef<'r>) -> Result { value.kind.try_integer() } } ================================================ FILE: sqlx-core/src/any/types/mod.rs ================================================ //! Conversions between Rust and standard **SQL** types. //! //! # Types //! //! | Rust type | SQL type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `bool` | BOOLEAN | //! | `i16` | SMALLINT | //! | `i32` | INT | //! | `i64` | BIGINT | //! | `f32` | FLOAT | //! | `f64` | DOUBLE | //! | `&str`, [`String`] | VARCHAR, CHAR, TEXT | //! //! # Nullable //! //! In addition, `Option` is supported where `T` implements `Type`. An `Option` represents //! a potentially `NULL` value from SQL. mod blob; mod bool; mod float; mod int; mod str; #[test] fn test_type_impls() { use crate::any::Any; use crate::decode::Decode; use crate::encode::Encode; use crate::types::Type; fn has_type() where T: Type, for<'a> T: Encode<'a, Any>, for<'a> T: Decode<'a, Any>, { } has_type::(); has_type::(); has_type::(); has_type::(); has_type::(); has_type::(); // These imply that there are also impls for the equivalent slice types. has_type::>(); has_type::(); } ================================================ FILE: sqlx-core/src/any/types/str.rs ================================================ use crate::any::types::str; use crate::any::{Any, AnyTypeInfo, AnyTypeInfoKind, AnyValueKind}; use crate::database::Database; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use std::sync::Arc; impl Type for str { fn type_info() -> AnyTypeInfo { AnyTypeInfo { kind: AnyTypeInfoKind::Text, } } } impl<'a> Encode<'a, Any> for &'a str { fn encode(self, buf: &mut ::ArgumentBuffer) -> Result where Self: Sized, { buf.0.push(AnyValueKind::Text(Arc::new(self.into()))); Ok(IsNull::No) } fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { (*self).encode(buf) } } impl<'a> Decode<'a, Any> for &'a str { fn decode(value: ::ValueRef<'a>) -> Result { match value.kind { AnyValueKind::Text(text) => Ok(text.as_str()), other => other.unexpected(), } } } impl Type for String { fn type_info() -> AnyTypeInfo { >::type_info() } } impl Encode<'_, Any> for String { fn encode(self, buf: &mut ::ArgumentBuffer) -> Result { buf.0.push(AnyValueKind::Text(Arc::new(self))); Ok(IsNull::No) } fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { buf.0.push(AnyValueKind::Text(Arc::new(self.clone()))); Ok(IsNull::No) } } impl<'r> Decode<'r, Any> for String { fn decode(value: ::ValueRef<'r>) -> Result { match value.kind { AnyValueKind::Text(text) => Ok(text.to_string()), other => other.unexpected(), } } } ================================================ FILE: sqlx-core/src/any/value.rs ================================================ use crate::any::{Any, AnyTypeInfo, AnyTypeInfoKind}; use crate::database::Database; use crate::error::BoxDynError; use crate::types::Type; use crate::value::{Value, ValueRef}; use std::borrow::Cow; use std::sync::Arc; #[derive(Clone, Debug)] #[non_exhaustive] pub enum AnyValueKind { Null(AnyTypeInfoKind), Bool(bool), SmallInt(i16), Integer(i32), BigInt(i64), Real(f32), Double(f64), Text(Arc), TextSlice(Arc), Blob(Arc>), } impl AnyValueKind { fn type_info(&self) -> AnyTypeInfo { AnyTypeInfo { kind: match self { AnyValueKind::Null(_) => AnyTypeInfoKind::Null, AnyValueKind::Bool(_) => AnyTypeInfoKind::Bool, AnyValueKind::SmallInt(_) => AnyTypeInfoKind::SmallInt, AnyValueKind::Integer(_) => AnyTypeInfoKind::Integer, AnyValueKind::BigInt(_) => AnyTypeInfoKind::BigInt, AnyValueKind::Real(_) => AnyTypeInfoKind::Real, AnyValueKind::Double(_) => AnyTypeInfoKind::Double, AnyValueKind::Text(_) => AnyTypeInfoKind::Text, AnyValueKind::TextSlice(_) => AnyTypeInfoKind::Text, AnyValueKind::Blob(_) => AnyTypeInfoKind::Blob, }, } } pub(in crate::any) fn unexpected>(&self) -> Result { Err(format!("expected {}, got {:?}", Expected::type_info(), self).into()) } pub(in crate::any) fn try_integer(&self) -> Result where T: Type + TryFrom + TryFrom + TryFrom, BoxDynError: From<>::Error>, BoxDynError: From<>::Error>, BoxDynError: From<>::Error>, { Ok(match self { AnyValueKind::SmallInt(i) => (*i).try_into()?, AnyValueKind::Integer(i) => (*i).try_into()?, AnyValueKind::BigInt(i) => (*i).try_into()?, _ => return self.unexpected(), }) } } #[derive(Clone, Debug)] pub struct AnyValue { #[doc(hidden)] pub kind: AnyValueKind, } #[derive(Clone, Debug)] pub struct AnyValueRef<'a> { pub(crate) kind: &'a AnyValueKind, } impl Value for AnyValue { type Database = Any; fn as_ref(&self) -> ::ValueRef<'_> { AnyValueRef { kind: &self.kind } } fn type_info(&self) -> Cow<'_, ::TypeInfo> { Cow::Owned(self.kind.type_info()) } fn is_null(&self) -> bool { matches!(self.kind, AnyValueKind::Null(_)) } } impl<'a> ValueRef<'a> for AnyValueRef<'a> { type Database = Any; fn to_owned(&self) -> ::Value { AnyValue { kind: self.kind.clone(), } } fn type_info(&self) -> Cow<'_, ::TypeInfo> { Cow::Owned(self.kind.type_info()) } fn is_null(&self) -> bool { matches!(self.kind, AnyValueKind::Null(_)) } } ================================================ FILE: sqlx-core/src/arguments.rs ================================================ //! Types and traits for passing arguments to SQL queries. use crate::database::Database; use crate::encode::Encode; use crate::error::BoxDynError; use crate::types::Type; use std::fmt::{self, Write}; /// A tuple of arguments to be sent to the database. // This lint is designed for general collections, but `Arguments` is not meant to be as such. #[allow(clippy::len_without_is_empty)] pub trait Arguments: Send + Sized + Default { type Database: Database; /// Reserves the capacity for at least `additional` more values (of `size` total bytes) to /// be added to the arguments without a reallocation. fn reserve(&mut self, additional: usize, size: usize); /// Add the value to the end of the arguments. fn add<'t, T>(&mut self, value: T) -> Result<(), BoxDynError> where T: Encode<'t, Self::Database> + Type; /// The number of arguments that were already added. fn len(&self) -> usize; fn format_placeholder(&self, writer: &mut W) -> fmt::Result { writer.write_str("?") } } pub trait IntoArguments: Sized + Send { fn into_arguments(self) -> ::Arguments; } // NOTE: required due to lack of lazy normalization #[macro_export] macro_rules! impl_into_arguments_for_arguments { ($Arguments:path) => { impl $crate::arguments::IntoArguments<<$Arguments as $crate::arguments::Arguments>::Database> for $Arguments { fn into_arguments(self) -> $Arguments { self } } }; } /// used by the query macros to prevent supernumerary `.bind()` calls pub struct ImmutableArguments(pub ::Arguments); impl IntoArguments for ImmutableArguments { fn into_arguments(self) -> ::Arguments { self.0 } } ================================================ FILE: sqlx-core/src/column.rs ================================================ use crate::database::Database; use crate::error::Error; use std::fmt::Debug; use std::sync::Arc; pub trait Column: 'static + Send + Sync + Debug { type Database: Database; /// Gets the column ordinal. /// /// This can be used to unambiguously refer to this column within a row in case more than /// one column have the same name fn ordinal(&self) -> usize; /// Gets the column name or alias. /// /// The column name is unreliable (and can change between database minor versions) if this /// column is an expression that has not been aliased. fn name(&self) -> &str; /// Gets the type information for the column. fn type_info(&self) -> &::TypeInfo; /// If this column comes from a table, return the table and original column name. /// /// Returns [`ColumnOrigin::Expression`] if the column is the result of an expression /// or else the source table could not be determined. /// /// Returns [`ColumnOrigin::Unknown`] if the database driver does not have that information, /// or has not overridden this method. // This method returns an owned value instead of a reference, // to give the implementor more flexibility. fn origin(&self) -> ColumnOrigin { ColumnOrigin::Unknown } } /// A [`Column`] that originates from a table. #[derive(Debug, Clone)] #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] pub struct TableColumn { /// The name of the table (optionally schema-qualified) that the column comes from. pub table: Arc, /// The original name of the column. pub name: Arc, } /// The possible statuses for our knowledge of the origin of a [`Column`]. #[derive(Debug, Clone, Default)] #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] pub enum ColumnOrigin { /// The column is known to originate from a table. /// /// Included is the table name and original column name. Table(TableColumn), /// The column originates from an expression, or else its origin could not be determined. Expression, /// The database driver does not know the column origin at this time. /// /// This may happen if: /// * The connection is in the middle of executing a query, /// and cannot query the catalog to fetch this information. /// * The connection does not have access to the database catalog. /// * The implementation of [`Column`] did not override [`Column::origin()`]. #[default] Unknown, } impl ColumnOrigin { /// Returns the true column origin, if known. pub fn table_column(&self) -> Option<&TableColumn> { if let Self::Table(table_column) = self { Some(table_column) } else { None } } } /// A type that can be used to index into a [`Row`] or [`Statement`]. /// /// The [`get`] and [`try_get`] methods of [`Row`] accept any type that implements `ColumnIndex`. /// This trait is implemented for strings which are used to look up a column by name, and for /// `usize` which is used as a positional index into the row. /// /// [`Row`]: crate::row::Row /// [`Statement`]: crate::statement::Statement /// [`get`]: crate::row::Row::get /// [`try_get`]: crate::row::Row::try_get /// pub trait ColumnIndex: Debug { /// Returns a valid positional index into the row or statement, [`ColumnIndexOutOfBounds`], or, /// [`ColumnNotFound`]. /// /// [`ColumnNotFound`]: Error::ColumnNotFound /// [`ColumnIndexOutOfBounds`]: Error::ColumnIndexOutOfBounds fn index(&self, container: &T) -> Result; } impl + ?Sized> ColumnIndex for &'_ I { #[inline] fn index(&self, row: &T) -> Result { (**self).index(row) } } #[macro_export] macro_rules! impl_column_index_for_row { ($R:ident) => { impl $crate::column::ColumnIndex<$R> for usize { fn index(&self, row: &$R) -> Result { let len = $crate::row::Row::len(row); if *self >= len { return Err($crate::error::Error::ColumnIndexOutOfBounds { len, index: *self }); } Ok(*self) } } }; } #[macro_export] macro_rules! impl_column_index_for_statement { ($S:ident) => { impl $crate::column::ColumnIndex<$S> for usize { fn index(&self, statement: &$S) -> Result { let len = $crate::statement::Statement::columns(statement).len(); if *self >= len { return Err($crate::error::Error::ColumnIndexOutOfBounds { len, index: *self }); } Ok(*self) } } }; } ================================================ FILE: sqlx-core/src/common/mod.rs ================================================ mod statement_cache; pub use statement_cache::StatementCache; use std::fmt::{Debug, Formatter}; use std::ops::{Deref, DerefMut}; /// A wrapper for `Fn`s that provides a debug impl that just says "Function" pub struct DebugFn(pub F); impl Deref for DebugFn { type Target = F; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for DebugFn { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl Debug for DebugFn { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_tuple("Function").finish() } } ================================================ FILE: sqlx-core/src/common/statement_cache.rs ================================================ use hashlink::lru_cache::LruCache; /// A cache for prepared statements. When full, the least recently used /// statement gets removed. #[derive(Debug)] pub struct StatementCache { inner: LruCache, } impl StatementCache { /// Create a new cache with the given capacity. pub fn new(capacity: usize) -> Self { Self { inner: LruCache::new(capacity), } } /// Returns a mutable reference to the value corresponding to the given key /// in the cache, if any. pub fn get_mut(&mut self, k: &str) -> Option<&mut T> { self.inner.get_mut(k) } /// Inserts a new statement to the cache, returning the least recently used /// statement id if the cache is full, or if inserting with an existing key, /// the replaced existing statement. pub fn insert(&mut self, k: &str, v: T) -> Option { let mut lru_item = None; if self.capacity() == self.len() && !self.contains_key(k) { lru_item = self.remove_lru(); } else if self.contains_key(k) { lru_item = self.inner.remove(k); } self.inner.insert(k.into(), v); lru_item } /// The number of statements in the cache. pub fn len(&self) -> usize { self.inner.len() } pub fn is_empty(&self) -> bool { self.inner.is_empty() } /// Removes the least recently used item from the cache. pub fn remove_lru(&mut self) -> Option { self.inner.remove_lru().map(|(_, v)| v) } /// Clear all cached statements from the cache. pub fn clear(&mut self) { self.inner.clear(); } /// True if cache has a value for the given key. pub fn contains_key(&mut self, k: &str) -> bool { self.inner.contains_key(k) } /// Returns the maximum number of statements the cache can hold. pub fn capacity(&self) -> usize { self.inner.capacity() } /// Returns true if the cache capacity is more than 0. #[allow(dead_code)] // Only used for some `cfg`s pub fn is_enabled(&self) -> bool { self.capacity() > 0 } } ================================================ FILE: sqlx-core/src/config/common.rs ================================================ /// Configuration shared by multiple components. #[derive(Debug, Default)] #[cfg_attr( feature = "sqlx-toml", derive(serde::Deserialize), serde(default, rename_all = "kebab-case", deny_unknown_fields) )] pub struct Config { /// Override the database URL environment variable. /// /// This is used by both the macros and `sqlx-cli`. /// /// Case-sensitive. Defaults to `DATABASE_URL`. /// /// Example: Multi-Database Project /// ------- /// You can use multiple databases in the same project by breaking it up into multiple crates, /// then using a different environment variable for each. /// /// For example, with two crates in the workspace named `foo` and `bar`: /// /// #### `foo/sqlx.toml` /// ```toml /// [common] /// database-url-var = "FOO_DATABASE_URL" /// ``` /// /// #### `bar/sqlx.toml` /// ```toml /// [common] /// database-url-var = "BAR_DATABASE_URL" /// ``` /// /// #### `.env` /// ```text /// FOO_DATABASE_URL=postgres://postgres@localhost:5432/foo /// BAR_DATABASE_URL=postgres://postgres@localhost:5432/bar /// ``` /// /// The query macros used in `foo` will use `FOO_DATABASE_URL`, /// and the ones used in `bar` will use `BAR_DATABASE_URL`. pub database_url_var: Option, } impl Config { pub fn database_url_var(&self) -> &str { self.database_url_var.as_deref().unwrap_or("DATABASE_URL") } } ================================================ FILE: sqlx-core/src/config/drivers.rs ================================================ use std::error::Error; /// Configuration for specific database drivers (**applies to macros and `sqlx-cli` only**). /// /// # Note: Does Not Apply at Application Run-Time /// As of writing, these configuration parameters do *not* have any bearing on /// the runtime configuration of SQLx database drivers. /// /// Any parameters which overlap with runtime configuration /// (e.g. [`drivers.sqlite.unsafe-load-extensions`][SqliteConfig::unsafe_load_extensions]) /// _must_ be configured their normal ways at runtime (e.g. `SqliteConnectOptions::extension()`). /// /// See the documentation of individual fields for details. #[derive(Debug, Default)] #[cfg_attr( feature = "sqlx-toml", derive(serde::Deserialize), serde(default, rename_all = "kebab-case", deny_unknown_fields) )] pub struct Config { /// Configuration for the MySQL database driver. /// /// See [`MySqlConfig`] for details. pub mysql: MySqlConfig, /// Configuration for the Postgres database driver. /// /// See [`PgConfig`] for details. pub postgres: PgConfig, /// Configuration for the SQLite database driver. /// /// See [`SqliteConfig`] for details. pub sqlite: SqliteConfig, /// Configuration for external database drivers. /// /// See [`ExternalDriverConfig`] for details. pub external: ExternalDriverConfig, } /// Configuration for the MySQL database driver. #[derive(Debug, Default)] #[cfg_attr( feature = "sqlx-toml", derive(serde::Deserialize), serde(default, rename_all = "kebab-case", deny_unknown_fields) )] pub struct MySqlConfig { // No fields implemented yet. This key is only used to validate parsing. } /// Configuration for the Postgres database driver. #[derive(Debug, Default)] #[cfg_attr( feature = "sqlx-toml", derive(serde::Deserialize), serde(default, rename_all = "kebab-case", deny_unknown_fields) )] pub struct PgConfig { // No fields implemented yet. This key is only used to validate parsing. } /// Configuration for the SQLite database driver. #[derive(Debug, Default)] #[cfg_attr( feature = "sqlx-toml", derive(serde::Deserialize), serde(default, rename_all = "kebab-case", deny_unknown_fields) )] pub struct SqliteConfig { /// Specify extensions to load, either by name or by path. /// /// Paths should be relative to the workspace root. /// /// See [Loading an Extension](https://www.sqlite.org/loadext.html#loading_an_extension) /// in the SQLite manual for details. /// /// The `sqlite-load-extension` feature must be enabled and SQLite must be built /// _without_ [`SQLITE_OMIT_LOAD_EXTENSION`] enabled. /// /// [`SQLITE_OMIT_LOAD_EXTENSION`]: https://www.sqlite.org/compile.html#omit_load_extension /// /// # Note: Does Not Configure Runtime Extension Loading /// Extensions to be loaded at runtime *must* be separately configured with /// `SqliteConnectOptions::extension()` or `SqliteConnectOptions::extension_with_entrypoint()`. /// /// # Safety /// This causes arbitrary DLLs on the filesystem to be loaded at execution time, /// which can easily result in undefined behavior, memory corruption, /// or exploitable vulnerabilities if misused. /// /// It is not possible to provide a truly safe version of this API. /// /// Use this field with care, and only load extensions that you trust. /// /// # Example /// Load the `uuid` and `vsv` extensions from [`sqlean`](https://github.com/nalgeon/sqlean). /// /// `sqlx.toml`: /// ```toml /// [common.drivers.sqlite] /// unsafe-load-extensions = ["uuid", "vsv"] /// ``` pub unsafe_load_extensions: Vec, } /// Configuration for external database drivers. #[derive(Debug, Default)] #[cfg_attr(feature = "sqlx-toml", derive(serde::Deserialize), serde(transparent))] pub struct ExternalDriverConfig { #[cfg(feature = "sqlx-toml")] by_name: std::collections::BTreeMap, } /// Type-erased [`toml::de::Error`]. pub type TryParseError = Box; impl ExternalDriverConfig { /// Try to parse the config for a given driver name, returning `Ok(None)` if it does not exist. #[cfg(feature = "sqlx-toml")] pub fn try_parse( &self, name: &str, ) -> Result, TryParseError> { let Some(config) = self.by_name.get(name) else { return Ok(None); }; // What's really baffling is that `toml` doesn't provide any way to deserialize // from a `&Table` or `&Value`, only owned variants, so cloning is unavoidable here. Ok(Some(config.clone().try_into()?)) } /// Try to parse the config for a given driver name, returning `Ok(None)` if it does not exist. #[cfg(not(feature = "sqlx-toml"))] pub fn try_parse(&self, _name: &str) -> Result, TryParseError> { Ok(None) } } ================================================ FILE: sqlx-core/src/config/macros.rs ================================================ use std::collections::BTreeMap; /// Configuration for the `query!()` family of macros. /// /// See also [`common::Config`][crate::config::common::Config] for renaming `DATABASE_URL`. #[derive(Debug, Default)] #[cfg_attr( feature = "sqlx-toml", derive(serde::Deserialize), serde(default, rename_all = "kebab-case", deny_unknown_fields) )] pub struct Config { /// Specify which crates' types to use when types from multiple crates apply. /// /// See [`PreferredCrates`] for details. pub preferred_crates: PreferredCrates, /// Specify global overrides for mapping SQL type names to Rust type names. /// /// Default type mappings are defined by the database driver. /// Refer to the `sqlx::types` module for details. /// /// ## Note: Case-Sensitive /// Currently, the case of the type name MUST match the name SQLx knows it by. /// Built-in types are spelled in all-uppercase to match SQL convention. /// /// However, user-created types in Postgres are all-lowercase unless quoted. /// /// ## Note: Orthogonal to Nullability /// These overrides do not affect whether `query!()` decides to wrap a column in `Option<_>` /// or not. They only override the inner type used. /// /// ## Note: Schema Qualification (Postgres) /// Type names may be schema-qualified in Postgres. If so, the schema should be part /// of the type string, e.g. `'foo.bar'` to reference type `bar` in schema `foo`. /// /// The schema and/or type name may additionally be quoted in the string /// for a quoted identifier (see next section). /// /// Schema qualification should not be used for types in the search path. /// /// ## Note: Quoted Identifiers (Postgres) /// Type names using [quoted identifiers in Postgres] must also be specified with quotes here. /// /// Note, however, that the TOML format parses way the outer pair of quotes, /// so for quoted names in Postgres, double-quoting is necessary, /// e.g. `'"Foo"'` for SQL type `"Foo"`. /// /// To reference a schema-qualified type with a quoted name, use double-quotes after the /// dot, e.g. `'foo."Bar"'` to reference type `"Bar"` of schema `foo`, and vice versa for /// quoted schema names. /// /// We recommend wrapping all type names in single quotes, as shown below, /// to avoid confusion. /// /// MySQL/MariaDB and SQLite do not support custom types, so quoting type names should /// never be necessary. /// /// [quoted identifiers in Postgres]: https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS // Note: we wanted to be able to handle this intelligently, // but the `toml` crate authors weren't interested: https://github.com/toml-rs/toml/issues/761 // // We decided to just encourage always quoting type names instead. /// Example: Custom Wrapper Types /// ------- /// Does SQLx not support a type that you need? Do you want additional semantics not /// implemented on the built-in types? You can create a custom wrapper, /// or use an external crate. /// /// #### `sqlx.toml` /// ```toml /// [macros.type-overrides] /// # Override a built-in type /// 'UUID' = "crate::types::MyUuid" /// /// # Support an external or custom wrapper type (e.g. from the `isn` Postgres extension) /// # (NOTE: FOR DOCUMENTATION PURPOSES ONLY; THIS CRATE/TYPE DOES NOT EXIST AS OF WRITING) /// 'isbn13' = "isn_rs::sqlx::ISBN13" /// ``` /// /// Example: Custom Types in Postgres /// ------- /// If you have a custom type in Postgres that you want to map without needing to use /// the type override syntax in `sqlx::query!()` every time, you can specify a global /// override here. /// /// For example, a custom enum type `foo`: /// /// #### Migration or Setup SQL (e.g. `migrations/0_setup.sql`) /// ```sql /// CREATE TYPE foo AS ENUM ('Bar', 'Baz'); /// ``` /// /// #### `src/types.rs` /// ```rust,no_run /// #[derive(sqlx::Type)] /// pub enum Foo { /// Bar, /// Baz /// } /// ``` /// /// If you're not using `PascalCase` in your enum variants then you'll want to use /// `#[sqlx(rename_all = "")]` on your enum. /// See [`Type`][crate::type::Type] for details. /// /// #### `sqlx.toml` /// ```toml /// [macros.type-overrides] /// # Map SQL type `foo` to `crate::types::Foo` /// 'foo' = "crate::types::Foo" /// ``` /// /// Example: Schema-Qualified Types /// ------- /// (See `Note` section above for details.) /// /// ```toml /// [macros.type-overrides] /// # Map SQL type `foo.foo` to `crate::types::Foo` /// 'foo.foo' = "crate::types::Foo" /// ``` /// /// Example: Quoted Identifiers /// ------- /// If a type or schema uses quoted identifiers, /// it must be wrapped in quotes _twice_ for SQLx to know the difference: /// /// ```toml /// [macros.type-overrides] /// # `"Foo"` in SQLx /// '"Foo"' = "crate::types::Foo" /// # **NOT** `"Foo"` in SQLx (parses as just `Foo`) /// "Foo" = "crate::types::Foo" /// /// # Schema-qualified /// '"foo".foo' = "crate::types::Foo" /// 'foo."Foo"' = "crate::types::Foo" /// '"foo"."Foo"' = "crate::types::Foo" /// ``` /// /// (See `Note` section above for details.) // TODO: allow specifying different types for input vs output // e.g. to accept `&[T]` on input but output `Vec` pub type_overrides: BTreeMap, /// Specify per-table and per-column overrides for mapping SQL types to Rust types. /// /// Default type mappings are defined by the database driver. /// Refer to the `sqlx::types` module for details. /// /// The supported syntax is similar to [`type_overrides`][Self::type_overrides], /// (with the same caveat for quoted names!) but column names must be qualified /// by a separately quoted table name, which may optionally be schema-qualified. /// /// Multiple columns for the same SQL table may be written in the same table in TOML /// (see examples below). /// /// ## Note: Orthogonal to Nullability /// These overrides do not affect whether `query!()` decides to wrap a column in `Option<_>` /// or not. They only override the inner type used. /// /// ## Note: Schema Qualification /// Table names may be schema-qualified. If so, the schema should be part /// of the table name string, e.g. `'foo.bar'` to reference table `bar` in schema `foo`. /// /// The schema and/or type name may additionally be quoted in the string /// for a quoted identifier (see next section). /// /// Postgres users: schema qualification should not be used for tables in the search path. /// /// ## Note: Quoted Identifiers /// Schema, table, or column names using quoted identifiers ([MySQL], [Postgres], [SQLite]) /// in SQL must also be specified with quotes here. /// /// Postgres and SQLite use double-quotes (`"Foo"`) while MySQL uses backticks (`\`Foo\`). /// /// Note, however, that the TOML format parses way the outer pair of quotes, /// so for quoted names in Postgres, double-quoting is necessary, /// e.g. `'"Foo"'` for SQL name `"Foo"`. /// /// To reference a schema-qualified table with a quoted name, use the appropriate quotation /// characters after the dot, e.g. `'foo."Bar"'` to reference table `"Bar"` of schema `foo`, /// and vice versa for quoted schema names. /// /// We recommend wrapping all table and column names in single quotes, as shown below, /// to avoid confusion. /// /// [MySQL]: https://dev.mysql.com/doc/refman/8.4/en/identifiers.html /// [Postgres]: https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS /// [SQLite]: https://sqlite.org/lang_keywords.html // Note: we wanted to be able to handle this intelligently, // but the `toml` crate authors weren't interested: https://github.com/toml-rs/toml/issues/761 // // We decided to just encourage always quoting type names instead. /// /// Example /// ------- /// /// #### `sqlx.toml` /// ```toml /// [macros.table-overrides.'foo'] /// # Map column `bar` of table `foo` to Rust type `crate::types::Foo`: /// 'bar' = "crate::types::Bar" /// /// # Quoted column name /// # Note: same quoting requirements as `macros.type_overrides` /// '"Bar"' = "crate::types::Bar" /// /// # Note: will NOT work (parses as `Bar`) /// # "Bar" = "crate::types::Bar" /// /// # Table name may be quoted (note the wrapping single-quotes) /// [macros.table-overrides.'"Foo"'] /// 'bar' = "crate::types::Bar" /// '"Bar"' = "crate::types::Bar" /// /// # Table name may also be schema-qualified. /// # Note how the dot is inside the quotes. /// [macros.table-overrides.'my_schema.my_table'] /// 'my_column' = "crate::types::MyType" /// /// # Quoted schema, table, and column names /// [macros.table-overrides.'"My Schema"."My Table"'] /// '"My Column"' = "crate::types::MyType" /// ``` pub table_overrides: BTreeMap>, } #[derive(Debug, Default)] #[cfg_attr( feature = "sqlx-toml", derive(serde::Deserialize), serde(default, rename_all = "kebab-case") )] pub struct PreferredCrates { /// Specify the crate to use for mapping date/time types to Rust. /// /// The default behavior is to use whatever crate is enabled, /// [`chrono`] or [`time`] (the latter takes precedent). /// /// [`chrono`]: crate::types::chrono /// [`time`]: crate::types::time /// /// Example: Always Use Chrono /// ------- /// Thanks to Cargo's [feature unification], a crate in the dependency graph may enable /// the `time` feature of SQLx which will force it on for all crates using SQLx, /// which will result in problems if your crate wants to use types from [`chrono`]. /// /// You can use the type override syntax (see `sqlx::query!` for details), /// or you can force an override globally by setting this option. /// /// #### `sqlx.toml` /// ```toml /// [macros.preferred-crates] /// date-time = "chrono" /// ``` /// /// [feature unification]: https://doc.rust-lang.org/cargo/reference/features.html#feature-unification pub date_time: DateTimeCrate, /// Specify the crate to use for mapping `NUMERIC` types to Rust. /// /// The default behavior is to use whatever crate is enabled, /// [`bigdecimal`] or [`rust_decimal`] (the latter takes precedent). /// /// [`bigdecimal`]: crate::types::bigdecimal /// [`rust_decimal`]: crate::types::rust_decimal /// /// Example: Always Use `bigdecimal` /// ------- /// Thanks to Cargo's [feature unification], a crate in the dependency graph may enable /// the `rust_decimal` feature of SQLx which will force it on for all crates using SQLx, /// which will result in problems if your crate wants to use types from [`bigdecimal`]. /// /// You can use the type override syntax (see `sqlx::query!` for details), /// or you can force an override globally by setting this option. /// /// #### `sqlx.toml` /// ```toml /// [macros.preferred-crates] /// numeric = "bigdecimal" /// ``` /// /// [feature unification]: https://doc.rust-lang.org/cargo/reference/features.html#feature-unification pub numeric: NumericCrate, } /// The preferred crate to use for mapping date/time types to Rust. #[derive(Debug, Default, PartialEq, Eq)] #[cfg_attr( feature = "sqlx-toml", derive(serde::Deserialize), serde(rename_all = "snake_case") )] pub enum DateTimeCrate { /// Use whichever crate is enabled (`time` then `chrono`). #[default] Inferred, /// Always use types from [`chrono`][crate::types::chrono]. /// /// ```toml /// [macros.preferred-crates] /// date-time = "chrono" /// ``` Chrono, /// Always use types from [`time`][crate::types::time]. /// /// ```toml /// [macros.preferred-crates] /// date-time = "time" /// ``` Time, } /// The preferred crate to use for mapping `NUMERIC` types to Rust. #[derive(Debug, Default, PartialEq, Eq)] #[cfg_attr( feature = "sqlx-toml", derive(serde::Deserialize), serde(rename_all = "snake_case") )] pub enum NumericCrate { /// Use whichever crate is enabled (`rust_decimal` then `bigdecimal`). #[default] Inferred, /// Always use types from [`bigdecimal`][crate::types::bigdecimal]. /// /// ```toml /// [macros.preferred-crates] /// numeric = "bigdecimal" /// ``` #[cfg_attr(feature = "sqlx-toml", serde(rename = "bigdecimal"))] BigDecimal, /// Always use types from [`rust_decimal`][crate::types::rust_decimal]. /// /// ```toml /// [macros.preferred-crates] /// numeric = "rust_decimal" /// ``` RustDecimal, } /// A SQL type name; may optionally be schema-qualified. /// /// See [`macros.type-overrides`][Config::type_overrides] for usages. pub type SqlType = Box; /// A SQL table name; may optionally be schema-qualified. /// /// See [`macros.table-overrides`][Config::table_overrides] for usages. pub type TableName = Box; /// A column in a SQL table. /// /// See [`macros.table-overrides`][Config::table_overrides] for usages. pub type ColumnName = Box; /// A Rust type name or path. /// /// Should be a global path (not relative). pub type RustType = Box; /// Internal getter methods. impl Config { /// Get the override for a given type name (optionally schema-qualified). pub fn type_override(&self, type_name: &str) -> Option<&str> { // TODO: make this case-insensitive self.type_overrides.get(type_name).map(|s| &**s) } /// Get the override for a given column and table name (optionally schema-qualified). pub fn column_override(&self, table: &str, column: &str) -> Option<&str> { self.table_overrides .get(table) .and_then(|by_column| by_column.get(column)) .map(|s| &**s) } } impl DateTimeCrate { /// Returns `self == Self::Inferred` #[inline(always)] pub fn is_inferred(&self) -> bool { *self == Self::Inferred } #[inline(always)] pub fn crate_name(&self) -> Option<&str> { match self { Self::Inferred => None, Self::Chrono => Some("chrono"), Self::Time => Some("time"), } } } impl NumericCrate { /// Returns `self == Self::Inferred` #[inline(always)] pub fn is_inferred(&self) -> bool { *self == Self::Inferred } #[inline(always)] pub fn crate_name(&self) -> Option<&str> { match self { Self::Inferred => None, Self::BigDecimal => Some("bigdecimal"), Self::RustDecimal => Some("rust_decimal"), } } } ================================================ FILE: sqlx-core/src/config/migrate.rs ================================================ use std::collections::BTreeSet; /// Configuration for migrations when executed using `sqlx::migrate!()` or through `sqlx-cli`. /// /// ### Note /// A manually constructed [`Migrator`][crate::migrate::Migrator] will not be aware of these /// configuration options. We recommend using `sqlx::migrate!()` instead. /// /// ### Warning: Potential Data Loss or Corruption! /// Many of these options, if changed after migrations are set up, /// can result in data loss or corruption of a production database /// if the proper precautions are not taken. /// /// Be sure you know what you are doing and that you read all relevant documentation _thoroughly_. #[derive(Debug, Default)] #[cfg_attr( feature = "sqlx-toml", derive(serde::Deserialize), serde(default, rename_all = "kebab-case", deny_unknown_fields) )] pub struct Config { /// Specify the names of schemas to create if they don't already exist. /// /// This is done before checking the existence of the migrations table /// (`_sqlx_migrations` or overridden `table_name` below) so that it may be placed in /// one of these schemas. /// /// ### Example /// `sqlx.toml`: /// ```toml /// [migrate] /// create-schemas = ["foo"] /// ``` pub create_schemas: BTreeSet>, /// Override the name of the table used to track executed migrations. /// /// May be schema-qualified and/or contain quotes. Defaults to `_sqlx_migrations`. /// /// Potentially useful for multi-tenant databases. /// /// ### Warning: Potential Data Loss or Corruption! /// Changing this option for a production database will likely result in data loss or corruption /// as the migration machinery will no longer be aware of what migrations have been applied /// and will attempt to re-run them. /// /// You should create the new table as a copy of the existing migrations table (with contents!), /// and be sure all instances of your application have been migrated to the new /// table before deleting the old one. /// /// ### Example /// `sqlx.toml`: /// ```toml /// [migrate] /// # Put `_sqlx_migrations` in schema `foo` /// table-name = "foo._sqlx_migrations" /// ``` pub table_name: Option>, /// Override the directory used for migrations files. /// /// Relative to the crate root for `sqlx::migrate!()`, or the current directory for `sqlx-cli`. pub migrations_dir: Option>, /// Specify characters that should be ignored when hashing migrations. /// /// Any characters contained in the given array will be dropped when a migration is hashed. /// /// ### Warning: May Change Hashes for Existing Migrations /// Changing the characters considered in hashing migrations will likely /// change the output of the hash. /// /// This may require manual rectification for deployed databases. /// /// ### Example: Ignore Carriage Return (`` | `\r`) /// Line ending differences between platforms can result in migrations having non-repeatable /// hashes. The most common culprit is the carriage return (`` | `\r`), which Windows /// uses in its line endings alongside line feed (`` | `\n`), often written `CRLF` or `\r\n`, /// whereas Linux and macOS use only line feeds. /// /// `sqlx.toml`: /// ```toml /// [migrate] /// ignored-chars = ["\r"] /// ``` /// /// For projects using Git, this can also be addressed using [`.gitattributes`]: /// /// ```text /// # Force newlines in migrations to be line feeds on all platforms /// migrations/*.sql text eol=lf /// ``` /// /// This may require resetting or re-checking out the migrations files to take effect. /// /// [`.gitattributes`]: https://git-scm.com/docs/gitattributes /// /// ### Example: Ignore all Whitespace Characters /// To make your migrations amenable to reformatting, you may wish to tell SQLx to ignore /// _all_ whitespace characters in migrations. /// /// ##### Warning: Beware Syntactically Significant Whitespace! /// If your migrations use string literals or quoted identifiers which contain whitespace, /// this configuration will cause the migration machinery to ignore some changes to these. /// This may result in a mismatch between the development and production versions of /// your database. /// /// `sqlx.toml`: /// ```toml /// [migrate] /// # Ignore common whitespace characters when hashing /// ignored-chars = [" ", "\t", "\r", "\n"] # Space, tab, CR, LF /// ``` // Likely lower overhead for small sets than `HashSet`. pub ignored_chars: BTreeSet, /// Specify default options for new migrations created with `sqlx migrate add`. pub defaults: MigrationDefaults, } #[derive(Debug, Default)] #[cfg_attr( feature = "sqlx-toml", derive(serde::Deserialize), serde(default, rename_all = "kebab-case") )] pub struct MigrationDefaults { /// Specify the default type of migration that `sqlx migrate add` should create by default. /// /// ### Example: Use Reversible Migrations by Default /// `sqlx.toml`: /// ```toml /// [migrate.defaults] /// migration-type = "reversible" /// ``` pub migration_type: DefaultMigrationType, /// Specify the default scheme that `sqlx migrate add` should use for version integers. /// /// ### Example: Use Sequential Versioning by Default /// `sqlx.toml`: /// ```toml /// [migrate.defaults] /// migration-versioning = "sequential" /// ``` pub migration_versioning: DefaultVersioning, } /// The default type of migration that `sqlx migrate add` should create by default. #[derive(Debug, Default, PartialEq, Eq)] #[cfg_attr( feature = "sqlx-toml", derive(serde::Deserialize), serde(rename_all = "snake_case") )] pub enum DefaultMigrationType { /// Create the same migration type as that of the latest existing migration, /// or `Simple` otherwise. #[default] Inferred, /// Create non-reversible migrations (`_.sql`) by default. Simple, /// Create reversible migrations (`_.up.sql` and `[...].down.sql`) by default. Reversible, } /// The default scheme that `sqlx migrate add` should use for version integers. #[derive(Debug, Default, PartialEq, Eq)] #[cfg_attr( feature = "sqlx-toml", derive(serde::Deserialize), serde(rename_all = "snake_case") )] pub enum DefaultVersioning { /// Infer the versioning scheme from existing migrations: /// /// * If the versions of the last two migrations differ by `1`, infer `Sequential`. /// * If only one migration exists and has version `1`, infer `Sequential`. /// * Otherwise, infer `Timestamp`. #[default] Inferred, /// Use UTC timestamps for migration versions. /// /// This is the recommended versioning format as it's less likely to collide when multiple /// developers are creating migrations on different branches. /// /// The exact timestamp format is unspecified. Timestamp, /// Use sequential integers for migration versions. Sequential, } #[cfg(feature = "migrate")] impl Config { pub fn migrations_dir(&self) -> &str { self.migrations_dir.as_deref().unwrap_or("migrations") } pub fn table_name(&self) -> &str { self.table_name.as_deref().unwrap_or("_sqlx_migrations") } pub fn to_resolve_config(&self) -> crate::migrate::ResolveConfig { let mut config = crate::migrate::ResolveConfig::new(); config.ignore_chars(self.ignored_chars.iter().copied()); config } } ================================================ FILE: sqlx-core/src/config/mod.rs ================================================ //! (Exported for documentation only) Guide and reference for `sqlx.toml` files. //! //! To use, create a `sqlx.toml` file in your crate root (the same directory as your `Cargo.toml`). //! The configuration in a `sqlx.toml` configures SQLx *only* for the current crate. //! //! Requires the `sqlx-toml` feature (not enabled by default). //! //! `sqlx-cli` will also read `sqlx.toml` when running migrations. //! //! See the [`Config`] type and its fields for individual configuration options. //! //! See the [reference][`_reference`] for the full `sqlx.toml` file. use std::error::Error; use std::fmt::Debug; use std::io; use std::path::{Path, PathBuf}; /// Configuration shared by multiple components. /// /// See [`common::Config`] for details. pub mod common; pub mod drivers; /// Configuration for the `query!()` family of macros. /// /// See [`macros::Config`] for details. pub mod macros; /// Configuration for migrations when executed using `sqlx::migrate!()` or through `sqlx-cli`. /// /// See [`migrate::Config`] for details. pub mod migrate; /// Reference for `sqlx.toml` files /// /// Source: `sqlx-core/src/config/reference.toml` /// /// ```toml #[doc = include_str!("reference.toml")] /// ``` pub mod _reference {} #[cfg(all(test, feature = "sqlx-toml"))] mod tests; /// The parsed structure of a `sqlx.toml` file. #[derive(Debug, Default)] #[cfg_attr( feature = "sqlx-toml", derive(serde::Deserialize), serde(default, rename_all = "kebab-case", deny_unknown_fields) )] pub struct Config { /// Configuration shared by multiple components. /// /// See [`common::Config`] for details. pub common: common::Config, /// Configuration for database drivers. /// /// See [`drivers::Config`] for details. pub drivers: drivers::Config, /// Configuration for the `query!()` family of macros. /// /// See [`macros::Config`] for details. pub macros: macros::Config, /// Configuration for migrations when executed using `sqlx::migrate!()` or through `sqlx-cli`. /// /// See [`migrate::Config`] for details. pub migrate: migrate::Config, } /// Error returned from various methods of [`Config`]. #[derive(thiserror::Error, Debug)] pub enum ConfigError { /// The loading method expected `CARGO_MANIFEST_DIR` to be set and it wasn't. /// /// This is necessary to locate the root of the crate currently being compiled. /// /// See [the "Environment Variables" page of the Cargo Book][cargo-env] for details. /// /// [cargo-env]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates #[error("environment variable `CARGO_MANIFEST_DIR` must be set and valid")] Env( #[from] #[source] std::env::VarError, ), /// No configuration file was found. Not necessarily fatal. #[error("config file {path:?} not found")] NotFound { path: PathBuf }, /// An I/O error occurred while attempting to read the config file at `path`. /// /// If the error is [`io::ErrorKind::NotFound`], [`Self::NotFound`] is returned instead. #[error("error reading config file {path:?}")] Io { path: PathBuf, #[source] error: io::Error, }, /// An error in the TOML was encountered while parsing the config file at `path`. /// /// The error gives line numbers and context when printed with `Display`/`ToString`. /// /// Only returned if the `sqlx-toml` feature is enabled. #[error("error parsing config file {path:?}")] Parse { path: PathBuf, /// Type-erased [`toml::de::Error`]. #[source] error: Box, }, /// A `sqlx.toml` file was found or specified, but the `sqlx-toml` feature is not enabled. #[error("SQLx found config file at {path:?} but the `sqlx-toml` feature was not enabled")] ParseDisabled { path: PathBuf }, } impl ConfigError { /// Create a [`ConfigError`] from a [`std::io::Error`]. /// /// Maps to either `NotFound` or `Io`. pub fn from_io(path: impl Into, error: io::Error) -> Self { if error.kind() == io::ErrorKind::NotFound { Self::NotFound { path: path.into() } } else { Self::Io { path: path.into(), error, } } } /// If this error means the file was not found, return the path that was attempted. pub fn not_found_path(&self) -> Option<&Path> { if let Self::NotFound { path } = self { Some(path) } else { None } } } /// Internal methods for loading a `Config`. #[allow(clippy::result_large_err)] impl Config { /// Read `$CARGO_MANIFEST_DIR/sqlx.toml` or return `Config::default()` if it does not exist. /// /// # Errors /// * If `CARGO_MANIFEST_DIR` is not set. /// * If the file exists but could not be read or parsed. /// * If the file exists but the `sqlx-toml` feature is disabled. pub fn try_from_crate_or_default() -> Result { Self::try_from_path_or_default(get_crate_path()?) } /// Attempt to read `Config` from the path given, or return `Config::default()` if it does not exist. /// /// # Errors /// * If the file exists but could not be read or parsed. /// * If the file exists but the `sqlx-toml` feature is disabled. pub fn try_from_path_or_default(path: PathBuf) -> Result { Self::read_from(path).or_else(|e| { if let ConfigError::NotFound { .. } = e { Ok(Config::default()) } else { Err(e) } }) } /// Attempt to read `Config` from the path given. /// /// # Errors /// * If the file does not exist. /// * If the file exists but could not be read or parsed. /// * If the file exists but the `sqlx-toml` feature is disabled. pub fn try_from_path(path: PathBuf) -> Result { Self::read_from(path) } #[cfg(feature = "sqlx-toml")] fn read_from(path: PathBuf) -> Result { // The `toml` crate doesn't provide an incremental reader. let toml_s = match std::fs::read_to_string(&path) { Ok(toml) => toml, Err(error) => { return Err(ConfigError::from_io(path, error)); } }; // TODO: parse and lint TOML structure before deserializing // Motivation: https://github.com/toml-rs/toml/issues/761 tracing::debug!("read config TOML from {path:?}:\n{toml_s}"); toml::from_str(&toml_s).map_err(|error| ConfigError::Parse { path, error: Box::new(error), }) } #[cfg(not(feature = "sqlx-toml"))] fn read_from(path: PathBuf) -> Result { match path.try_exists() { Ok(true) => Err(ConfigError::ParseDisabled { path }), Ok(false) => Err(ConfigError::NotFound { path }), Err(e) => Err(ConfigError::from_io(path, e)), } } } fn get_crate_path() -> Result { let mut path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?); path.push("sqlx.toml"); Ok(path) } ================================================ FILE: sqlx-core/src/config/reference.toml ================================================ # `sqlx.toml` reference. # # Note: shown values are *not* defaults. # They are explicitly set to non-default values to test parsing. # Refer to the comment for a given option for its default value. ############################################################################################### # Configuration shared by multiple components. [common] # Change the environment variable to get the database URL. # # This is used by both the macros and `sqlx-cli`. # # If not specified, defaults to `DATABASE_URL` database-url-var = "FOO_DATABASE_URL" ############################################################################################### # Configuration of SQLx database drivers (**applies to macros and sqlx-cli only**) [drivers] # Configure MySQL databases in macros and sqlx-cli. [drivers.mysql] # No fields implemented yet. This key is only used to validate parsing. # Configure Postgres databases in macros and sqlx-cli. [drivers.postgres] # No fields implemented yet. This key is only used to validate parsing. # Configure Postgres databases in macros and sqlx-cli. [drivers.sqlite] # Load extensions into SQLite when running macros or migrations # # Defaults to an empty list, which has no effect. # # Must be specified separately at run-time using `SqliteConnectOptions::extension()` or `::extension_with_entrypoint()`. # # Safety # This causes arbitrary DLLs on the filesystem to be loaded at runtime, # which can easily result in undefined behavior, memory corruption, # or exploitable vulnerabilities if misused. # # It is not possible to provide a truly safe version of this API. # # Use this field with care, and only load extensions that you trust. unsafe-load-extensions = ["uuid", "vsv"] # Configure external drivers in macros and sqlx-cli. # # These keys are only validated when the external driver tries to parse them, # unlike config for built-in drivers which is validated directly. [drivers.external.""] foo = 'foo' bar = true ############################################################################################### # Configuration for the `query!()` family of macros. [macros] [macros.preferred-crates] # Force the macros to use the `chrono` crate for date/time types, even if `time` is enabled. # # Defaults to "inferred": use whichever crate is enabled (`time` takes precedence over `chrono`). date-time = "chrono" # Or, ensure the macros always prefer `time` # in case new date/time crates are added in the future: # date-time = "time" # Force the macros to use the `rust_decimal` crate for `NUMERIC`, even if `bigdecimal` is enabled. # # Defaults to "inferred": use whichever crate is enabled (`bigdecimal` takes precedence over `rust_decimal`). numeric = "rust_decimal" # Or, ensure the macros always prefer `bigdecimal` # in case new decimal crates are added in the future: # numeric = "bigdecimal" # Set global overrides for mapping SQL types to Rust types. # # Default type mappings are defined by the database driver. # Refer to the `sqlx::types` module for details. # # Postgres users: schema qualification should not be used for types in the search path. # # ### Note: Orthogonal to Nullability # These overrides do not affect whether `query!()` decides to wrap a column in `Option<_>` # or not. They only override the inner type used. [macros.type-overrides] # Override a built-in type (map all `UUID` columns to `crate::types::MyUuid`) # Note: currently, the case of the type name MUST match. # Built-in types are spelled in all-uppercase to match SQL convention. 'UUID' = "crate::types::MyUuid" # Support an external or custom wrapper type (e.g. from the `isn` Postgres extension) # (NOTE: FOR DOCUMENTATION PURPOSES ONLY; THIS CRATE/TYPE DOES NOT EXIST AS OF WRITING) 'isbn13' = "isn_rs::isbn::ISBN13" # SQL type `foo` to Rust type `crate::types::Foo`: 'foo' = "crate::types::Foo" # SQL type `"Bar"` to Rust type `crate::types::Bar`; notice the extra pair of quotes: '"Bar"' = "crate::types::Bar" # Will NOT work (the first pair of quotes are parsed by TOML) # "Bar" = "crate::types::Bar" # Schema qualified 'foo.bar' = "crate::types::Bar" # Schema qualified and quoted 'foo."Bar"' = "crate::schema::foo::Bar" # Quoted schema name '"Foo".bar' = "crate::schema::foo::Bar" # Quoted schema and type name '"Foo"."Bar"' = "crate::schema::foo::Bar" # Set per-table and per-column overrides for mapping SQL types to Rust types. # # Note: table name is required in the header. # # Postgres users: schema qualification should not be used for types in the search path. # # ### Note: Orthogonal to Nullability # These overrides do not affect whether `query!()` decides to wrap a column in `Option<_>` # or not. They only override the inner type used. [macros.table-overrides.'foo'] # Map column `bar` of table `foo` to Rust type `crate::types::Foo`: 'bar' = "crate::types::Bar" # Quoted column name # Note: same quoting requirements as `macros.type_overrides` '"Bar"' = "crate::types::Bar" # Note: will NOT work (parses as `Bar`) # "Bar" = "crate::types::Bar" # Table name may be quoted (note the wrapping single-quotes) [macros.table-overrides.'"Foo"'] 'bar' = "crate::types::Bar" '"Bar"' = "crate::types::Bar" # Table name may also be schema-qualified. # Note how the dot is inside the quotes. [macros.table-overrides.'my_schema.my_table'] 'my_column' = "crate::types::MyType" # Quoted schema, table, and column names [macros.table-overrides.'"My Schema"."My Table"'] '"My Column"' = "crate::types::MyType" ############################################################################################### # Configuration for migrations when executed using `sqlx::migrate!()` or through `sqlx-cli`. # # ### Note # A manually constructed [`Migrator`][crate::migrate::Migrator] will not be aware of these # configuration options. We recommend using `sqlx::migrate!()` instead. # # ### Warning: Potential Data Loss or Corruption! # Many of these options, if changed after migrations are set up, # can result in data loss or corruption of a production database # if the proper precautions are not taken. # # Be sure you know what you are doing and that you read all relevant documentation _thoroughly_. [migrate] # Override the name of the table used to track executed migrations. # # May be schema-qualified and/or contain quotes. Defaults to `_sqlx_migrations`. # # Potentially useful for multi-tenant databases. # # ### Warning: Potential Data Loss or Corruption! # Changing this option for a production database will likely result in data loss or corruption # as the migration machinery will no longer be aware of what migrations have been applied # and will attempt to re-run them. # # You should create the new table as a copy of the existing migrations table (with contents!), # and be sure all instances of your application have been migrated to the new # table before deleting the old one. table-name = "foo._sqlx_migrations" # Override the directory used for migrations files. # # Relative to the crate root for `sqlx::migrate!()`, or the current directory for `sqlx-cli`. migrations-dir = "foo/migrations" # Specify characters that should be ignored when hashing migrations. # # Any characters contained in the given set will be dropped when a migration is hashed. # # Defaults to an empty array (don't drop any characters). # # ### Warning: May Change Hashes for Existing Migrations # Changing the characters considered in hashing migrations will likely # change the output of the hash. # # This may require manual rectification for deployed databases. # ignored-chars = [] # Ignore Carriage Returns (`` | `\r`) # Note that the TOML format requires double-quoted strings to process escapes. # ignored-chars = ["\r"] # Ignore common whitespace characters (beware syntactically significant whitespace!) # Space, tab, CR, LF, zero-width non-breaking space (U+FEFF) # # U+FEFF is added by some editors as a magic number at the beginning of a text file indicating it is UTF-8 encoded, # where it is known as a byte-order mark (BOM): https://en.wikipedia.org/wiki/Byte_order_mark ignored-chars = [" ", "\t", "\r", "\n", "\uFEFF"] # Set default options for new migrations. [migrate.defaults] # Specify reversible migrations by default (for `sqlx migrate create`). # # Defaults to "inferred": uses the type of the last migration, or "simple" otherwise. migration-type = "reversible" # Specify simple (non-reversible) migrations by default. # migration-type = "simple" # Specify sequential versioning by default (for `sqlx migrate create`). # # Defaults to "inferred": guesses the versioning scheme from the latest migrations, # or "timestamp" otherwise. migration-versioning = "sequential" # Specify timestamp versioning by default. # migration-versioning = "timestamp" ================================================ FILE: sqlx-core/src/config/tests.rs ================================================ use crate::config::{self, Config}; use std::collections::BTreeSet; #[test] fn reference_parses_as_config() { let config: Config = toml::from_str(include_str!("reference.toml")) // The `Display` impl of `toml::Error` is *actually* more useful than `Debug` .unwrap_or_else(|e| panic!("expected reference.toml to parse as Config: {e}")); assert_common_config(&config.common); assert_drivers_config(&config.drivers); assert_macros_config(&config.macros); assert_migrate_config(&config.migrate); } fn assert_common_config(config: &config::common::Config) { assert_eq!(config.database_url_var.as_deref(), Some("FOO_DATABASE_URL")); } fn assert_drivers_config(config: &config::drivers::Config) { assert_eq!(config.sqlite.unsafe_load_extensions, ["uuid", "vsv"]); #[derive(Debug, Eq, PartialEq, serde::Deserialize)] #[serde(rename_all = "kebab-case")] struct TestExternalDriverConfig { foo: String, bar: bool, } assert_eq!( config.external.try_parse("").unwrap(), Some(TestExternalDriverConfig { foo: "foo".to_string(), bar: true }) ); } fn assert_macros_config(config: &config::macros::Config) { use config::macros::*; assert_eq!(config.preferred_crates.date_time, DateTimeCrate::Chrono); assert_eq!(config.preferred_crates.numeric, NumericCrate::RustDecimal); // Type overrides // Don't need to cover everything, just some important canaries. assert_eq!(config.type_override("UUID"), Some("crate::types::MyUuid")); assert_eq!(config.type_override("foo"), Some("crate::types::Foo")); assert_eq!(config.type_override(r#""Bar""#), Some("crate::types::Bar"),); assert_eq!( config.type_override(r#""Foo".bar"#), Some("crate::schema::foo::Bar"), ); assert_eq!( config.type_override(r#""Foo"."Bar""#), Some("crate::schema::foo::Bar"), ); // Column overrides assert_eq!( config.column_override("foo", "bar"), Some("crate::types::Bar"), ); assert_eq!( config.column_override("foo", r#""Bar""#), Some("crate::types::Bar"), ); assert_eq!( config.column_override(r#""Foo""#, "bar"), Some("crate::types::Bar"), ); assert_eq!( config.column_override(r#""Foo""#, r#""Bar""#), Some("crate::types::Bar"), ); assert_eq!( config.column_override("my_schema.my_table", "my_column"), Some("crate::types::MyType"), ); assert_eq!( config.column_override(r#""My Schema"."My Table""#, r#""My Column""#), Some("crate::types::MyType"), ); } fn assert_migrate_config(config: &config::migrate::Config) { use config::migrate::*; assert_eq!(config.table_name.as_deref(), Some("foo._sqlx_migrations")); assert_eq!(config.migrations_dir.as_deref(), Some("foo/migrations")); let ignored_chars = BTreeSet::from([' ', '\t', '\r', '\n', '\u{FEFF}']); assert_eq!(config.ignored_chars, ignored_chars); assert_eq!( config.defaults.migration_type, DefaultMigrationType::Reversible ); assert_eq!( config.defaults.migration_versioning, DefaultVersioning::Sequential ); } ================================================ FILE: sqlx-core/src/connection.rs ================================================ use crate::database::{Database, HasStatementCache}; use crate::error::Error; use crate::config; use crate::sql_str::SqlSafeStr; use crate::transaction::{Transaction, TransactionManager}; use futures_core::future::BoxFuture; use log::LevelFilter; use std::fmt::Debug; use std::future::Future; use std::str::FromStr; use std::time::Duration; use url::Url; /// Represents a single database connection. pub trait Connection: Send { type Database: Database; type Options: ConnectOptions; /// Explicitly close this database connection. /// /// This notifies the database server that the connection is closing so that it can /// free up any server-side resources in use. /// /// While connections can simply be dropped to clean up local resources, /// the `Drop` handler itself cannot notify the server that the connection is being closed /// because that may require I/O to send a termination message. That can result in a delay /// before the server learns that the connection is gone, usually from a TCP keepalive timeout. /// /// Creating and dropping many connections in short order without calling `.close()` may /// lead to errors from the database server because those senescent connections will still /// count against any connection limit or quota that is configured. /// /// Therefore it is recommended to call `.close()` on a connection when you are done using it /// and to `.await` the result to ensure the termination message is sent. fn close(self) -> impl Future> + Send + 'static; /// Immediately close the connection without sending a graceful shutdown. /// /// This should still at least send a TCP `FIN` frame to let the server know we're dying. #[doc(hidden)] fn close_hard(self) -> impl Future> + Send + 'static; /// Checks if a connection to the database is still valid. fn ping(&mut self) -> impl Future> + Send + '_; /// Begin a new transaction or establish a savepoint within the active transaction. /// /// Returns a [`Transaction`] for controlling and tracking the new transaction. fn begin( &mut self, ) -> impl Future, Error>> + Send + '_; /// Begin a new transaction with a custom statement. /// /// Returns a [`Transaction`] for controlling and tracking the new transaction. /// /// Returns an error if the connection is already in a transaction or if /// `statement` does not put the connection into a transaction. fn begin_with( &mut self, statement: impl SqlSafeStr, ) -> impl Future, Error>> + Send + '_ where Self: Sized, { Transaction::begin(self, Some(statement.into_sql_str())) } /// Returns `true` if the connection is currently in a transaction. /// /// # Note: Automatic Rollbacks May Not Be Counted /// Certain database errors (such as a serializable isolation failure) /// can cause automatic rollbacks of a transaction /// which may not be indicated in the return value of this method. #[inline] fn is_in_transaction(&self) -> bool { ::TransactionManager::get_transaction_depth(self) != 0 } /// Execute the function inside a transaction. /// /// If the function returns an error, the transaction will be rolled back. If it does not /// return an error, the transaction will be committed. /// /// # Example /// /// ```rust /// use sqlx::postgres::{PgConnection, PgRow}; /// use sqlx::Connection; /// /// # pub async fn _f(conn: &mut PgConnection) -> sqlx::Result> { /// conn.transaction(|txn| Box::pin(async move { /// sqlx::query("select * from ..").fetch_all(&mut **txn).await /// })).await /// # } /// ``` fn transaction<'a, F, R, E>( &'a mut self, callback: F, ) -> impl Future> + Send + 'a where for<'c> F: FnOnce(&'c mut Transaction<'_, Self::Database>) -> BoxFuture<'c, Result> + 'a + Send + Sync, Self: Sized, R: Send, E: From + Send, { async move { let mut transaction = self.begin().await?; let ret = callback(&mut transaction).await; match ret { Ok(ret) => { transaction.commit().await?; Ok(ret) } Err(err) => { transaction.rollback().await?; Err(err) } } } } /// The number of statements currently cached in the connection. fn cached_statements_size(&self) -> usize where Self::Database: HasStatementCache, { 0 } /// Removes all statements from the cache, closing them on the server if /// needed. fn clear_cached_statements(&mut self) -> impl Future> + Send + '_ where Self::Database: HasStatementCache, { async move { Ok(()) } } /// Restore any buffers in the connection to their default capacity, if possible. /// /// Sending a large query or receiving a resultset with many columns can cause the connection /// to allocate additional buffer space to fit the data which is retained afterwards in /// case it's needed again. This can give the outward appearance of a memory leak, but is /// in fact the intended behavior. /// /// Calling this method tells the connection to release that excess memory if it can, /// though be aware that calling this too often can cause unnecessary thrashing or /// fragmentation in the global allocator. If there's still data in the connection buffers /// (unlikely if the last query was run to completion) then it may need to be moved to /// allow the buffers to shrink. fn shrink_buffers(&mut self); #[doc(hidden)] fn flush(&mut self) -> impl Future> + Send + '_; #[doc(hidden)] fn should_flush(&self) -> bool; /// Establish a new database connection. /// /// A value of [`Options`][Self::Options] is parsed from the provided connection string. This parsing /// is database-specific. #[inline] fn connect(url: &str) -> impl Future> + Send + 'static where Self: Sized, { let options = url.parse(); async move { Self::connect_with(&options?).await } } /// Establish a new database connection with the provided options. fn connect_with( options: &Self::Options, ) -> impl Future> + Send + '_ where Self: Sized, { options.connect() } } #[derive(Clone, Debug)] #[non_exhaustive] pub struct LogSettings { pub statements_level: LevelFilter, pub slow_statements_level: LevelFilter, pub slow_statements_duration: Duration, } impl Default for LogSettings { fn default() -> Self { LogSettings { statements_level: LevelFilter::Debug, slow_statements_level: LevelFilter::Warn, slow_statements_duration: Duration::from_secs(1), } } } impl LogSettings { pub fn log_statements(&mut self, level: LevelFilter) { self.statements_level = level; } pub fn log_slow_statements(&mut self, level: LevelFilter, duration: Duration) { self.slow_statements_level = level; self.slow_statements_duration = duration; } } pub trait ConnectOptions: 'static + Send + Sync + FromStr + Debug + Clone { type Connection: Connection + ?Sized; /// Parse the `ConnectOptions` from a URL. fn from_url(url: &Url) -> Result; /// Get a connection URL that may be used to connect to the same database as this `ConnectOptions`. /// /// ### Note: Lossy /// Any flags or settings which do not have a representation in the URL format will be lost. /// They will fall back to their default settings when the URL is parsed. /// /// The only settings guaranteed to be preserved are: /// * Username /// * Password /// * Hostname /// * Port /// * Database name /// * Unix socket or SQLite database file path /// * SSL mode (if applicable) /// * SSL CA certificate path /// * SSL client certificate path /// * SSL client key path /// /// Additional settings are driver-specific. Refer to the source of a given implementation /// to see which options are preserved in the URL. /// /// ### Panics /// This defaults to `unimplemented!()`. /// /// Individual drivers should override this to implement the intended behavior. fn to_url_lossy(&self) -> Url { unimplemented!() } /// Establish a new database connection with the options specified by `self`. fn connect(&self) -> impl Future> + Send + '_ where Self::Connection: Sized; /// Log executed statements with the specified `level` fn log_statements(self, level: LevelFilter) -> Self; /// Log executed statements with a duration above the specified `duration` /// at the specified `level`. fn log_slow_statements(self, level: LevelFilter, duration: Duration) -> Self; /// Entirely disables statement logging (both slow and regular). fn disable_statement_logging(self) -> Self { self.log_statements(LevelFilter::Off) .log_slow_statements(LevelFilter::Off, Duration::default()) } #[doc(hidden)] fn __unstable_apply_driver_config( self, _config: &config::drivers::Config, ) -> crate::Result { Ok(self) } } ================================================ FILE: sqlx-core/src/database.rs ================================================ //! Traits to represent a database driver. //! //! # Support //! //! ## Tier 1 //! //! Tier 1 support can be thought of as "guaranteed to work". Automated testing is setup to //! ensure a high level of stability and functionality. //! //! | Database | Version | Driver | //! | - | - | - | //! | [MariaDB] | 10.1+ | [`mysql`] | //! | [Microsoft SQL Server] | 2019 | [`mssql`] (Pending a full rewrite) | //! | [MySQL] | 5.6, 5.7, 8.0 | [`mysql`] | //! | [PostgreSQL] | 13+ | [`postgres`] | //! | [SQLite] | 3.20.1+ | [`sqlite`] | //! //! [MariaDB]: https://mariadb.com/ //! [MySQL]: https://www.mysql.com/ //! [Microsoft SQL Server]: https://www.microsoft.com/en-us/sql-server //! [PostgreSQL]: https://www.postgresql.org/ //! [SQLite]: https://www.sqlite.org/ //! //! [`mysql`]: crate::mysql //! [`postgres`]: crate::postgres //! [`mssql`]: crate::mssql //! [`sqlite`]: crate::sqlite //! //! ## Tier 2 //! //! Tier 2 support can be thought as "should work". No specific automated testing is done, //! at this time, but there are efforts to ensure compatibility. Tier 2 support also includes //! database distributions that provide protocols that closely match a database from Tier 1. //! //! _No databases are in tier 2 at this time._ //! //! # `Any` //! //! Selecting a database driver is, by default, a compile-time decision. SQLx is designed this way //! to take full advantage of the performance and type safety made available by Rust. //! //! We recognize that you may wish to make a runtime decision to decide the database driver. The //! [`Any`](crate::any) driver is provided for that purpose. //! //! ## Example //! //! ```rust,ignore //! // connect to SQLite //! let conn = AnyConnection::connect("sqlite://file.db").await?; //! //! // connect to Postgres, no code change //! // required, decided by the scheme of the URL //! let conn = AnyConnection::connect("postgres://localhost/sqlx").await?; //! ``` use std::fmt::Debug; use crate::arguments::Arguments; use crate::column::Column; use crate::connection::Connection; use crate::row::Row; use crate::statement::Statement; use crate::transaction::TransactionManager; use crate::type_info::TypeInfo; use crate::value::{Value, ValueRef}; /// A database driver. /// /// This trait encapsulates a complete set of traits that implement a driver for a /// specific database (e.g., MySQL, PostgreSQL). pub trait Database: 'static + Sized + Send + Debug { /// The concrete `Connection` implementation for this database. type Connection: Connection; /// The concrete `TransactionManager` implementation for this database. type TransactionManager: TransactionManager; /// The concrete `Row` implementation for this database. type Row: Row; /// The concrete `QueryResult` implementation for this database. type QueryResult: 'static + Sized + Send + Sync + Default + Extend; /// The concrete `Column` implementation for this database. type Column: Column; /// The concrete `TypeInfo` implementation for this database. type TypeInfo: TypeInfo; /// The concrete type used to hold an owned copy of the not-yet-decoded value that was /// received from the database. type Value: Value + 'static; /// The concrete type used to hold a reference to the not-yet-decoded value that has just been /// received from the database. type ValueRef<'r>: ValueRef<'r, Database = Self>; /// The concrete `Arguments` implementation for this database. type Arguments: Arguments; /// The concrete type used as a buffer for arguments while encoding. type ArgumentBuffer; /// The concrete `Statement` implementation for this database. type Statement: Statement; /// The display name for this database driver. const NAME: &'static str; /// The schemes for database URLs that should match this driver. const URL_SCHEMES: &'static [&'static str]; } /// A [`Database`] that maintains a client-side cache of prepared statements. pub trait HasStatementCache {} ================================================ FILE: sqlx-core/src/decode.rs ================================================ //! Provides [`Decode`] for decoding values from the database. use std::borrow::Cow; use std::rc::Rc; use std::sync::Arc; use crate::database::Database; use crate::error::BoxDynError; use crate::value::ValueRef; /// A type that can be decoded from the database. /// /// ## How can I implement `Decode`? /// /// A manual implementation of `Decode` can be useful when adding support for /// types externally to SQLx. /// /// The following showcases how to implement `Decode` to be generic over [`Database`]. The /// implementation can be marginally simpler if you remove the `DB` type parameter and explicitly /// use the concrete [`ValueRef`](Database::ValueRef) and [`TypeInfo`](Database::TypeInfo) types. /// /// ```rust /// # use sqlx_core::database::{Database}; /// # use sqlx_core::decode::Decode; /// # use sqlx_core::types::Type; /// # use std::error::Error; /// # /// struct MyType; /// /// # impl Type for MyType { /// # fn type_info() -> DB::TypeInfo { todo!() } /// # } /// # /// # impl std::str::FromStr for MyType { /// # type Err = sqlx_core::error::Error; /// # fn from_str(s: &str) -> Result { todo!() } /// # } /// # /// // DB is the database driver /// // `'r` is the lifetime of the `Row` being decoded /// impl<'r, DB: Database> Decode<'r, DB> for MyType /// where /// // we want to delegate some of the work to string decoding so let's make sure strings /// // are supported by the database /// &'r str: Decode<'r, DB> /// { /// fn decode( /// value: ::ValueRef<'r>, /// ) -> Result> { /// // the interface of ValueRef is largely unstable at the moment /// // so this is not directly implementable /// /// // however, you can delegate to a type that matches the format of the type you want /// // to decode (such as a UTF-8 string) /// /// let value = <&str as Decode>::decode(value)?; /// /// // now you can parse this into your type (assuming there is a `FromStr`) /// /// Ok(value.parse()?) /// } /// } /// ``` pub trait Decode<'r, DB: Database>: Sized { /// Decode a new value of this type using a raw value from the database. fn decode(value: ::ValueRef<'r>) -> Result; } // implement `Decode` for Option for all SQL types impl<'r, DB, T> Decode<'r, DB> for Option where DB: Database, T: Decode<'r, DB>, { fn decode(value: ::ValueRef<'r>) -> Result { if value.is_null() { Ok(None) } else { Ok(Some(T::decode(value)?)) } } } macro_rules! impl_decode_for_smartpointer { ($smart_pointer:tt) => { impl<'r, DB, T> Decode<'r, DB> for $smart_pointer where DB: Database, T: Decode<'r, DB>, { fn decode(value: ::ValueRef<'r>) -> Result { Ok(Self::new(T::decode(value)?)) } } impl<'r, DB> Decode<'r, DB> for $smart_pointer where DB: Database, &'r str: Decode<'r, DB>, { fn decode(value: ::ValueRef<'r>) -> Result { let ref_str = <&str as Decode>::decode(value)?; Ok(ref_str.into()) } } impl<'r, DB> Decode<'r, DB> for $smart_pointer<[u8]> where DB: Database, Vec: Decode<'r, DB>, { fn decode(value: ::ValueRef<'r>) -> Result { // The `Postgres` implementation requires this to be decoded as an owned value because // bytes can be sent in text format. let bytes = as Decode>::decode(value)?; Ok(bytes.into()) } } }; } impl_decode_for_smartpointer!(Arc); impl_decode_for_smartpointer!(Box); impl_decode_for_smartpointer!(Rc); // implement `Decode` for Cow for all SQL types impl<'r, DB, T> Decode<'r, DB> for Cow<'_, T> where DB: Database, // `ToOwned` is required here to satisfy `Cow` T: ToOwned + ?Sized, ::Owned: Decode<'r, DB>, { fn decode(value: ::ValueRef<'r>) -> Result { // See https://github.com/launchbadge/sqlx/pull/3674#discussion_r2008611502 for more info // about why decoding to a `Cow::Owned` was chosen. <::Owned as Decode>::decode(value).map(Cow::Owned) } } ================================================ FILE: sqlx-core/src/describe.rs ================================================ use crate::database::Database; use either::Either; use std::convert::identity; /// Provides extended information on a statement. /// /// Returned from [`Executor::describe`]. /// /// The query macros (e.g., `query!`, `query_as!`, etc.) use the information here to validate /// output and parameter types; and, generate an anonymous record. #[derive(Debug)] #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr( feature = "offline", serde(bound( serialize = "DB::TypeInfo: serde::Serialize, DB::Column: serde::Serialize", deserialize = "DB::TypeInfo: serde::de::DeserializeOwned, DB::Column: serde::de::DeserializeOwned", )) )] #[doc(hidden)] pub struct Describe { pub columns: Vec, pub parameters: Option, usize>>, pub nullable: Vec>, } impl Describe { /// Gets all columns in this statement. pub fn columns(&self) -> &[DB::Column] { &self.columns } /// Gets the column information at `index`. /// /// Panics if `index` is out of bounds. pub fn column(&self, index: usize) -> &DB::Column { &self.columns[index] } /// Gets the available information for parameters in this statement. /// /// Some drivers may return more or less than others. As an example, **PostgreSQL** will /// return `Some(Either::Left(_))` with a full list of type information for each parameter. /// However, **MSSQL** will return `None` as there is no information available. pub fn parameters(&self) -> Option> { self.parameters.as_ref().map(|p| match p { Either::Left(params) => Either::Left(&**params), Either::Right(count) => Either::Right(*count), }) } /// Gets whether a column may be `NULL`, if this information is available. pub fn nullable(&self, column: usize) -> Option { self.nullable.get(column).copied().and_then(identity) } } #[cfg(feature = "any")] impl Describe { #[doc(hidden)] pub fn try_into_any(self) -> crate::Result> where crate::any::AnyColumn: for<'a> TryFrom<&'a DB::Column, Error = crate::Error>, crate::any::AnyTypeInfo: for<'a> TryFrom<&'a DB::TypeInfo, Error = crate::Error>, { use crate::any::AnyTypeInfo; let columns = self .columns .iter() .map(crate::any::AnyColumn::try_from) .collect::, _>>()?; let parameters = match self.parameters { Some(Either::Left(parameters)) => Some(Either::Left( parameters .iter() .enumerate() .map(|(i, type_info)| { AnyTypeInfo::try_from(type_info).map_err(|_| { crate::Error::AnyDriverError( format!( "Any driver does not support type {type_info} of parameter {i}" ) .into(), ) }) }) .collect::, _>>()?, )), Some(Either::Right(count)) => Some(Either::Right(count)), None => None, }; Ok(Describe { columns, parameters, nullable: self.nullable, }) } } ================================================ FILE: sqlx-core/src/encode.rs ================================================ //! Provides [`Encode`] for encoding values for the database. use std::borrow::Cow; use std::mem; use std::rc::Rc; use std::sync::Arc; use crate::database::Database; use crate::error::BoxDynError; /// The return type of [Encode::encode]. #[must_use] pub enum IsNull { /// The value is null; no data was written. Yes, /// The value is not null. /// /// This does not mean that data was written. No, } impl IsNull { pub fn is_null(&self) -> bool { matches!(self, IsNull::Yes) } } /// Encode a single value to be sent to the database. pub trait Encode<'q, DB: Database> { /// Writes the value of `self` into `buf` in the expected format for the database. fn encode(self, buf: &mut ::ArgumentBuffer) -> Result where Self: Sized, { self.encode_by_ref(buf) } /// Writes the value of `self` into `buf` without moving `self`. /// /// Where possible, make use of `encode` instead as it can take advantage of re-using /// memory. fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result; fn produces(&self) -> Option { // `produces` is inherently a hook to allow database drivers to produce value-dependent // type information; if the driver doesn't need this, it can leave this as `None` None } #[inline] fn size_hint(&self) -> usize { mem::size_of_val(self) } } impl<'q, T, DB: Database> Encode<'q, DB> for &'_ T where T: Encode<'q, DB>, { #[inline] fn encode(self, buf: &mut ::ArgumentBuffer) -> Result { >::encode_by_ref(self, buf) } #[inline] fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { <&T as Encode>::encode(self, buf) } #[inline] fn produces(&self) -> Option { (**self).produces() } #[inline] fn size_hint(&self) -> usize { (**self).size_hint() } } #[macro_export] macro_rules! impl_encode_for_option { ($DB:ident) => { impl<'q, T> $crate::encode::Encode<'q, $DB> for Option where T: $crate::encode::Encode<'q, $DB> + $crate::types::Type<$DB> + 'q, { #[inline] fn produces(&self) -> Option<<$DB as $crate::database::Database>::TypeInfo> { if let Some(v) = self { v.produces() } else { T::type_info().into() } } #[inline] fn encode( self, buf: &mut <$DB as $crate::database::Database>::ArgumentBuffer, ) -> Result<$crate::encode::IsNull, $crate::error::BoxDynError> { if let Some(v) = self { v.encode(buf) } else { Ok($crate::encode::IsNull::Yes) } } #[inline] fn encode_by_ref( &self, buf: &mut <$DB as $crate::database::Database>::ArgumentBuffer, ) -> Result<$crate::encode::IsNull, $crate::error::BoxDynError> { if let Some(v) = self { v.encode_by_ref(buf) } else { Ok($crate::encode::IsNull::Yes) } } #[inline] fn size_hint(&self) -> usize { self.as_ref().map_or(0, $crate::encode::Encode::size_hint) } } }; } macro_rules! impl_encode_for_smartpointer { ($smart_pointer:ty) => { impl<'q, T, DB: Database> Encode<'q, DB> for $smart_pointer where T: Encode<'q, DB>, { #[inline] fn encode( self, buf: &mut ::ArgumentBuffer, ) -> Result { >::encode_by_ref(self.as_ref(), buf) } #[inline] fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { <&T as Encode>::encode(self, buf) } #[inline] fn produces(&self) -> Option { (**self).produces() } #[inline] fn size_hint(&self) -> usize { (**self).size_hint() } } }; } impl_encode_for_smartpointer!(Arc); impl_encode_for_smartpointer!(Box); impl_encode_for_smartpointer!(Rc); impl<'q, T, DB: Database> Encode<'q, DB> for Cow<'q, T> where T: Encode<'q, DB>, T: ToOwned, { #[inline] fn encode(self, buf: &mut ::ArgumentBuffer) -> Result { <&T as Encode>::encode_by_ref(&self.as_ref(), buf) } #[inline] fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { <&T as Encode>::encode_by_ref(&self.as_ref(), buf) } #[inline] fn produces(&self) -> Option { <&T as Encode>::produces(&self.as_ref()) } #[inline] fn size_hint(&self) -> usize { <&T as Encode>::size_hint(&self.as_ref()) } } #[macro_export] macro_rules! forward_encode_impl { ($for_type:ty, $forward_to:ty, $db:ident) => { impl<'q> Encode<'q, $db> for $for_type { fn encode_by_ref( &self, buf: &mut <$db as sqlx_core::database::Database>::ArgumentBuffer, ) -> Result { <$forward_to as Encode<$db>>::encode(self.as_ref(), buf) } } }; } ================================================ FILE: sqlx-core/src/error.rs ================================================ //! Types for working with errors produced by SQLx. use std::any::type_name; use std::borrow::Cow; use std::error::Error as StdError; use std::fmt::Display; use std::io; use crate::database::Database; use crate::type_info::TypeInfo; use crate::types::Type; /// A specialized `Result` type for SQLx. pub type Result = ::std::result::Result; // Convenience type alias for usage within SQLx. // Do not make this type public. pub type BoxDynError = Box; /// An unexpected `NULL` was encountered during decoding. /// /// Returned from [`Row::get`](crate::row::Row::get) if the value from the database is `NULL`, /// and you are not decoding into an `Option`. #[derive(thiserror::Error, Debug)] #[error("unexpected null; try decoding as an `Option`")] pub struct UnexpectedNullError; /// Represents all the ways a method can fail within SQLx. #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum Error { /// Error occurred while parsing a connection string. #[error("error with configuration: {0}")] Configuration(#[source] BoxDynError), /// One or more of the arguments to the called function was invalid. /// /// The string contains more information. #[error("{0}")] InvalidArgument(String), /// Error returned from the database. #[error("error returned from database: {0}")] Database(#[source] Box), /// Error communicating with the database backend. #[error("error communicating with database: {0}")] Io(#[from] io::Error), /// Error occurred while attempting to establish a TLS connection. #[error("error occurred while attempting to establish a TLS connection: {0}")] Tls(#[source] BoxDynError), /// Unexpected or invalid data encountered while communicating with the database. /// /// This should indicate there is a programming error in a SQLx driver or there /// is something corrupted with the connection to the database itself. #[error("encountered unexpected or invalid data: {0}")] Protocol(String), /// No rows returned by a query that expected to return at least one row. #[error("no rows returned by a query that expected to return at least one row")] RowNotFound, /// Type in query doesn't exist. Likely due to typo or missing user type. #[error("type named {type_name} not found")] TypeNotFound { type_name: String }, /// Column index was out of bounds. #[error("column index out of bounds: the len is {len}, but the index is {index}")] ColumnIndexOutOfBounds { index: usize, len: usize }, /// No column found for the given name. #[error("no column found for name: {0}")] ColumnNotFound(String), /// Error occurred while decoding a value from a specific column. #[error("error occurred while decoding column {index}: {source}")] ColumnDecode { index: String, #[source] source: BoxDynError, }, /// Error occurred while encoding a value. #[error("error occurred while encoding a value: {0}")] Encode(#[source] BoxDynError), /// Error occurred while decoding a value. #[error("error occurred while decoding: {0}")] Decode(#[source] BoxDynError), /// Error occurred within the `Any` driver mapping to/from the native driver. #[error("error in Any driver mapping: {0}")] AnyDriverError(#[source] BoxDynError), /// A [`Pool::acquire`] timed out due to connections not becoming available or /// because another task encountered too many errors while trying to open a new connection. /// /// [`Pool::acquire`]: crate::pool::Pool::acquire #[error("pool timed out while waiting for an open connection")] PoolTimedOut, /// [`Pool::close`] was called while we were waiting in [`Pool::acquire`]. /// /// [`Pool::acquire`]: crate::pool::Pool::acquire /// [`Pool::close`]: crate::pool::Pool::close #[error("attempted to acquire a connection on a closed pool")] PoolClosed, /// A background worker has crashed. #[error("attempted to communicate with a crashed background worker")] WorkerCrashed, #[cfg(feature = "migrate")] #[error("{0}")] Migrate(#[source] Box), #[error("attempted to call begin_with at non-zero transaction depth")] InvalidSavePointStatement, #[error("got unexpected connection status after attempting to begin transaction")] BeginFailed, // Not returned in normal operation. /// Error occurred while reading configuration file #[doc(hidden)] #[error("error reading configuration file: {0}")] ConfigFile(#[from] crate::config::ConfigError), } impl StdError for Box {} impl Error { pub fn into_database_error(self) -> Option> { match self { Error::Database(err) => Some(err), _ => None, } } pub fn as_database_error(&self) -> Option<&(dyn DatabaseError + 'static)> { match self { Error::Database(err) => Some(&**err), _ => None, } } #[doc(hidden)] #[inline] pub fn protocol(err: impl Display) -> Self { Error::Protocol(err.to_string()) } #[doc(hidden)] #[inline] pub fn database(err: impl DatabaseError) -> Self { Error::Database(Box::new(err)) } #[doc(hidden)] #[inline] pub fn config(err: impl StdError + Send + Sync + 'static) -> Self { Error::Configuration(err.into()) } pub(crate) fn tls(err: impl Into>) -> Self { Error::Tls(err.into()) } #[doc(hidden)] #[inline] pub fn decode(err: impl Into>) -> Self { Error::Decode(err.into()) } } pub fn mismatched_types>(ty: &DB::TypeInfo) -> BoxDynError { // TODO: `#name` only produces `TINYINT` but perhaps we want to show `TINYINT(1)` format!( "mismatched types; Rust type `{}` (as SQL type `{}`) is not compatible with SQL type `{}`", type_name::(), T::type_info().name(), ty.name() ) .into() } /// The error kind. /// /// This enum is to be used to identify frequent errors that can be handled by the program. /// Although it currently only supports constraint violations, the type may grow in the future. #[derive(Debug, PartialEq, Eq)] #[non_exhaustive] pub enum ErrorKind { /// Unique/primary key constraint violation. UniqueViolation, /// Foreign key constraint violation. ForeignKeyViolation, /// Not-null constraint violation. NotNullViolation, /// Check constraint violation. CheckViolation, /// Exclusion constraint violation. ExclusionViolation, /// An unmapped error. Other, } /// An error that was returned from the database. pub trait DatabaseError: 'static + Send + Sync + StdError { /// The primary, human-readable error message. fn message(&self) -> &str; /// The (SQLSTATE) code for the error. fn code(&self) -> Option> { None } #[doc(hidden)] fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static); #[doc(hidden)] fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static); #[doc(hidden)] fn into_error(self: Box) -> Box; #[doc(hidden)] fn is_transient_in_connect_phase(&self) -> bool { false } /// Returns the name of the constraint that triggered the error, if applicable. /// If the error was caused by a conflict of a unique index, this will be the index name. /// /// ### Note /// Currently only populated by the Postgres driver. fn constraint(&self) -> Option<&str> { None } /// Returns the name of the table that was affected by the error, if applicable. /// /// ### Note /// Currently only populated by the Postgres driver. fn table(&self) -> Option<&str> { None } /// Returns the kind of the error, if supported. /// /// ### Note /// Not all back-ends behave the same when reporting the error code. fn kind(&self) -> ErrorKind; /// Returns whether the error kind is a violation of a unique/primary key constraint. fn is_unique_violation(&self) -> bool { matches!(self.kind(), ErrorKind::UniqueViolation) } /// Returns whether the error kind is a violation of a foreign key. fn is_foreign_key_violation(&self) -> bool { matches!(self.kind(), ErrorKind::ForeignKeyViolation) } /// Returns whether the error kind is a violation of a check. fn is_check_violation(&self) -> bool { matches!(self.kind(), ErrorKind::CheckViolation) } } impl dyn DatabaseError { /// Downcast a reference to this generic database error to a specific /// database error type. /// /// # Panics /// /// Panics if the database error type is not `E`. This is a deliberate contrast from /// `Error::downcast_ref` which returns `Option<&E>`. In normal usage, you should know the /// specific error type. In other cases, use `try_downcast_ref`. pub fn downcast_ref(&self) -> &E { self.try_downcast_ref().unwrap_or_else(|| { panic!("downcast to wrong DatabaseError type; original error: {self}") }) } /// Downcast this generic database error to a specific database error type. /// /// # Panics /// /// Panics if the database error type is not `E`. This is a deliberate contrast from /// `Error::downcast` which returns `Option`. In normal usage, you should know the /// specific error type. In other cases, use `try_downcast`. pub fn downcast(self: Box) -> Box { self.try_downcast() .unwrap_or_else(|e| panic!("downcast to wrong DatabaseError type; original error: {e}")) } /// Downcast a reference to this generic database error to a specific /// database error type. #[inline] pub fn try_downcast_ref(&self) -> Option<&E> { self.as_error().downcast_ref() } /// Downcast this generic database error to a specific database error type. #[inline] pub fn try_downcast(self: Box) -> Result, Box> { if self.as_error().is::() { Ok(self.into_error().downcast().unwrap()) } else { Err(self) } } } impl From for Error where E: DatabaseError, { #[inline] fn from(error: E) -> Self { Error::Database(Box::new(error)) } } #[cfg(feature = "migrate")] impl From for Error { #[inline] fn from(error: crate::migrate::MigrateError) -> Self { Error::Migrate(Box::new(error)) } } /// Format an error message as a `Protocol` error #[macro_export] macro_rules! err_protocol { ($($fmt_args:tt)*) => { $crate::error::Error::Protocol( format!( "{} ({}:{})", // Note: the format string needs to be unmodified (e.g. by `concat!()`) // for implicit formatting arguments to work format_args!($($fmt_args)*), module_path!(), line!(), ) ) }; } ================================================ FILE: sqlx-core/src/executor.rs ================================================ use crate::database::Database; use crate::error::{BoxDynError, Error}; use crate::sql_str::{SqlSafeStr, SqlStr}; use either::Either; use futures_core::future::BoxFuture; use futures_core::stream::BoxStream; use futures_util::{FutureExt, StreamExt, TryFutureExt, TryStreamExt}; use std::{fmt::Debug, future}; /// A type that contains or can provide a database /// connection to use for executing queries against the database. /// /// No guarantees are provided that successive queries run on the same /// physical database connection. /// /// A [`Connection`](crate::connection::Connection) is an `Executor` that guarantees that /// successive queries are ran on the same physical database connection. /// /// Implemented for the following: /// /// * [`&Pool`](super::pool::Pool) /// * [`&mut Connection`](super::connection::Connection) /// /// The [`Executor`] impls for [`Transaction`](crate::transaction::Transaction) /// and [`PoolConnection`](crate::pool::PoolConnection) have been deleted because they /// cannot exist in the new crate architecture without rewriting the Executor trait entirely. /// To fix this breakage, simply add a dereference where an impl [`Executor`] is expected, as /// they both dereference to the inner connection type which will still implement it: /// * `&mut transaction` -> `&mut *transaction` /// * `&mut connection` -> `&mut *connection` /// pub trait Executor<'c>: Send + Debug + Sized { type Database: Database; /// Execute the query and return the total number of rows affected. fn execute<'e, 'q: 'e, E>( self, query: E, ) -> BoxFuture<'e, Result<::QueryResult, Error>> where 'c: 'e, E: 'q + Execute<'q, Self::Database>, { self.execute_many(query).try_collect().boxed() } /// Execute multiple queries and return the rows affected from each query, in a stream. fn execute_many<'e, 'q: 'e, E>( self, query: E, ) -> BoxStream<'e, Result<::QueryResult, Error>> where 'c: 'e, E: 'q + Execute<'q, Self::Database>, { self.fetch_many(query) .try_filter_map(|step| async move { Ok(match step { Either::Left(rows) => Some(rows), Either::Right(_) => None, }) }) .boxed() } /// Execute the query and return the generated results as a stream. fn fetch<'e, 'q: 'e, E>( self, query: E, ) -> BoxStream<'e, Result<::Row, Error>> where 'c: 'e, E: 'q + Execute<'q, Self::Database>, { self.fetch_many(query) .try_filter_map(|step| async move { Ok(match step { Either::Left(_) => None, Either::Right(row) => Some(row), }) }) .boxed() } /// Execute multiple queries and return the generated results as a stream /// from each query, in a stream. fn fetch_many<'e, 'q: 'e, E>( self, query: E, ) -> BoxStream< 'e, Result< Either<::QueryResult, ::Row>, Error, >, > where 'c: 'e, E: 'q + Execute<'q, Self::Database>; /// Execute the query and return all the generated results, collected into a [`Vec`]. fn fetch_all<'e, 'q: 'e, E>( self, query: E, ) -> BoxFuture<'e, Result::Row>, Error>> where 'c: 'e, E: 'q + Execute<'q, Self::Database>, { self.fetch(query).try_collect().boxed() } /// Execute the query and returns exactly one row. fn fetch_one<'e, 'q: 'e, E>( self, query: E, ) -> BoxFuture<'e, Result<::Row, Error>> where 'c: 'e, E: 'q + Execute<'q, Self::Database>, { self.fetch_optional(query) .and_then(|row| { future::ready(match row { Some(row) => Ok(row), None => Err(Error::RowNotFound), }) }) .boxed() } /// Execute the query and returns at most one row. fn fetch_optional<'e, 'q: 'e, E>( self, query: E, ) -> BoxFuture<'e, Result::Row>, Error>> where 'c: 'e, E: 'q + Execute<'q, Self::Database>; /// Prepare the SQL query to inspect the type information of its parameters /// and results. /// /// Be advised that when using the `query`, `query_as`, or `query_scalar` functions, the query /// is transparently prepared and executed. /// /// This explicit API is provided to allow access to the statement metadata available after /// it prepared but before the first row is returned. #[inline] fn prepare<'e>( self, query: SqlStr, ) -> BoxFuture<'e, Result<::Statement, Error>> where 'c: 'e, { self.prepare_with(query, &[]) } /// Prepare the SQL query, with parameter type information, to inspect the /// type information about its parameters and results. /// /// Only some database drivers (PostgreSQL, MSSQL) can take advantage of /// this extra information to influence parameter type inference. fn prepare_with<'e>( self, sql: SqlStr, parameters: &'e [::TypeInfo], ) -> BoxFuture<'e, Result<::Statement, Error>> where 'c: 'e; /// Describe the SQL query and return type information about its parameters /// and results. /// /// This is used by compile-time verification in the query macros to /// power their type inference. #[doc(hidden)] #[cfg(feature = "offline")] fn describe<'e>( self, sql: SqlStr, ) -> BoxFuture<'e, Result, Error>> where 'c: 'e; } /// A type that may be executed against a database connection. /// /// Implemented for the following: /// /// * [`&str`](std::str) /// * [`Query`](super::query::Query) /// pub trait Execute<'q, DB: Database>: Send + Sized { /// Gets the SQL that will be executed. fn sql(self) -> SqlStr; /// Gets the previously cached statement, if available. fn statement(&self) -> Option<&DB::Statement>; /// Returns the arguments to be bound against the query string. /// /// Returning `Ok(None)` for `Arguments` indicates to use a "simple" query protocol and to not /// prepare the query. Returning `Ok(Some(Default::default()))` is an empty arguments object that /// will be prepared (and cached) before execution. /// /// Returns `Err` if encoding any of the arguments failed. fn take_arguments(&mut self) -> Result::Arguments>, BoxDynError>; /// Returns `true` if the statement should be cached. fn persistent(&self) -> bool; } impl Execute<'_, DB> for T where T: SqlSafeStr + Send, { #[inline] fn sql(self) -> SqlStr { self.into_sql_str() } #[inline] fn statement(&self) -> Option<&DB::Statement> { None } #[inline] fn take_arguments(&mut self) -> Result::Arguments>, BoxDynError> { Ok(None) } #[inline] fn persistent(&self) -> bool { true } } impl Execute<'_, DB> for (T, Option<::Arguments>) where T: SqlSafeStr + Send, { #[inline] fn sql(self) -> SqlStr { self.0.into_sql_str() } #[inline] fn statement(&self) -> Option<&DB::Statement> { None } #[inline] fn take_arguments(&mut self) -> Result::Arguments>, BoxDynError> { Ok(self.1.take()) } #[inline] fn persistent(&self) -> bool { true } } ================================================ FILE: sqlx-core/src/ext/async_stream.rs ================================================ //! A minimalist clone of the `async-stream` crate in 100% safe code, without proc macros. //! //! This was created initially to get around some weird compiler errors we were getting with //! `async-stream`, and now it'd just be more work to replace. use std::future::{self, Future}; use std::pin::Pin; use std::sync::{Arc, Mutex}; use std::task::{Context, Poll}; use futures_core::future::BoxFuture; use futures_core::stream::Stream; use futures_core::FusedFuture; use futures_util::future::Fuse; use futures_util::FutureExt; use crate::error::Error; pub struct TryAsyncStream<'a, T> { yielder: Yielder, future: Fuse>>, } impl<'a, T> TryAsyncStream<'a, T> { pub fn new(f: F) -> Self where F: FnOnce(Yielder) -> Fut + Send, Fut: 'a + Future> + Send, T: 'a + Send, { let yielder = Yielder::new(); let future = f(yielder.duplicate()).boxed().fuse(); Self { future, yielder } } } pub struct Yielder { // This mutex should never have any contention in normal operation. // We're just using it because `Rc>>` would not be `Send`. value: Arc>>, } impl Yielder { fn new() -> Self { Yielder { value: Arc::new(Mutex::new(None)), } } // Don't want to expose a `Clone` impl fn duplicate(&self) -> Self { Yielder { value: self.value.clone(), } } /// NOTE: may deadlock the task if called from outside the future passed to `TryAsyncStream`. pub async fn r#yield(&self, val: T) { let replaced = self .value .lock() .expect("BUG: panicked while holding a lock") .replace(val); debug_assert!( replaced.is_none(), "BUG: previously yielded value not taken" ); let mut yielded = false; // Allows the generating future to suspend its execution without changing the task priority, // which would happen with `tokio::task::yield_now()`. // // Note that because this has no way to schedule a wakeup, this could deadlock the task // if called in the wrong place. future::poll_fn(|_cx| { if !yielded { yielded = true; Poll::Pending } else { Poll::Ready(()) } }) .await } fn take(&self) -> Option { self.value .lock() .expect("BUG: panicked while holding a lock") .take() } } impl Stream for TryAsyncStream<'_, T> { type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { if self.future.is_terminated() { return Poll::Ready(None); } match self.future.poll_unpin(cx) { Poll::Ready(Ok(())) => { // Future returned without yielding another value, // or else it would have returned `Pending` instead. Poll::Ready(None) } Poll::Ready(Err(e)) => Poll::Ready(Some(Err(e))), Poll::Pending => self .yielder .take() .map_or(Poll::Pending, |val| Poll::Ready(Some(Ok(val)))), } } } #[macro_export] macro_rules! try_stream { ($($block:tt)*) => { $crate::ext::async_stream::TryAsyncStream::new(move |yielder| ::tracing::Instrument::in_current_span(async move { // Anti-footgun: effectively pins `yielder` to this future to prevent any accidental // move to another task, which could deadlock. let yielder = &yielder; macro_rules! r#yield { ($v:expr) => {{ yielder.r#yield($v).await; }} } $($block)* })) } } ================================================ FILE: sqlx-core/src/ext/mod.rs ================================================ pub mod ustr; #[macro_use] pub mod async_stream; ================================================ FILE: sqlx-core/src/ext/ustr.rs ================================================ use std::borrow::Borrow; use std::fmt::{self, Debug, Display, Formatter}; use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::sync::Arc; // U meaning micro // a micro-string is either a reference-counted string or a static string // this guarantees these are cheap to clone everywhere #[derive(Clone, Eq)] pub enum UStr { Static(&'static str), Shared(Arc), } impl UStr { pub fn new(s: &str) -> Self { UStr::Shared(Arc::from(s.to_owned())) } /// Apply [str::strip_prefix], without copying if possible. pub fn strip_prefix(this: &Self, prefix: &str) -> Option { match this { UStr::Static(s) => s.strip_prefix(prefix).map(Self::Static), UStr::Shared(s) => s.strip_prefix(prefix).map(|s| Self::Shared(s.into())), } } } impl Deref for UStr { type Target = str; #[inline] fn deref(&self) -> &str { match self { UStr::Static(s) => s, UStr::Shared(s) => s, } } } impl Hash for UStr { #[inline] fn hash(&self, state: &mut H) { // Forward the hash to the string representation of this // A derive(Hash) encodes the enum discriminant (**self).hash(state); } } impl Borrow for UStr { #[inline] fn borrow(&self) -> &str { self } } impl PartialEq for UStr { fn eq(&self, other: &UStr) -> bool { (**self).eq(&**other) } } impl From<&'static str> for UStr { #[inline] fn from(s: &'static str) -> Self { UStr::Static(s) } } impl<'a> From<&'a UStr> for UStr { fn from(value: &'a UStr) -> Self { value.clone() } } impl From for UStr { #[inline] fn from(s: String) -> Self { UStr::Shared(s.into()) } } impl Debug for UStr { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.pad(self) } } impl Display for UStr { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.pad(self) } } // manual impls because otherwise things get a little screwy with lifetimes #[cfg(feature = "offline")] impl<'de> serde::Deserialize<'de> for UStr { fn deserialize(deserializer: D) -> Result>::Error> where D: serde::Deserializer<'de>, { Ok(String::deserialize(deserializer)?.into()) } } #[cfg(feature = "offline")] impl serde::Serialize for UStr { fn serialize( &self, serializer: S, ) -> Result<::Ok, ::Error> where S: serde::Serializer, { serializer.serialize_str(self) } } ================================================ FILE: sqlx-core/src/from_row.rs ================================================ use crate::{error::Error, row::Row}; /// A record that can be built from a row returned by the database. /// /// In order to use [`query_as`](crate::query_as) the output type must implement `FromRow`. /// /// ## Derivable /// /// This trait can be derived by SQLx for any struct. The generated implementation /// will consist of a sequence of calls to [`Row::try_get`] using the name from each /// struct field. /// /// ```rust,ignore /// #[derive(sqlx::FromRow)] /// struct User { /// id: i32, /// name: String, /// } /// ``` /// /// ### Field attributes /// /// Several attributes can be specified to customize how each column in a row is read: /// /// #### `rename` /// /// When the name of a field in Rust does not match the name of its corresponding column, /// you can use the `rename` attribute to specify the name that the field has in the row. /// For example: /// /// ```rust,ignore /// #[derive(sqlx::FromRow)] /// struct User { /// id: i32, /// name: String, /// #[sqlx(rename = "description")] /// about_me: String /// } /// ``` /// /// Given a query such as: /// /// ```sql /// SELECT id, name, description FROM users; /// ``` /// /// will read the content of the column `description` into the field `about_me`. /// /// #### `rename_all` /// By default, field names are expected verbatim (with the exception of the raw identifier prefix `r#`, if present). /// Placed at the struct level, this attribute changes how the field name is mapped to its SQL column name: /// /// ```rust,ignore /// #[derive(sqlx::FromRow)] /// #[sqlx(rename_all = "camelCase")] /// struct UserPost { /// id: i32, /// // remapped to "userId" /// user_id: i32, /// contents: String /// } /// ``` /// /// The supported values are `snake_case` (available if you have non-snake-case field names for some /// reason), `lowercase`, `UPPERCASE`, `camelCase`, `PascalCase`, `SCREAMING_SNAKE_CASE` and `kebab-case`. /// The styling of each option is intended to be an example of its behavior. /// /// Case conversion is handled by the `heck` crate. /// See [its documentation](https://docs.rs/heck/0.5.0/heck/#definition-of-a-word-boundary) /// for details. /// /// Note that numbers are *not* considered separate words. /// For example, `Foo1` to snake case would be `foo1`, *not* `foo_1`. /// See [this issue](https://github.com/launchbadge/sqlx/issues/3864) for discussion. /// /// #### `default` /// /// When your struct contains a field that is not present in your query, /// if the field type has an implementation for [`Default`], /// you can use the `default` attribute to assign the default value to said field. /// For example: /// /// ```rust,ignore /// #[derive(sqlx::FromRow)] /// struct User { /// id: i32, /// name: String, /// #[sqlx(default)] /// location: Option /// } /// ``` /// /// Given a query such as: /// /// ```sql /// SELECT id, name FROM users; /// ``` /// /// will set the value of the field `location` to the default value of `Option`, /// which is `None`. /// /// Moreover, if the struct has an implementation for [`Default`], you can use the `default` /// attribute at the struct level rather than for each single field. If a field does not appear in the result, /// its value is taken from the `Default` implementation for the struct. /// For example: /// /// ```rust, ignore /// #[derive(Default, sqlx::FromRow)] /// #[sqlx(default)] /// struct Options { /// option_a: Option, /// option_b: Option, /// option_c: Option, /// } /// ``` /// /// For a derived `Default` implementation this effectively populates each missing field /// with `Default::default()`, but a manual `Default` implementation can provide /// different placeholder values, if applicable. /// /// This is similar to how `#[serde(default)]` behaves. /// /// #### `flatten` /// /// If you want to handle a field that implements [`FromRow`], /// you can use the `flatten` attribute to specify that you want /// it to use [`FromRow`] for parsing rather than the usual method. /// For example: /// /// ```rust,ignore /// #[derive(sqlx::FromRow)] /// struct Address { /// country: String, /// city: String, /// road: String, /// } /// /// #[derive(sqlx::FromRow)] /// struct User { /// id: i32, /// name: String, /// #[sqlx(flatten)] /// address: Address, /// } /// ``` /// Given a query such as: /// /// ```sql /// SELECT id, name, country, city, road FROM users; /// ``` /// /// This field is compatible with the `default` attribute. /// /// #### `skip` /// /// This is a variant of the `default` attribute which instead always takes the value from /// the `Default` implementation for this field type ignoring any results in your query. /// This can be useful, if some field does not satifisfy the trait bounds (i.e. /// `sqlx::decode::Decode`, `sqlx::type::Type`), in particular in case of nested structures. /// For example: /// /// ```rust,ignore /// #[derive(sqlx::FromRow)] /// struct Address { /// user_name: String, /// street: String, /// city: String, /// } /// /// #[derive(sqlx::FromRow)] /// struct User { /// name: String, /// #[sqlx(skip)] /// addresses: Vec
, /// } /// ``` /// /// Then when querying into `User`, only `name` needs to be set: /// /// ```rust,ignore /// let user: User = sqlx::query_as("SELECT name FROM users") /// .fetch_one(&mut some_connection) /// .await?; /// /// // `Default` for `Vec
` is an empty vector. /// assert!(user.addresses.is_empty()); /// ``` /// /// #### `try_from` /// /// When your struct contains a field whose type is not matched with the database type, /// if the field type has an implementation [`TryFrom`] for the database type, /// you can use the `try_from` attribute to convert the database type to the field type. /// For example: /// /// ```rust,ignore /// #[derive(sqlx::FromRow)] /// struct User { /// id: i32, /// name: String, /// #[sqlx(try_from = "i64")] /// bigIntInMySql: u64 /// } /// ``` /// /// Given a query such as: /// /// ```sql /// SELECT id, name, bigIntInMySql FROM users; /// ``` /// /// In MySql, `BigInt` type matches `i64`, but you can convert it to `u64` by `try_from`. /// /// #### `json` /// /// If your database supports a JSON type, you can leverage `#[sqlx(json)]` /// to automatically integrate JSON deserialization in your [`FromRow`] implementation using [`serde`](https://docs.rs/serde/latest/serde/). /// /// ```rust,ignore /// #[derive(serde::Deserialize)] /// struct Data { /// field1: String, /// field2: u64 /// } /// /// #[derive(sqlx::FromRow)] /// struct User { /// id: i32, /// name: String, /// #[sqlx(json)] /// metadata: Data /// } /// ``` /// /// Given a query like the following: /// /// ```sql /// SELECT /// 1 AS id, /// 'Name' AS name, /// JSON_OBJECT('field1', 'value1', 'field2', 42) AS metadata /// ``` /// /// The `metadata` field will be deserialized used its `serde::Deserialize` implementation: /// /// ```rust,ignore /// User { /// id: 1, /// name: "Name", /// metadata: Data { /// field1: "value1", /// field2: 42 /// } /// } /// ``` /// /// By default the `#[sqlx(json)]` attribute will assume that the underlying database row is /// _not_ NULL. This can cause issues when your field type is an `Option` because this would be /// represented as the _not_ NULL (in terms of DB) JSON value of `null`. /// /// If you wish to describe a database row which _is_ NULLable but _cannot_ contain the JSON value `null`, /// use the `#[sqlx(json(nullable))]` attribute. /// /// For example /// ```rust,ignore /// #[derive(serde::Deserialize)] /// struct Data { /// field1: String, /// field2: u64 /// } /// /// #[derive(sqlx::FromRow)] /// struct User { /// id: i32, /// name: String, /// #[sqlx(json(nullable))] /// metadata: Option /// } /// ``` /// Would describe a database field which _is_ NULLable but if it exists it must be the JSON representation of `Data` /// and cannot be the JSON value `null` /// /// ## Manual implementation /// /// You can also implement the [`FromRow`] trait by hand. This can be useful if you /// have a struct with a field that needs manual decoding: /// /// /// ```rust,ignore /// use sqlx::{FromRow, sqlite::SqliteRow, sqlx::Row}; /// struct MyCustomType { /// custom: String, /// } /// /// struct Foo { /// bar: MyCustomType, /// } /// /// impl FromRow<'_, SqliteRow> for Foo { /// fn from_row(row: &SqliteRow) -> sqlx::Result { /// Ok(Self { /// bar: MyCustomType { /// custom: row.try_get("custom")? /// } /// }) /// } /// } /// ``` pub trait FromRow<'r, R: Row>: Sized { fn from_row(row: &'r R) -> Result; } impl<'r, R> FromRow<'r, R> for () where R: Row, { #[inline] fn from_row(_: &'r R) -> Result { Ok(()) } } // implement FromRow for tuples of types that implement Decode // up to tuples of 16 values macro_rules! impl_from_row_for_tuple { ($( ($idx:tt) -> $T:ident );+;) => { impl<'r, R, $($T,)+> FromRow<'r, R> for ($($T,)+) where R: Row, usize: crate::column::ColumnIndex, $($T: crate::decode::Decode<'r, R::Database> + crate::types::Type,)+ { #[inline] fn from_row(row: &'r R) -> Result { Ok(($(row.try_get($idx as usize)?,)+)) } } }; } impl_from_row_for_tuple!( (0) -> T1; ); impl_from_row_for_tuple!( (0) -> T1; (1) -> T2; ); impl_from_row_for_tuple!( (0) -> T1; (1) -> T2; (2) -> T3; ); impl_from_row_for_tuple!( (0) -> T1; (1) -> T2; (2) -> T3; (3) -> T4; ); impl_from_row_for_tuple!( (0) -> T1; (1) -> T2; (2) -> T3; (3) -> T4; (4) -> T5; ); impl_from_row_for_tuple!( (0) -> T1; (1) -> T2; (2) -> T3; (3) -> T4; (4) -> T5; (5) -> T6; ); impl_from_row_for_tuple!( (0) -> T1; (1) -> T2; (2) -> T3; (3) -> T4; (4) -> T5; (5) -> T6; (6) -> T7; ); impl_from_row_for_tuple!( (0) -> T1; (1) -> T2; (2) -> T3; (3) -> T4; (4) -> T5; (5) -> T6; (6) -> T7; (7) -> T8; ); impl_from_row_for_tuple!( (0) -> T1; (1) -> T2; (2) -> T3; (3) -> T4; (4) -> T5; (5) -> T6; (6) -> T7; (7) -> T8; (8) -> T9; ); impl_from_row_for_tuple!( (0) -> T1; (1) -> T2; (2) -> T3; (3) -> T4; (4) -> T5; (5) -> T6; (6) -> T7; (7) -> T8; (8) -> T9; (9) -> T10; ); impl_from_row_for_tuple!( (0) -> T1; (1) -> T2; (2) -> T3; (3) -> T4; (4) -> T5; (5) -> T6; (6) -> T7; (7) -> T8; (8) -> T9; (9) -> T10; (10) -> T11; ); impl_from_row_for_tuple!( (0) -> T1; (1) -> T2; (2) -> T3; (3) -> T4; (4) -> T5; (5) -> T6; (6) -> T7; (7) -> T8; (8) -> T9; (9) -> T10; (10) -> T11; (11) -> T12; ); impl_from_row_for_tuple!( (0) -> T1; (1) -> T2; (2) -> T3; (3) -> T4; (4) -> T5; (5) -> T6; (6) -> T7; (7) -> T8; (8) -> T9; (9) -> T10; (10) -> T11; (11) -> T12; (12) -> T13; ); impl_from_row_for_tuple!( (0) -> T1; (1) -> T2; (2) -> T3; (3) -> T4; (4) -> T5; (5) -> T6; (6) -> T7; (7) -> T8; (8) -> T9; (9) -> T10; (10) -> T11; (11) -> T12; (12) -> T13; (13) -> T14; ); impl_from_row_for_tuple!( (0) -> T1; (1) -> T2; (2) -> T3; (3) -> T4; (4) -> T5; (5) -> T6; (6) -> T7; (7) -> T8; (8) -> T9; (9) -> T10; (10) -> T11; (11) -> T12; (12) -> T13; (13) -> T14; (14) -> T15; ); impl_from_row_for_tuple!( (0) -> T1; (1) -> T2; (2) -> T3; (3) -> T4; (4) -> T5; (5) -> T6; (6) -> T7; (7) -> T8; (8) -> T9; (9) -> T10; (10) -> T11; (11) -> T12; (12) -> T13; (13) -> T14; (14) -> T15; (15) -> T16; ); ================================================ FILE: sqlx-core/src/fs.rs ================================================ use std::ffi::OsString; use std::fs::Metadata; use std::io; use std::path::{Path, PathBuf}; use crate::rt; pub struct ReadDir { inner: Option, } pub struct DirEntry { pub path: PathBuf, pub file_name: OsString, pub metadata: Metadata, } // Filesystem operations are generally not capable of being non-blocking // so Tokio and async-std don't bother; they just send the work to a blocking thread pool. // // We save on code duplication here by just implementing the same strategy ourselves // using the runtime's `spawn_blocking()` primitive. pub async fn read>(path: P) -> io::Result> { let path = PathBuf::from(path.as_ref()); rt::spawn_blocking(move || std::fs::read(path)).await } pub async fn read_to_string>(path: P) -> io::Result { let path = PathBuf::from(path.as_ref()); rt::spawn_blocking(move || std::fs::read_to_string(path)).await } pub async fn create_dir_all>(path: P) -> io::Result<()> { let path = PathBuf::from(path.as_ref()); rt::spawn_blocking(move || std::fs::create_dir_all(path)).await } pub async fn remove_file>(path: P) -> io::Result<()> { let path = PathBuf::from(path.as_ref()); rt::spawn_blocking(move || std::fs::remove_file(path)).await } pub async fn remove_dir>(path: P) -> io::Result<()> { let path = PathBuf::from(path.as_ref()); rt::spawn_blocking(move || std::fs::remove_dir(path)).await } pub async fn remove_dir_all>(path: P) -> io::Result<()> { let path = PathBuf::from(path.as_ref()); rt::spawn_blocking(move || std::fs::remove_dir_all(path)).await } pub async fn read_dir(path: PathBuf) -> io::Result { let read_dir = rt::spawn_blocking(move || std::fs::read_dir(path)).await?; Ok(ReadDir { inner: Some(read_dir), }) } impl ReadDir { pub async fn next(&mut self) -> io::Result> { if let Some(mut read_dir) = self.inner.take() { let maybe = rt::spawn_blocking(move || { let entry = read_dir.next().transpose()?; entry .map(|entry| -> io::Result<_> { Ok(( read_dir, DirEntry { path: entry.path(), file_name: entry.file_name(), // We always want the metadata as well so might as well fetch // it in the same blocking call. metadata: entry.metadata()?, }, )) }) .transpose() }) .await?; match maybe { Some((read_dir, entry)) => { self.inner = Some(read_dir); Ok(Some(entry)) } None => Ok(None), } } else { Ok(None) } } } ================================================ FILE: sqlx-core/src/io/buf.rs ================================================ use std::str::from_utf8; use bytes::{Buf, Bytes}; use memchr::memchr; use crate::error::Error; pub trait BufExt: Buf { // Read a nul-terminated byte sequence fn get_bytes_nul(&mut self) -> Result; // Read a byte sequence of the exact length fn get_bytes(&mut self, len: usize) -> Bytes; // Read a nul-terminated string fn get_str_nul(&mut self) -> Result; // Read a string of the exact length fn get_str(&mut self, len: usize) -> Result; } impl BufExt for Bytes { fn get_bytes_nul(&mut self) -> Result { let nul = memchr(b'\0', self).ok_or_else(|| err_protocol!("expected NUL in byte sequence"))?; let v = self.slice(0..nul); self.advance(nul + 1); Ok(v) } fn get_bytes(&mut self, len: usize) -> Bytes { let v = self.slice(..len); self.advance(len); v } fn get_str_nul(&mut self) -> Result { self.get_bytes_nul().and_then(|bytes| { from_utf8(&bytes) .map(ToOwned::to_owned) .map_err(|err| err_protocol!("{}", err)) }) } fn get_str(&mut self, len: usize) -> Result { let v = from_utf8(&self[..len]) .map_err(|err| err_protocol!("{}", err)) .map(ToOwned::to_owned)?; self.advance(len); Ok(v) } } ================================================ FILE: sqlx-core/src/io/buf_mut.rs ================================================ use bytes::BufMut; pub trait BufMutExt: BufMut { fn put_str_nul(&mut self, s: &str); } impl BufMutExt for Vec { fn put_str_nul(&mut self, s: &str) { self.extend(s.as_bytes()); self.push(0); } } ================================================ FILE: sqlx-core/src/io/buf_stream.rs ================================================ #![allow(dead_code)] use std::io; use std::ops::{Deref, DerefMut}; use bytes::BytesMut; use sqlx_rt::{AsyncRead, AsyncReadExt, AsyncWrite}; use crate::error::Error; use crate::io::write_and_flush::WriteAndFlush; use crate::io::{decode::Decode, encode::Encode}; use std::io::Cursor; pub struct BufStream where S: AsyncRead + AsyncWrite + Unpin, { pub(crate) stream: S, // writes with `write` to the underlying stream are buffered // this can be flushed with `flush` pub(crate) wbuf: Vec, // we read into the read buffer using 100% safe code rbuf: BytesMut, } impl BufStream where S: AsyncRead + AsyncWrite + Unpin, { pub fn new(stream: S) -> Self { Self { stream, wbuf: Vec::with_capacity(512), rbuf: BytesMut::with_capacity(4096), } } pub fn write<'en, T>(&mut self, value: T) where T: Encode<'en, ()>, { self.write_with(value, ()) } pub fn write_with<'en, T, C>(&mut self, value: T, context: C) where T: Encode<'en, C>, { value.encode_with(&mut self.wbuf, context); } pub fn flush(&mut self) -> WriteAndFlush<'_, S> { WriteAndFlush { stream: &mut self.stream, buf: Cursor::new(&mut self.wbuf), } } pub async fn read<'de, T>(&mut self, cnt: usize) -> Result where T: Decode<'de, ()>, { self.read_with(cnt, ()).await } pub async fn read_with<'de, T, C>(&mut self, cnt: usize, context: C) -> Result where T: Decode<'de, C>, { T::decode_with(self.read_raw(cnt).await?.freeze(), context) } pub async fn read_raw(&mut self, cnt: usize) -> Result { read_raw_into(&mut self.stream, &mut self.rbuf, cnt).await?; let buf = self.rbuf.split_to(cnt); Ok(buf) } pub async fn read_raw_into(&mut self, buf: &mut BytesMut, cnt: usize) -> Result<(), Error> { read_raw_into(&mut self.stream, buf, cnt).await } } impl Deref for BufStream where S: AsyncRead + AsyncWrite + Unpin, { type Target = S; fn deref(&self) -> &Self::Target { &self.stream } } impl DerefMut for BufStream where S: AsyncRead + AsyncWrite + Unpin, { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.stream } } ================================================ FILE: sqlx-core/src/io/decode.rs ================================================ use bytes::Bytes; use crate::error::Error; pub trait ProtocolDecode<'de, Context = ()> where Self: Sized, { fn decode(buf: Bytes) -> Result where Self: ProtocolDecode<'de, ()>, { Self::decode_with(buf, ()) } fn decode_with(buf: Bytes, context: Context) -> Result; } impl ProtocolDecode<'_> for Bytes { fn decode_with(buf: Bytes, _: ()) -> Result { Ok(buf) } } impl ProtocolDecode<'_> for () { fn decode_with(_: Bytes, _: ()) -> Result<(), Error> { Ok(()) } } ================================================ FILE: sqlx-core/src/io/encode.rs ================================================ pub trait ProtocolEncode<'en, Context = ()> { fn encode(&self, buf: &mut Vec) -> Result<(), crate::Error> where Self: ProtocolEncode<'en, ()>, { self.encode_with(buf, ()) } fn encode_with(&self, buf: &mut Vec, context: Context) -> Result<(), crate::Error>; } impl ProtocolEncode<'_, C> for &'_ [u8] { fn encode_with(&self, buf: &mut Vec, _context: C) -> Result<(), crate::Error> { buf.extend_from_slice(self); Ok(()) } } ================================================ FILE: sqlx-core/src/io/mod.rs ================================================ mod buf; mod buf_mut; // mod buf_stream; mod decode; mod encode; mod read_buf; // mod write_and_flush; pub use buf::BufExt; pub use buf_mut::BufMutExt; //pub use buf_stream::BufStream; pub use decode::ProtocolDecode; pub use encode::ProtocolEncode; pub use read_buf::ReadBuf; #[cfg(not(feature = "_rt-tokio"))] pub use futures_io::AsyncRead; #[cfg(feature = "_rt-tokio")] pub use tokio::io::AsyncRead; #[cfg(not(feature = "_rt-tokio"))] pub use futures_util::io::AsyncReadExt; #[cfg(feature = "_rt-tokio")] pub use tokio::io::AsyncReadExt; ================================================ FILE: sqlx-core/src/io/read_buf.rs ================================================ use bytes::{BufMut, BytesMut}; /// An extension for [`BufMut`] for getting a writeable buffer in safe code. pub trait ReadBuf: BufMut { /// Get the full capacity of this buffer as a safely initialized slice. fn init_mut(&mut self) -> &mut [u8]; } impl ReadBuf for &'_ mut [u8] { #[inline(always)] fn init_mut(&mut self) -> &mut [u8] { self } } impl ReadBuf for BytesMut { #[inline(always)] fn init_mut(&mut self) -> &mut [u8] { // `self.remaining_mut()` returns `usize::MAX - self.len()` let remaining = self.capacity() - self.len(); // I'm hoping for most uses that this operation is elided by the optimizer. self.put_bytes(0, remaining); self } } #[test] fn test_read_buf_bytes_mut() { let mut buf = BytesMut::with_capacity(8); buf.put_u32(0x12345678); assert_eq!(buf.init_mut(), [0x12, 0x34, 0x56, 0x78, 0, 0, 0, 0]); } ================================================ FILE: sqlx-core/src/io/write_and_flush.rs ================================================ use crate::error::Error; use sqlx_rt::AsyncWrite; use std::future::Future; use std::io::{BufRead, Cursor}; use std::pin::Pin; use std::task::{ready, Context, Poll}; // Atomic operation that writes the full buffer to the stream, flushes the stream, and then // clears the buffer (even if either of the two previous operations failed). pub struct WriteAndFlush<'a, S> { pub(super) stream: &'a mut S, pub(super) buf: Cursor<&'a mut Vec>, } impl Future for WriteAndFlush<'_, S> { type Output = Result<(), Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let Self { ref mut stream, ref mut buf, } = *self; loop { let read = buf.fill_buf()?; if !read.is_empty() { let written = ready!(Pin::new(&mut *stream).poll_write(cx, read)?); buf.consume(written); } else { break; } } Pin::new(stream).poll_flush(cx).map_err(Error::Io) } } impl<'a, S> Drop for WriteAndFlush<'a, S> { fn drop(&mut self) { // clear the buffer regardless of whether the flush succeeded or not self.buf.get_mut().clear(); } } ================================================ FILE: sqlx-core/src/lib.rs ================================================ //! Core of SQLx, the rust SQL toolkit. //! //! ### Note: Semver Exempt API //! The API of this crate is not meant for general use and does *not* follow Semantic Versioning. //! The only crate that follows Semantic Versioning in the project is the `sqlx` crate itself. //! If you are building a custom SQLx driver, you should pin an exact version for `sqlx-core` to //! avoid breakages: //! //! ```toml //! sqlx-core = { version = "=0.6.2" } //! ``` //! //! And then make releases in lockstep with `sqlx-core`. We recommend all driver crates, in-tree //! or otherwise, use the same version numbers as `sqlx-core` to avoid confusion. #![recursion_limit = "512"] #![warn(future_incompatible, rust_2018_idioms)] #![allow(clippy::needless_doctest_main, clippy::type_complexity)] // The only unsafe code in SQLx is that necessary to interact with native APIs like with SQLite, // and that can live in its own separate driver crate. #![forbid(unsafe_code)] // Allows an API be documented as only available in some specific platforms. // #![cfg_attr(docsrs, feature(doc_cfg))] #[macro_use] pub mod ext; #[macro_use] pub mod error; #[macro_use] pub mod arguments; #[macro_use] pub mod pool; pub mod connection; #[macro_use] pub mod transaction; #[macro_use] pub mod encode; #[macro_use] pub mod decode; #[macro_use] pub mod types; #[macro_use] pub mod query; #[macro_use] pub mod acquire; #[macro_use] pub mod column; #[macro_use] pub mod statement; pub mod common; pub mod database; pub mod describe; pub mod executor; pub mod from_row; pub mod fs; pub mod io; pub mod logger; pub mod net; pub mod query_as; pub mod query_builder; pub mod query_scalar; pub mod sql_str; pub mod raw_sql; pub mod row; pub mod rt; pub mod sync; pub mod type_checking; pub mod type_info; pub mod value; #[cfg(feature = "migrate")] pub mod migrate; #[cfg(feature = "any")] pub mod any; // Implements test support with automatic DB management. #[cfg(feature = "migrate")] pub mod testing; pub mod config; pub use error::{Error, Result}; pub use either::Either; pub use hashbrown::{hash_map, HashMap}; pub use indexmap::IndexMap; pub use percent_encoding; pub use smallvec::SmallVec; pub use url::{self, Url}; pub use bytes; /// Helper module to get drivers compiling again that used to be in this crate, /// to avoid having to replace tons of `use crate::<...>` imports. /// /// This module can be glob-imported and should not clash with any modules a driver /// would want to implement itself. pub mod driver_prelude { pub use crate::{ acquire, common, decode, describe, encode, executor, ext, from_row, fs, io, logger, net, pool, query, query_as, query_builder, query_scalar, rt, sync, }; pub use crate::error::{Error, Result}; pub use crate::{hash_map, HashMap}; pub use either::Either; } ================================================ FILE: sqlx-core/src/logger.rs ================================================ use crate::{connection::LogSettings, sql_str::SqlStr}; use std::time::Instant; // Yes these look silly. `tracing` doesn't currently support dynamic levels // https://github.com/tokio-rs/tracing/issues/372 #[doc(hidden)] #[macro_export] macro_rules! private_tracing_dynamic_enabled { (target: $target:expr, $level:expr) => {{ use ::tracing::Level; match $level { Level::ERROR => ::tracing::enabled!(target: $target, Level::ERROR), Level::WARN => ::tracing::enabled!(target: $target, Level::WARN), Level::INFO => ::tracing::enabled!(target: $target, Level::INFO), Level::DEBUG => ::tracing::enabled!(target: $target, Level::DEBUG), Level::TRACE => ::tracing::enabled!(target: $target, Level::TRACE), } }}; ($level:expr) => {{ $crate::private_tracing_dynamic_enabled!(target: module_path!(), $level) }}; } #[doc(hidden)] #[macro_export] macro_rules! private_tracing_dynamic_event { (target: $target:expr, $level:expr, $($args:tt)*) => {{ use ::tracing::Level; match $level { Level::ERROR => ::tracing::event!(target: $target, Level::ERROR, $($args)*), Level::WARN => ::tracing::event!(target: $target, Level::WARN, $($args)*), Level::INFO => ::tracing::event!(target: $target, Level::INFO, $($args)*), Level::DEBUG => ::tracing::event!(target: $target, Level::DEBUG, $($args)*), Level::TRACE => ::tracing::event!(target: $target, Level::TRACE, $($args)*), } }}; } #[doc(hidden)] pub fn private_level_filter_to_levels( filter: log::LevelFilter, ) -> Option<(tracing::Level, log::Level)> { let tracing_level = match filter { log::LevelFilter::Error => Some(tracing::Level::ERROR), log::LevelFilter::Warn => Some(tracing::Level::WARN), log::LevelFilter::Info => Some(tracing::Level::INFO), log::LevelFilter::Debug => Some(tracing::Level::DEBUG), log::LevelFilter::Trace => Some(tracing::Level::TRACE), log::LevelFilter::Off => None, }; tracing_level.zip(filter.to_level()) } pub(crate) fn private_level_filter_to_trace_level( filter: log::LevelFilter, ) -> Option { private_level_filter_to_levels(filter).map(|(level, _)| level) } pub struct QueryLogger { sql: SqlStr, rows_returned: u64, rows_affected: u64, start: Instant, settings: LogSettings, } impl QueryLogger { pub fn new(sql: SqlStr, settings: LogSettings) -> Self { Self { sql, rows_returned: 0, rows_affected: 0, start: Instant::now(), settings, } } pub fn increment_rows_returned(&mut self) { self.rows_returned += 1; } pub fn increase_rows_affected(&mut self, n: u64) { self.rows_affected += n; } pub fn sql(&self) -> &SqlStr { &self.sql } pub fn finish(&self) { let elapsed = self.start.elapsed(); let was_slow = elapsed >= self.settings.slow_statements_duration; let lvl = if was_slow { self.settings.slow_statements_level } else { self.settings.statements_level }; if let Some((tracing_level, log_level)) = private_level_filter_to_levels(lvl) { // The enabled level could be set from either tracing world or log world, so check both // to see if logging should be enabled for our level let log_is_enabled = log::log_enabled!(target: "sqlx::query", log_level) || private_tracing_dynamic_enabled!(target: "sqlx::query", tracing_level); if log_is_enabled { let mut summary = parse_query_summary(self.sql.as_str()); let sql = if summary != self.sql.as_str() { summary.push_str(" …"); format!("\n\n{}\n", self.sql.as_str()) } else { String::new() }; if was_slow { private_tracing_dynamic_event!( target: "sqlx::query", tracing_level, summary, db.statement = sql, rows_affected = self.rows_affected, rows_returned = self.rows_returned, // Human-friendly - includes units (usually ms). Also kept for backward compatibility ?elapsed, // Search friendly - numeric elapsed_secs = elapsed.as_secs_f64(), // When logging to JSON, one can trigger alerts from the presence of this field. slow_threshold=?self.settings.slow_statements_duration, // Make sure to use "slow" in the message as that's likely // what people will grep for. "slow statement: execution time exceeded alert threshold" ); } else { private_tracing_dynamic_event!( target: "sqlx::query", tracing_level, summary, db.statement = sql, rows_affected = self.rows_affected, rows_returned = self.rows_returned, // Human-friendly - includes units (usually ms). Also kept for backward compatibility ?elapsed, // Search friendly - numeric elapsed_secs = elapsed.as_secs_f64(), ); } } } } } impl Drop for QueryLogger { fn drop(&mut self) { self.finish(); } } pub fn parse_query_summary(sql: &str) -> String { // For now, just take the first 4 words sql.split_whitespace() .take(4) .collect::>() .join(" ") } ================================================ FILE: sqlx-core/src/migrate/error.rs ================================================ use crate::error::{BoxDynError, Error}; #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum MigrateError { #[error("while executing migrations: {0}")] Execute(#[from] Error), #[error("while executing migration {1}: {0}")] ExecuteMigration(#[source] Error, i64), #[error("while resolving migrations: {0}")] Source(#[source] BoxDynError), #[error("migration {0} was previously applied but is missing in the resolved migrations")] VersionMissing(i64), #[error("migration {0} was previously applied but has been modified")] VersionMismatch(i64), #[error("migration {0} is not present in the migration source")] VersionNotPresent(i64), #[error("migration {0} is older than the latest applied migration {1}")] VersionTooOld(i64, i64), #[error("migration {0} is newer than the latest applied migration {1}")] VersionTooNew(i64, i64), #[error("database driver does not support force-dropping a database (Only PostgreSQL)")] ForceNotSupported, #[deprecated = "migration types are now inferred"] #[error("cannot mix reversible migrations with simple migrations. All migrations should be reversible or simple migrations")] InvalidMixReversibleAndSimple, // NOTE: this will only happen with a database that does not have transactional DDL (.e.g, MySQL or Oracle) #[error( "migration {0} is partially applied; fix and remove row from `_sqlx_migrations` table" )] Dirty(i64), #[error("database driver does not support creation of schemas at migrate time: {0}")] CreateSchemasNotSupported(String), } ================================================ FILE: sqlx-core/src/migrate/migrate.rs ================================================ use crate::error::Error; use crate::migrate::{AppliedMigration, MigrateError, Migration}; use futures_core::future::BoxFuture; use std::future::Future; use std::time::Duration; pub trait MigrateDatabase { // create database in url // uses a maintenance database depending on driver fn create_database(url: &str) -> impl Future> + Send + '_; // check if the database in url exists // uses a maintenance database depending on driver fn database_exists(url: &str) -> impl Future> + Send + '_; // drop database in url // uses a maintenance database depending on driver fn drop_database(url: &str) -> impl Future> + Send + '_; // force drop database in url // uses a maintenance database depending on driver fn force_drop_database(_url: &str) -> impl Future> + Send + '_ { async { Err(MigrateError::ForceNotSupported)? } } } // 'e = Executor pub trait Migrate { /// Create a database schema with the given name if it does not already exist. fn create_schema_if_not_exists<'e>( &'e mut self, schema_name: &'e str, ) -> BoxFuture<'e, Result<(), MigrateError>>; // ensure migrations table exists // will create or migrate it if needed fn ensure_migrations_table<'e>( &'e mut self, table_name: &'e str, ) -> BoxFuture<'e, Result<(), MigrateError>>; // Return the version on which the database is dirty or None otherwise. // "dirty" means there is a partially applied migration that failed. fn dirty_version<'e>( &'e mut self, table_name: &'e str, ) -> BoxFuture<'e, Result, MigrateError>>; // Return the ordered list of applied migrations fn list_applied_migrations<'e>( &'e mut self, table_name: &'e str, ) -> BoxFuture<'e, Result, MigrateError>>; // Should acquire a database lock so that only one migration process // can run at a time. [`Migrate`] will call this function before applying // any migrations. fn lock(&mut self) -> BoxFuture<'_, Result<(), MigrateError>>; // Should release the lock. [`Migrate`] will call this function after all // migrations have been run. fn unlock(&mut self) -> BoxFuture<'_, Result<(), MigrateError>>; // run SQL from migration in a DDL transaction // insert new row to [_migrations] table on completion (success or failure) // returns the time taking to run the migration SQL fn apply<'e>( &'e mut self, table_name: &'e str, migration: &'e Migration, ) -> BoxFuture<'e, Result>; // run a revert SQL from migration in a DDL transaction // deletes the row in [_migrations] table with specified migration version on completion (success or failure) // returns the time taking to run the migration SQL fn revert<'e>( &'e mut self, table_name: &'e str, migration: &'e Migration, ) -> BoxFuture<'e, Result>; } ================================================ FILE: sqlx-core/src/migrate/migration.rs ================================================ use sha2::{Digest, Sha384}; use std::borrow::Cow; use crate::sql_str::SqlStr; use super::MigrationType; #[derive(Debug, Clone)] pub struct Migration { pub version: i64, pub description: Cow<'static, str>, pub migration_type: MigrationType, pub sql: SqlStr, pub checksum: Cow<'static, [u8]>, pub no_tx: bool, } impl Migration { pub fn new( version: i64, description: Cow<'static, str>, migration_type: MigrationType, sql: SqlStr, no_tx: bool, ) -> Self { let checksum = checksum(sql.as_str()); Self::with_checksum( version, description, migration_type, sql, checksum.into(), no_tx, ) } pub(crate) fn with_checksum( version: i64, description: Cow<'static, str>, migration_type: MigrationType, sql: SqlStr, checksum: Cow<'static, [u8]>, no_tx: bool, ) -> Self { Migration { version, description, migration_type, sql, checksum, no_tx, } } } #[derive(Debug, Clone)] pub struct AppliedMigration { pub version: i64, pub checksum: Cow<'static, [u8]>, } pub fn checksum(sql: &str) -> Vec { Vec::from(Sha384::digest(sql).as_slice()) } pub fn checksum_fragments<'a>(fragments: impl Iterator) -> Vec { let mut digest = Sha384::new(); for fragment in fragments { digest.update(fragment); } digest.finalize().to_vec() } #[test] fn fragments_checksum_equals_full_checksum() { // Copied from `examples/postgres/axum-social-with-tests/migrations/3_comment.sql` let sql = "\ \u{FEFF}create table comment (\r\n\ \tcomment_id uuid primary key default gen_random_uuid(),\r\n\ \tpost_id uuid not null references post(post_id),\r\n\ \tuser_id uuid not null references \"user\"(user_id),\r\n\ \tcontent text not null,\r\n\ \tcreated_at timestamptz not null default now()\r\n\ );\r\n\ \r\n\ create index on comment(post_id, created_at);\r\n\ "; // Should yield a string for each character let fragments_checksum = checksum_fragments(sql.split("")); let full_checksum = checksum(sql); assert_eq!(fragments_checksum, full_checksum); } ================================================ FILE: sqlx-core/src/migrate/migration_type.rs ================================================ use super::Migrator; /// Migration Type represents the type of migration #[derive(Debug, Copy, Clone, PartialEq)] pub enum MigrationType { /// Simple migration are single file migrations with no up / down queries Simple, /// ReversibleUp migrations represents the add or update part of a reversible migrations /// It is expected the every migration of this type will have a corresponding down file ReversibleUp, /// ReversibleDown migrations represents the delete or downgrade part of a reversible migrations /// It is expected the every migration of this type will have a corresponding up file ReversibleDown, } impl MigrationType { pub fn from_filename(filename: &str) -> Self { if filename.ends_with(MigrationType::ReversibleUp.suffix()) { MigrationType::ReversibleUp } else if filename.ends_with(MigrationType::ReversibleDown.suffix()) { MigrationType::ReversibleDown } else { MigrationType::Simple } } pub fn is_reversible(&self) -> bool { match self { MigrationType::Simple => false, MigrationType::ReversibleUp => true, MigrationType::ReversibleDown => true, } } pub fn is_up_migration(&self) -> bool { match self { MigrationType::Simple => true, MigrationType::ReversibleUp => true, MigrationType::ReversibleDown => false, } } pub fn is_down_migration(&self) -> bool { match self { MigrationType::Simple => false, MigrationType::ReversibleUp => false, MigrationType::ReversibleDown => true, } } pub fn label(&self) -> &'static str { match self { MigrationType::Simple => "migrate", MigrationType::ReversibleUp => "migrate", MigrationType::ReversibleDown => "revert", } } pub fn suffix(&self) -> &'static str { match self { MigrationType::Simple => ".sql", MigrationType::ReversibleUp => ".up.sql", MigrationType::ReversibleDown => ".down.sql", } } pub fn file_content(&self) -> &'static str { match self { MigrationType::Simple => "-- Add migration script here\n", MigrationType::ReversibleUp => "-- Add up migration script here\n", MigrationType::ReversibleDown => "-- Add down migration script here\n", } } #[deprecated = "unused"] pub fn infer(migrator: &Migrator, reversible: bool) -> MigrationType { match migrator.iter().last() { Some(first_migration) => first_migration.migration_type, None => { if reversible { MigrationType::ReversibleUp } else { MigrationType::Simple } } } } } ================================================ FILE: sqlx-core/src/migrate/migrator.rs ================================================ use crate::acquire::Acquire; use crate::migrate::{AppliedMigration, Migrate, MigrateError, Migration, MigrationSource}; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::ops::Deref; use std::slice; /// A resolved set of migrations, ready to be run. /// /// Can be constructed statically using `migrate!()` or at runtime using [`Migrator::new()`]. #[derive(Debug)] // Forbids `migrate!()` from constructing this: // #[non_exhaustive] pub struct Migrator { // NOTE: these fields are semver-exempt and may be changed or removed in any future version. // These have to be public for `migrate!()` to be able to initialize them in an implicitly // const-promotable context. A `const fn` constructor isn't implicitly const-promotable. #[doc(hidden)] pub migrations: Cow<'static, [Migration]>, #[doc(hidden)] pub ignore_missing: bool, #[doc(hidden)] pub locking: bool, #[doc(hidden)] pub no_tx: bool, #[doc(hidden)] pub table_name: Cow<'static, str>, #[doc(hidden)] pub create_schemas: Cow<'static, [Cow<'static, str>]>, } impl Migrator { #[doc(hidden)] pub const DEFAULT: Migrator = Migrator { migrations: Cow::Borrowed(&[]), ignore_missing: false, no_tx: false, locking: true, table_name: Cow::Borrowed("_sqlx_migrations"), create_schemas: Cow::Borrowed(&[]), }; /// Creates a new instance with the given source. /// /// # Examples /// /// ```rust,no_run /// # use sqlx_core::migrate::MigrateError; /// # fn main() -> Result<(), MigrateError> { /// # sqlx::__rt::test_block_on(async move { /// # use sqlx_core::migrate::Migrator; /// use std::path::Path; /// /// // Read migrations from a local folder: ./migrations /// let m = Migrator::new(Path::new("./migrations")).await?; /// # Ok(()) /// # }) /// # } /// ``` /// See [MigrationSource] for details on structure of the `./migrations` directory. pub async fn new<'s, S>(source: S) -> Result where S: MigrationSource<'s>, { Ok(Self { migrations: Cow::Owned(source.resolve().await.map_err(MigrateError::Source)?), ..Self::DEFAULT }) } /// Creates a new instance with the given migrations. /// /// /// # Examples /// /// ```rust,no_run /// use sqlx::{ SqlSafeStr, migrate::{Migration, MigrationType::*, Migrator}}; /// /// // Define your migrations. /// // You can also use include_str!("./xxx.sql") instead of hard-coded SQL statements. /// let migrations = vec![ /// Migration::new(1, "user".into(), ReversibleUp, "create table users ( ... )".into_sql_str(), false), /// Migration::new(2, "post".into(), ReversibleUp, "create table posts ( ... )".into_sql_str(), false), /// // add more... /// ]; /// let m = Migrator::with_migrations(migrations); /// ``` pub fn with_migrations(mut migrations: Vec) -> Self { // Ensure that we are sorted by version in ascending order. migrations.sort_by_key(|m| m.version); Self { migrations: Cow::Owned(migrations), ..Self::DEFAULT } } /// Override the name of the table used to track executed migrations. /// /// May be schema-qualified and/or contain quotes. Defaults to `_sqlx_migrations`. /// /// Potentially useful for multi-tenant databases. /// /// ### Warning: Potential Data Loss or Corruption! /// Changing this option for a production database will likely result in data loss or corruption /// as the migration machinery will no longer be aware of what migrations have been applied /// and will attempt to re-run them. /// /// You should create the new table as a copy of the existing migrations table (with contents!), /// and be sure all instances of your application have been migrated to the new /// table before deleting the old one. pub fn dangerous_set_table_name(&mut self, table_name: impl Into>) -> &Self { self.table_name = table_name.into(); self } /// Add a schema name to be created if it does not already exist. /// /// May be used with [`Self::dangerous_set_table_name()`] to place the migrations table /// in a new schema without requiring it to exist first. /// /// ### Note: Support Depends on Database /// SQLite cannot create new schemas without attaching them to a database file, /// the path of which must be specified separately in an [`ATTACH DATABASE`](https://www.sqlite.org/lang_attach.html) command. pub fn create_schema(&mut self, schema_name: impl Into>) -> &Self { self.create_schemas.to_mut().push(schema_name.into()); self } /// Specify whether applied migrations that are missing from the resolved migrations should be ignored. pub fn set_ignore_missing(&mut self, ignore_missing: bool) -> &mut Self { self.ignore_missing = ignore_missing; self } /// Specify whether or not to lock the database during migration. Defaults to `true`. /// /// ### Warning /// Disabling locking can lead to errors or data loss if multiple clients attempt to apply migrations simultaneously /// without some sort of mutual exclusion. /// /// This should only be used if the database does not support locking, e.g. CockroachDB which talks the Postgres /// protocol but does not support advisory locks used by SQLx's migrations support for Postgres. pub fn set_locking(&mut self, locking: bool) -> &mut Self { self.locking = locking; self } /// Get an iterator over all known migrations. pub fn iter(&self) -> slice::Iter<'_, Migration> { self.migrations.iter() } /// Check if a migration version exists. pub fn version_exists(&self, version: i64) -> bool { self.iter().any(|m| m.version == version) } /// Run any pending migrations against the database; and, validate previously applied migrations /// against the current migration source to detect accidental changes in previously-applied migrations. /// /// # Examples /// /// ```rust,no_run /// # use sqlx::migrate::MigrateError; /// # fn main() -> Result<(), MigrateError> { /// # sqlx::__rt::test_block_on(async move { /// use sqlx::migrate::Migrator; /// use sqlx::sqlite::SqlitePoolOptions; /// /// let m = Migrator::new(std::path::Path::new("./migrations")).await?; /// let pool = SqlitePoolOptions::new().connect("sqlite::memory:").await?; /// m.run(&pool).await /// # }) /// # } /// ``` pub async fn run<'a, A>(&self, migrator: A) -> Result<(), MigrateError> where A: Acquire<'a>, ::Target: Migrate, { let mut conn = migrator.acquire().await?; self.run_direct(None, &mut *conn).await } pub async fn run_to<'a, A>(&self, target: i64, migrator: A) -> Result<(), MigrateError> where A: Acquire<'a>, ::Target: Migrate, { let mut conn = migrator.acquire().await?; self.run_direct(Some(target), &mut *conn).await } // Getting around the annoying "implementation of `Acquire` is not general enough" error #[doc(hidden)] pub async fn run_direct(&self, target: Option, conn: &mut C) -> Result<(), MigrateError> where C: Migrate, { // lock the database for exclusive access by the migrator if self.locking { conn.lock().await?; } for schema_name in self.create_schemas.iter() { conn.create_schema_if_not_exists(schema_name).await?; } // creates [_migrations] table only if needed // eventually this will likely migrate previous versions of the table conn.ensure_migrations_table(&self.table_name).await?; let version = conn.dirty_version(&self.table_name).await?; if let Some(version) = version { return Err(MigrateError::Dirty(version)); } let applied_migrations = conn.list_applied_migrations(&self.table_name).await?; validate_applied_migrations(&applied_migrations, self)?; let applied_migrations: HashMap<_, _> = applied_migrations .into_iter() .map(|m| (m.version, m)) .collect(); for migration in self.iter() { if target.is_some_and(|target| target < migration.version) { // Target version reached break; } if migration.migration_type.is_down_migration() { continue; } match applied_migrations.get(&migration.version) { Some(applied_migration) => { if migration.checksum != applied_migration.checksum { return Err(MigrateError::VersionMismatch(migration.version)); } } None => { conn.apply(&self.table_name, migration).await?; } } } // unlock the migrator to allow other migrators to run // but do nothing as we already migrated if self.locking { conn.unlock().await?; } Ok(()) } /// Run down migrations against the database until a specific version. /// /// # Examples /// /// ```rust,no_run /// # use sqlx::migrate::MigrateError; /// # fn main() -> Result<(), MigrateError> { /// # sqlx::__rt::test_block_on(async move { /// use sqlx::migrate::Migrator; /// use sqlx::sqlite::SqlitePoolOptions; /// /// let m = Migrator::new(std::path::Path::new("./migrations")).await?; /// let pool = SqlitePoolOptions::new().connect("sqlite::memory:").await?; /// m.undo(&pool, 4).await /// # }) /// # } /// ``` pub async fn undo<'a, A>(&self, migrator: A, target: i64) -> Result<(), MigrateError> where A: Acquire<'a>, ::Target: Migrate, { let mut conn = migrator.acquire().await?; // lock the database for exclusive access by the migrator if self.locking { conn.lock().await?; } // creates [_migrations] table only if needed // eventually this will likely migrate previous versions of the table conn.ensure_migrations_table(&self.table_name).await?; let version = conn.dirty_version(&self.table_name).await?; if let Some(version) = version { return Err(MigrateError::Dirty(version)); } let applied_migrations = conn.list_applied_migrations(&self.table_name).await?; validate_applied_migrations(&applied_migrations, self)?; let applied_migrations: HashMap<_, _> = applied_migrations .into_iter() .map(|m| (m.version, m)) .collect(); for migration in self .iter() .rev() .filter(|m| m.migration_type.is_down_migration()) .filter(|m| applied_migrations.contains_key(&m.version)) .filter(|m| m.version > target) { conn.revert(&self.table_name, migration).await?; } // unlock the migrator to allow other migrators to run // but do nothing as we already migrated if self.locking { conn.unlock().await?; } Ok(()) } } fn validate_applied_migrations( applied_migrations: &[AppliedMigration], migrator: &Migrator, ) -> Result<(), MigrateError> { if migrator.ignore_missing { return Ok(()); } let migrations: HashSet<_> = migrator.iter().map(|m| m.version).collect(); for applied_migration in applied_migrations { if !migrations.contains(&applied_migration.version) { return Err(MigrateError::VersionMissing(applied_migration.version)); } } Ok(()) } ================================================ FILE: sqlx-core/src/migrate/mod.rs ================================================ mod error; #[allow(clippy::module_inception)] mod migrate; mod migration; mod migration_type; mod migrator; mod source; pub use error::MigrateError; pub use migrate::{Migrate, MigrateDatabase}; pub use migration::{AppliedMigration, Migration}; pub use migration_type::MigrationType; pub use migrator::Migrator; pub use source::{MigrationSource, ResolveConfig, ResolveWith}; #[doc(hidden)] pub use source::{resolve_blocking, resolve_blocking_with_config}; ================================================ FILE: sqlx-core/src/migrate/source.rs ================================================ use crate::error::BoxDynError; use crate::migrate::{migration, Migration, MigrationType}; use crate::sql_str::{AssertSqlSafe, SqlSafeStr}; use futures_core::future::BoxFuture; use std::borrow::Cow; use std::collections::BTreeSet; use std::fmt::Debug; use std::fs; use std::io; use std::path::{Path, PathBuf}; /// In the default implementation, a MigrationSource is a directory which /// contains the migration SQL scripts. All these scripts must be stored in /// files with names using the format `_.sql`, where /// `` is a string that can be parsed into `i64` and its value is /// greater than zero, and `` is a string. /// /// Files that don't match this format are silently ignored. /// /// You can create a new empty migration script using sqlx-cli: /// `sqlx migrate add `. /// /// Note that migrations for each database are tracked using the /// `_sqlx_migrations` table (stored in the database). If a migration's hash /// changes and it has already been run, this will cause an error. pub trait MigrationSource<'s>: Debug { fn resolve(self) -> BoxFuture<'s, Result, BoxDynError>>; } impl<'s> MigrationSource<'s> for &'s Path { fn resolve(self) -> BoxFuture<'s, Result, BoxDynError>> { // Behavior changed from previous because `canonicalize()` is potentially blocking // since it might require going to disk to fetch filesystem data. self.to_owned().resolve() } } impl MigrationSource<'static> for PathBuf { fn resolve(self) -> BoxFuture<'static, Result, BoxDynError>> { // Technically this could just be `Box::pin(spawn_blocking(...))` // but that would actually be a breaking behavior change because it would call // `spawn_blocking()` on the current thread Box::pin(async move { crate::rt::spawn_blocking(move || { let migrations_with_paths = resolve_blocking(&self)?; Ok(migrations_with_paths.into_iter().map(|(m, _p)| m).collect()) }) .await }) } } /// A [`MigrationSource`] implementation with configurable resolution. /// /// `S` may be `PathBuf`, `&Path` or any type that implements `Into`. /// /// See [`ResolveConfig`] for details. #[derive(Debug)] pub struct ResolveWith(pub S, pub ResolveConfig); impl<'s, S: Debug + Into + Send + 's> MigrationSource<'s> for ResolveWith { fn resolve(self) -> BoxFuture<'s, Result, BoxDynError>> { Box::pin(async move { let path = self.0.into(); let config = self.1; let migrations_with_paths = crate::rt::spawn_blocking(move || resolve_blocking_with_config(&path, &config)) .await?; Ok(migrations_with_paths.into_iter().map(|(m, _p)| m).collect()) }) } } #[derive(thiserror::Error, Debug)] #[error("{message}")] pub struct ResolveError { message: String, #[source] source: Option, } /// Configuration for migration resolution using [`ResolveWith`]. #[derive(Debug, Default)] pub struct ResolveConfig { ignored_chars: BTreeSet, } impl ResolveConfig { /// Return a default, empty configuration. pub fn new() -> Self { ResolveConfig { ignored_chars: BTreeSet::new(), } } /// Ignore a character when hashing migrations. /// /// The migration SQL string itself will still contain the character, /// but it will not be included when calculating the checksum. /// /// This can be used to ignore whitespace characters so changing formatting /// does not change the checksum. /// /// Adding the same `char` more than once is a no-op. /// /// ### Note: Changes Migration Checksum /// This will change the checksum of resolved migrations, /// which may cause problems with existing deployments. /// /// **Use at your own risk.** pub fn ignore_char(&mut self, c: char) -> &mut Self { self.ignored_chars.insert(c); self } /// Ignore one or more characters when hashing migrations. /// /// The migration SQL string itself will still contain these characters, /// but they will not be included when calculating the checksum. /// /// This can be used to ignore whitespace characters so changing formatting /// does not change the checksum. /// /// Adding the same `char` more than once is a no-op. /// /// ### Note: Changes Migration Checksum /// This will change the checksum of resolved migrations, /// which may cause problems with existing deployments. /// /// **Use at your own risk.** pub fn ignore_chars(&mut self, chars: impl IntoIterator) -> &mut Self { self.ignored_chars.extend(chars); self } /// Iterate over the set of ignored characters. /// /// Duplicate `char`s are not included. pub fn ignored_chars(&self) -> impl Iterator + '_ { self.ignored_chars.iter().copied() } } // FIXME: paths should just be part of `Migration` but we can't add a field backwards compatibly // since it's `#[non_exhaustive]`. #[doc(hidden)] pub fn resolve_blocking(path: &Path) -> Result, ResolveError> { resolve_blocking_with_config(path, &ResolveConfig::new()) } #[doc(hidden)] pub fn resolve_blocking_with_config( path: &Path, config: &ResolveConfig, ) -> Result, ResolveError> { let path = path.canonicalize().map_err(|e| ResolveError { message: format!("error canonicalizing path {}", path.display()), source: Some(e), })?; let s = fs::read_dir(&path).map_err(|e| ResolveError { message: format!("error reading migration directory {}", path.display()), source: Some(e), })?; let mut migrations = Vec::new(); for res in s { let entry = res.map_err(|e| ResolveError { message: format!( "error reading contents of migration directory {}", path.display() ), source: Some(e), })?; let entry_path = entry.path(); let metadata = fs::metadata(&entry_path).map_err(|e| ResolveError { message: format!( "error getting metadata of migration path {}", entry_path.display() ), source: Some(e), })?; if !metadata.is_file() { // not a file; ignore continue; } let file_name = entry.file_name(); // This is arguably the wrong choice, // but it really only matters for parsing the version and description. // // Using `.to_str()` and returning an error if the filename is not UTF-8 // would be a breaking change. let file_name = file_name.to_string_lossy(); let parts = file_name.splitn(2, '_').collect::>(); if parts.len() != 2 || !parts[1].ends_with(".sql") { // not of the format: _..sql; ignore continue; } let version: i64 = parts[0].parse() .map_err(|_e| ResolveError { message: format!("error parsing migration filename {file_name:?}; expected integer version prefix (e.g. `01_foo.sql`)"), source: None, })?; let migration_type = MigrationType::from_filename(parts[1]); // remove the `.sql` and replace `_` with ` ` let description = parts[1] .trim_end_matches(migration_type.suffix()) .replace('_', " ") .to_owned(); let sql = fs::read_to_string(&entry_path).map_err(|e| ResolveError { message: format!( "error reading contents of migration {}: {e}", entry_path.display() ), source: Some(e), })?; // opt-out of migration transaction let no_tx = sql.starts_with("-- no-transaction"); let checksum = checksum_with(&sql, &config.ignored_chars); migrations.push(( Migration::with_checksum( version, Cow::Owned(description), migration_type, AssertSqlSafe(sql).into_sql_str(), checksum.into(), no_tx, ), entry_path, )); } // Ensure that we are sorted by version in ascending order. migrations.sort_by_key(|(m, _)| m.version); Ok(migrations) } fn checksum_with(sql: &str, ignored_chars: &BTreeSet) -> Vec { if ignored_chars.is_empty() { // This is going to be much faster because it doesn't have to UTF-8 decode `sql`. return migration::checksum(sql); } migration::checksum_fragments(sql.split(|c| ignored_chars.contains(&c))) } #[test] fn checksum_with_ignored_chars() { // Ensure that `checksum_with` returns the same digest for a given set of ignored chars // as the equivalent string with the characters removed. let ignored_chars = [ ' ', '\t', '\r', '\n', // Zero-width non-breaking space (ZWNBSP), often added as a magic-number at the beginning // of UTF-8 encoded files as a byte-order mark (BOM): // https://en.wikipedia.org/wiki/Byte_order_mark '\u{FEFF}', ]; // Copied from `examples/postgres/axum-social-with-tests/migrations/3_comment.sql` let sql = "\ \u{FEFF}create table comment (\r\n\ \tcomment_id uuid primary key default gen_random_uuid(),\r\n\ \tpost_id uuid not null references post(post_id),\r\n\ \tuser_id uuid not null references \"user\"(user_id),\r\n\ \tcontent text not null,\r\n\ \tcreated_at timestamptz not null default now()\r\n\ );\r\n\ \r\n\ create index on comment(post_id, created_at);\r\n\ "; let stripped_sql = sql.replace(&ignored_chars[..], ""); let ignored_chars = BTreeSet::from(ignored_chars); let digest_ignored = checksum_with(sql, &ignored_chars); let digest_stripped = migration::checksum(&stripped_sql); assert_eq!(digest_ignored, digest_stripped); } ================================================ FILE: sqlx-core/src/net/mod.rs ================================================ mod socket; pub mod tls; pub use socket::{ connect_tcp, connect_uds, BufferedSocket, Socket, SocketIntoBox, WithSocket, WriteBuffer, }; ================================================ FILE: sqlx-core/src/net/socket/buffered.rs ================================================ use crate::error::Error; use crate::net::Socket; use bytes::BytesMut; use std::ops::ControlFlow; use std::{cmp, io}; use crate::io::{AsyncRead, AsyncReadExt, ProtocolDecode, ProtocolEncode}; // Tokio, async-std, and std all use this as the default capacity for their buffered I/O. const DEFAULT_BUF_SIZE: usize = 8192; pub struct BufferedSocket { socket: S, write_buf: WriteBuffer, read_buf: ReadBuffer, } pub struct WriteBuffer { buf: Vec, bytes_written: usize, bytes_flushed: usize, } pub struct ReadBuffer { read: BytesMut, available: BytesMut, } impl BufferedSocket { pub fn new(socket: S) -> Self where S: Sized, { BufferedSocket { socket, write_buf: WriteBuffer { buf: Vec::with_capacity(DEFAULT_BUF_SIZE), bytes_written: 0, bytes_flushed: 0, }, read_buf: ReadBuffer { read: BytesMut::new(), available: BytesMut::with_capacity(DEFAULT_BUF_SIZE), }, } } pub async fn read_buffered(&mut self, len: usize) -> Result { self.try_read(|buf| { Ok(if buf.len() < len { ControlFlow::Continue(len) } else { ControlFlow::Break(buf.split_to(len)) }) }) .await } /// Retryable read operation. /// /// The callback should check the contents of the buffer passed to it and either: /// /// * Remove a full message from the buffer and return [`ControlFlow::Break`], or: /// * Return [`ControlFlow::Continue`] with the expected _total_ length of the buffer, /// _without_ modifying it. /// /// Cancel-safe as long as the callback does not modify the passed `BytesMut` /// before returning [`ControlFlow::Continue`]. pub async fn try_read(&mut self, mut try_read: F) -> Result where F: FnMut(&mut BytesMut) -> Result, Error>, { loop { let read_len = match try_read(&mut self.read_buf.read)? { ControlFlow::Continue(read_len) => read_len, ControlFlow::Break(ret) => return Ok(ret), }; self.read_buf.read(read_len, &mut self.socket).await?; } } pub fn write_buffer(&self) -> &WriteBuffer { &self.write_buf } pub fn write_buffer_mut(&mut self) -> &mut WriteBuffer { &mut self.write_buf } pub async fn read<'de, T>(&mut self, byte_len: usize) -> Result where T: ProtocolDecode<'de, ()>, { self.read_with(byte_len, ()).await } pub async fn read_with<'de, T, C>(&mut self, byte_len: usize, context: C) -> Result where T: ProtocolDecode<'de, C>, { T::decode_with(self.read_buffered(byte_len).await?.freeze(), context) } #[inline(always)] pub fn write<'en, T>(&mut self, value: T) -> Result<(), Error> where T: ProtocolEncode<'en, ()>, { self.write_with(value, ()) } #[inline(always)] pub fn write_with<'en, T, C>(&mut self, value: T, context: C) -> Result<(), Error> where T: ProtocolEncode<'en, C>, { value.encode_with(self.write_buf.buf_mut(), context)?; self.write_buf.bytes_written = self.write_buf.buf.len(); self.write_buf.sanity_check(); Ok(()) } pub async fn flush(&mut self) -> io::Result<()> { while !self.write_buf.is_empty() { let written = self.socket.write(self.write_buf.get()).await?; self.write_buf.consume(written); self.write_buf.sanity_check(); } self.socket.flush().await?; Ok(()) } pub async fn shutdown(&mut self) -> io::Result<()> { self.flush().await?; self.socket.shutdown().await } pub fn shrink_buffers(&mut self) { // Won't drop data still in the buffer. self.write_buf.shrink(); self.read_buf.shrink(); } pub fn into_inner(self) -> S { self.socket } pub fn boxed(self) -> BufferedSocket> { BufferedSocket { socket: Box::new(self.socket), write_buf: self.write_buf, read_buf: self.read_buf, } } } impl WriteBuffer { fn sanity_check(&self) { assert_ne!(self.buf.capacity(), 0); assert!(self.bytes_written <= self.buf.len()); assert!(self.bytes_flushed <= self.bytes_written); } pub fn buf_mut(&mut self) -> &mut Vec { self.buf.truncate(self.bytes_written); self.sanity_check(); &mut self.buf } pub fn init_remaining_mut(&mut self) -> &mut [u8] { self.buf.resize(self.buf.capacity(), 0); self.sanity_check(); &mut self.buf[self.bytes_written..] } pub fn put_slice(&mut self, slice: &[u8]) { // If we already have an initialized area that can fit the slice, // don't change `self.buf.len()` if let Some(dest) = self.buf[self.bytes_written..].get_mut(..slice.len()) { dest.copy_from_slice(slice); } else { self.buf.truncate(self.bytes_written); self.buf.extend_from_slice(slice); } self.advance(slice.len()); self.sanity_check(); } pub fn advance(&mut self, amt: usize) { let new_bytes_written = self .bytes_written .checked_add(amt) .expect("self.bytes_written + amt overflowed"); assert!(new_bytes_written <= self.buf.len()); self.bytes_written = new_bytes_written; self.sanity_check(); } /// Read into the buffer from `source`, returning the number of bytes read. /// /// The buffer is automatically advanced by the number of bytes read. pub async fn read_from(&mut self, mut source: impl AsyncRead + Unpin) -> io::Result { let read = match () { // Tokio lets us read into the buffer without zeroing first #[cfg(feature = "_rt-tokio")] _ => source.read_buf(self.buf_mut()).await?, #[cfg(not(feature = "_rt-tokio"))] _ => source.read(self.init_remaining_mut()).await?, }; if read > 0 { self.advance(read); } Ok(read) } pub fn is_empty(&self) -> bool { self.bytes_flushed >= self.bytes_written } pub fn is_full(&self) -> bool { self.bytes_written == self.buf.len() } pub fn get(&self) -> &[u8] { &self.buf[self.bytes_flushed..self.bytes_written] } pub fn get_mut(&mut self) -> &mut [u8] { &mut self.buf[self.bytes_flushed..self.bytes_written] } pub fn shrink(&mut self) { if self.bytes_flushed > 0 { // Move any data that remains to be flushed to the beginning of the buffer, // if necessary. self.buf .copy_within(self.bytes_flushed..self.bytes_written, 0); self.bytes_written -= self.bytes_flushed; self.bytes_flushed = 0 } // Drop excess capacity. self.buf .truncate(cmp::max(self.bytes_written, DEFAULT_BUF_SIZE)); self.buf.shrink_to_fit(); } fn consume(&mut self, amt: usize) { let new_bytes_flushed = self .bytes_flushed .checked_add(amt) .expect("self.bytes_flushed + amt overflowed"); assert!(new_bytes_flushed <= self.bytes_written); self.bytes_flushed = new_bytes_flushed; if self.bytes_flushed == self.bytes_written { // Reset cursors to zero if we've consumed the whole buffer self.bytes_flushed = 0; self.bytes_written = 0; } self.sanity_check(); } } impl ReadBuffer { async fn read(&mut self, len: usize, socket: &mut impl Socket) -> io::Result<()> { // Because of how `BytesMut` works, we should only be shifting capacity back and forth // between `read` and `available` unless we have to read an oversize message. while self.read.len() < len { self.reserve(len - self.read.len()); let read = socket.read(&mut self.available).await?; if read == 0 { return Err(io::Error::new( io::ErrorKind::UnexpectedEof, format!( "expected to read {} bytes, got {} bytes at EOF", len, self.read.len() ), )); } self.advance(read); } Ok(()) } fn reserve(&mut self, amt: usize) { if let Some(additional) = amt.checked_sub(self.available.capacity()) { self.available.reserve(additional); } } fn advance(&mut self, amt: usize) { self.read.unsplit(self.available.split_to(amt)); } fn shrink(&mut self) { if self.available.capacity() > DEFAULT_BUF_SIZE { // `BytesMut` doesn't have a way to shrink its capacity, // but we only use `available` for spare capacity anyway so we can just replace it. // // If `self.read` still contains data on the next call to `advance` then this might // force a memcpy as they'll no longer be pointing to the same allocation, // but that's kind of unavoidable. // // The `async-std` impl of `Socket` will also need to re-zero the buffer, // but that's also kind of unavoidable. // // We should be warning the user not to call this often. self.available = BytesMut::with_capacity(DEFAULT_BUF_SIZE); } } } ================================================ FILE: sqlx-core/src/net/socket/mod.rs ================================================ use std::future::Future; use std::io; use std::path::Path; use std::pin::Pin; use std::task::{ready, Context, Poll}; pub use buffered::{BufferedSocket, WriteBuffer}; use bytes::BufMut; use cfg_if::cfg_if; use crate::io::ReadBuf; mod buffered; pub trait Socket: Send + Sync + Unpin + 'static { fn try_read(&mut self, buf: &mut dyn ReadBuf) -> io::Result; fn try_write(&mut self, buf: &[u8]) -> io::Result; fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll>; fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll>; fn poll_flush(&mut self, _cx: &mut Context<'_>) -> Poll> { // `flush()` is a no-op for TCP/UDS Poll::Ready(Ok(())) } fn poll_shutdown(&mut self, cx: &mut Context<'_>) -> Poll>; fn read<'a, B: ReadBuf>(&'a mut self, buf: &'a mut B) -> Read<'a, Self, B> where Self: Sized, { Read { socket: self, buf } } fn write<'a>(&'a mut self, buf: &'a [u8]) -> Write<'a, Self> where Self: Sized, { Write { socket: self, buf } } fn flush(&mut self) -> Flush<'_, Self> where Self: Sized, { Flush { socket: self } } fn shutdown(&mut self) -> Shutdown<'_, Self> where Self: Sized, { Shutdown { socket: self } } } pub struct Read<'a, S: ?Sized, B> { socket: &'a mut S, buf: &'a mut B, } impl Future for Read<'_, S, B> where S: Socket, B: ReadBuf, { type Output = io::Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = &mut *self; while this.buf.has_remaining_mut() { match this.socket.try_read(&mut *this.buf) { Err(e) if e.kind() == io::ErrorKind::WouldBlock => { ready!(this.socket.poll_read_ready(cx))?; } ready => return Poll::Ready(ready), } } Poll::Ready(Ok(0)) } } pub struct Write<'a, S: ?Sized> { socket: &'a mut S, buf: &'a [u8], } impl Future for Write<'_, S> where S: Socket, { type Output = io::Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = &mut *self; while !this.buf.is_empty() { match this.socket.try_write(this.buf) { Err(e) if e.kind() == io::ErrorKind::WouldBlock => { ready!(this.socket.poll_write_ready(cx))?; } ready => return Poll::Ready(ready), } } Poll::Ready(Ok(0)) } } pub struct Flush<'a, S: ?Sized> { socket: &'a mut S, } impl Future for Flush<'_, S> { type Output = io::Result<()>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.socket.poll_flush(cx) } } pub struct Shutdown<'a, S: ?Sized> { socket: &'a mut S, } impl Future for Shutdown<'_, S> where S: Socket, { type Output = io::Result<()>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.socket.poll_shutdown(cx) } } pub trait WithSocket { type Output; fn with_socket(self, socket: S) -> impl Future + Send; } pub struct SocketIntoBox; impl WithSocket for SocketIntoBox { type Output = Box; async fn with_socket(self, socket: S) -> Self::Output { Box::new(socket) } } impl Socket for Box { fn try_read(&mut self, buf: &mut dyn ReadBuf) -> io::Result { (**self).try_read(buf) } fn try_write(&mut self, buf: &[u8]) -> io::Result { (**self).try_write(buf) } fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { (**self).poll_read_ready(cx) } fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { (**self).poll_write_ready(cx) } fn poll_flush(&mut self, cx: &mut Context<'_>) -> Poll> { (**self).poll_flush(cx) } fn poll_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> { (**self).poll_shutdown(cx) } } pub async fn connect_tcp( host: &str, port: u16, with_socket: Ws, ) -> crate::Result { #[cfg(feature = "_rt-tokio")] if crate::rt::rt_tokio::available() { return Ok(with_socket .with_socket(tokio::net::TcpStream::connect((host, port)).await?) .await); } cfg_if! { if #[cfg(feature = "_rt-async-io")] { Ok(with_socket.with_socket(connect_tcp_async_io(host, port).await?).await) } else { crate::rt::missing_rt((host, port, with_socket)) } } } /// Open a TCP socket to `host` and `port`. /// /// If `host` is a hostname, attempt to connect to each address it resolves to. /// /// This implements the same behavior as [`tokio::net::TcpStream::connect()`]. #[cfg(feature = "_rt-async-io")] async fn connect_tcp_async_io(host: &str, port: u16) -> crate::Result { use async_io::Async; use std::net::{IpAddr, TcpStream, ToSocketAddrs}; // IPv6 addresses in URLs will be wrapped in brackets and the `url` crate doesn't trim those. let host = host.trim_matches(&['[', ']'][..]); if let Ok(addr) = host.parse::() { return Ok(Async::::connect((addr, port)).await?); } let host = host.to_string(); let addresses = crate::rt::spawn_blocking(move || { let addr = (host.as_str(), port); ToSocketAddrs::to_socket_addrs(&addr) }) .await?; let mut last_err = None; // Loop through all the Socket Addresses that the hostname resolves to for socket_addr in addresses { match Async::::connect(socket_addr).await { Ok(stream) => return Ok(stream), Err(e) => last_err = Some(e), } } // If we reach this point, it means we failed to connect to any of the addresses. // Return the last error we encountered, or a custom error if the hostname didn't resolve to any address. Err(last_err .unwrap_or_else(|| { io::Error::new( io::ErrorKind::AddrNotAvailable, "Hostname did not resolve to any addresses", ) }) .into()) } /// Connect a Unix Domain Socket at the given path. /// /// Returns an error if Unix Domain Sockets are not supported on this platform. pub async fn connect_uds, Ws: WithSocket>( path: P, with_socket: Ws, ) -> crate::Result { #[cfg(unix)] { #[cfg(feature = "_rt-tokio")] if crate::rt::rt_tokio::available() { use tokio::net::UnixStream; let stream = UnixStream::connect(path).await?; return Ok(with_socket.with_socket(stream).await); } cfg_if! { if #[cfg(feature = "_rt-async-io")] { use async_io::Async; use std::os::unix::net::UnixStream; let stream = Async::::connect(path).await?; Ok(with_socket.with_socket(stream).await) } else { crate::rt::missing_rt((path, with_socket)) } } } #[cfg(not(unix))] { drop((path, with_socket)); Err(io::Error::new( io::ErrorKind::Unsupported, "Unix domain sockets are not supported on this platform", ) .into()) } } ================================================ FILE: sqlx-core/src/net/tls/mod.rs ================================================ #![allow(dead_code)] use std::path::PathBuf; use crate::error::Error; use crate::net::socket::WithSocket; use crate::net::Socket; #[cfg(feature = "_tls-rustls")] mod tls_rustls; #[cfg(feature = "_tls-native-tls")] mod tls_native_tls; mod util; /// X.509 Certificate input, either a file path or a PEM encoded inline certificate(s). #[derive(Clone, Debug)] pub enum CertificateInput { /// PEM encoded certificate(s) Inline(Vec), /// Path to a file containing PEM encoded certificate(s) File(PathBuf), } impl From for CertificateInput { fn from(value: String) -> Self { // Leading and trailing whitespace/newlines let trimmed = value.trim(); // Heuristic for PEM encoded inputs: // https://tools.ietf.org/html/rfc7468 if trimmed.starts_with("-----BEGIN") && trimmed.ends_with("-----") { CertificateInput::Inline(value.as_bytes().to_vec()) } else { CertificateInput::File(PathBuf::from(value)) } } } impl CertificateInput { async fn data(&self) -> Result, std::io::Error> { use crate::fs; match self { CertificateInput::Inline(v) => Ok(v.clone()), CertificateInput::File(path) => fs::read(path).await, } } } impl std::fmt::Display for CertificateInput { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { CertificateInput::Inline(v) => write!(f, "{}", String::from_utf8_lossy(v.as_slice())), CertificateInput::File(path) => write!(f, "file: {}", path.display()), } } } pub struct TlsConfig<'a> { pub accept_invalid_certs: bool, pub accept_invalid_hostnames: bool, pub hostname: &'a str, pub root_cert_path: Option<&'a CertificateInput>, pub client_cert_path: Option<&'a CertificateInput>, pub client_key_path: Option<&'a CertificateInput>, } pub async fn handshake( socket: S, config: TlsConfig<'_>, with_socket: Ws, ) -> crate::Result where S: Socket, Ws: WithSocket, { #[cfg(feature = "_tls-native-tls")] return Ok(with_socket .with_socket(tls_native_tls::handshake(socket, config).await?) .await); #[cfg(all(feature = "_tls-rustls", not(feature = "_tls-native-tls")))] return Ok(with_socket .with_socket(tls_rustls::handshake(socket, config).await?) .await); #[cfg(not(any(feature = "_tls-native-tls", feature = "_tls-rustls")))] { drop((socket, config, with_socket)); panic!("one of the `runtime-*-native-tls` or `runtime-*-rustls` features must be enabled") } } pub fn available() -> bool { cfg!(any(feature = "_tls-native-tls", feature = "_tls-rustls")) } pub fn error_if_unavailable() -> crate::Result<()> { if !available() { return Err(Error::tls( "TLS upgrade required by connect options \ but SQLx was built without TLS support enabled", )); } Ok(()) } ================================================ FILE: sqlx-core/src/net/tls/tls_native_tls.rs ================================================ use std::io::{self, Read, Write}; use crate::io::ReadBuf; use crate::net::tls::util::StdSocket; use crate::net::tls::TlsConfig; use crate::net::Socket; use crate::rt; use crate::Error; use native_tls::{HandshakeError, Identity}; use std::task::{Context, Poll}; pub struct NativeTlsSocket { stream: native_tls::TlsStream>, } impl Socket for NativeTlsSocket { fn try_read(&mut self, buf: &mut dyn ReadBuf) -> io::Result { self.stream.read(buf.init_mut()) } fn try_write(&mut self, buf: &[u8]) -> io::Result { self.stream.write(buf) } fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.stream.get_mut().poll_ready(cx) } fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.stream.get_mut().poll_ready(cx) } fn poll_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> { match self.stream.shutdown() { Err(e) if e.kind() == io::ErrorKind::WouldBlock => self.stream.get_mut().poll_ready(cx), ready => Poll::Ready(ready), } } } pub async fn handshake( socket: S, config: TlsConfig<'_>, ) -> crate::Result> { let mut builder = native_tls::TlsConnector::builder(); builder .danger_accept_invalid_certs(config.accept_invalid_certs) .danger_accept_invalid_hostnames(config.accept_invalid_hostnames); if let Some(root_cert_path) = config.root_cert_path { let data = root_cert_path.data().await?; builder.add_root_certificate(native_tls::Certificate::from_pem(&data).map_err(Error::tls)?); } // authentication using user's key-file and its associated certificate if let (Some(cert_path), Some(key_path)) = (config.client_cert_path, config.client_key_path) { let cert_path = cert_path.data().await?; let key_path = key_path.data().await?; let identity = Identity::from_pkcs8(&cert_path, &key_path).map_err(Error::tls)?; builder.identity(identity); } // The openssl TlsConnector synchronously loads certificates from files. // Loading these files can block for tens of milliseconds. let connector = rt::spawn_blocking(move || builder.build()) .await .map_err(Error::tls)?; let mut mid_handshake = match connector.connect(config.hostname, StdSocket::new(socket)) { Ok(tls_stream) => return Ok(NativeTlsSocket { stream: tls_stream }), Err(HandshakeError::Failure(e)) => return Err(Error::tls(e)), Err(HandshakeError::WouldBlock(mid_handshake)) => mid_handshake, }; loop { mid_handshake.get_mut().ready().await?; match mid_handshake.handshake() { Ok(tls_stream) => return Ok(NativeTlsSocket { stream: tls_stream }), Err(HandshakeError::Failure(e)) => return Err(Error::tls(e)), Err(HandshakeError::WouldBlock(mid_handshake_)) => { mid_handshake = mid_handshake_; } } } } ================================================ FILE: sqlx-core/src/net/tls/tls_rustls.rs ================================================ use std::future; use std::io::{self, Read, Write}; use std::sync::Arc; use std::task::{ready, Context, Poll}; use rustls::{ client::{ danger::{ServerCertVerified, ServerCertVerifier}, WebPkiServerVerifier, }, crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}, pki_types::{ pem::{self, PemObject}, CertificateDer, PrivateKeyDer, ServerName, UnixTime, }, CertificateError, ClientConfig, ClientConnection, Error as TlsError, RootCertStore, }; use crate::error::Error; use crate::io::ReadBuf; use crate::net::tls::util::StdSocket; use crate::net::tls::TlsConfig; use crate::net::Socket; pub struct RustlsSocket { inner: StdSocket, state: ClientConnection, close_notify_sent: bool, } impl RustlsSocket { fn poll_complete_io(&mut self, cx: &mut Context<'_>) -> Poll> { loop { match self.state.complete_io(&mut self.inner) { Err(e) if e.kind() == io::ErrorKind::WouldBlock => { ready!(self.inner.poll_ready(cx))?; } ready => return Poll::Ready(ready.map(|_| ())), } } } async fn complete_io(&mut self) -> io::Result<()> { future::poll_fn(|cx| self.poll_complete_io(cx)).await } } impl Socket for RustlsSocket { fn try_read(&mut self, buf: &mut dyn ReadBuf) -> io::Result { self.state.reader().read(buf.init_mut()) } fn try_write(&mut self, buf: &[u8]) -> io::Result { match self.state.writer().write(buf) { // Returns a zero-length write when the buffer is full. Ok(0) => Err(io::ErrorKind::WouldBlock.into()), other => other, } } fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.poll_complete_io(cx) } fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.poll_complete_io(cx) } fn poll_flush(&mut self, cx: &mut Context<'_>) -> Poll> { self.poll_complete_io(cx) } fn poll_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> { if !self.close_notify_sent { self.state.send_close_notify(); self.close_notify_sent = true; } ready!(self.poll_complete_io(cx))?; // Server can close socket as soon as it receives the connection shutdown request. // We shouldn't expect it to stick around for the TLS session to close cleanly. // https://security.stackexchange.com/a/82034 let _ = ready!(self.inner.socket.poll_shutdown(cx)); Poll::Ready(Ok(())) } } pub async fn handshake(socket: S, tls_config: TlsConfig<'_>) -> Result, Error> where S: Socket, { #[cfg(all( feature = "_tls-rustls-aws-lc-rs", not(feature = "_tls-rustls-ring-webpki"), not(feature = "_tls-rustls-ring-native-roots") ))] let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); #[cfg(any( feature = "_tls-rustls-ring-webpki", feature = "_tls-rustls-ring-native-roots" ))] let provider = Arc::new(rustls::crypto::ring::default_provider()); // Unwrapping is safe here because we use a default provider. let config = ClientConfig::builder_with_provider(provider.clone()) .with_safe_default_protocol_versions() .unwrap(); // authentication using user's key and its associated certificate let user_auth = match (tls_config.client_cert_path, tls_config.client_key_path) { (Some(cert_path), Some(key_path)) => { let cert_chain = certs_from_pem(cert_path.data().await?)?; let key_der = private_key_from_pem(key_path.data().await?)?; Some((cert_chain, key_der)) } (None, None) => None, (_, _) => { return Err(Error::Configuration( "user auth key and certs must be given together".into(), )) } }; let config = if tls_config.accept_invalid_certs { if let Some(user_auth) = user_auth { config .dangerous() .with_custom_certificate_verifier(Arc::new(DummyTlsVerifier { provider })) .with_client_auth_cert(user_auth.0, user_auth.1) .map_err(Error::tls)? } else { config .dangerous() .with_custom_certificate_verifier(Arc::new(DummyTlsVerifier { provider })) .with_no_client_auth() } } else { let mut cert_store = import_root_certs(); if let Some(ca) = tls_config.root_cert_path { let data = ca.data().await?; for result in CertificateDer::pem_slice_iter(&data) { let Ok(cert) = result else { return Err(Error::Tls(format!("Invalid certificate {ca}").into())); }; cert_store.add(cert).map_err(|err| Error::Tls(err.into()))?; } } if tls_config.accept_invalid_hostnames { let verifier = WebPkiServerVerifier::builder(Arc::new(cert_store)) .build() .map_err(|err| Error::Tls(err.into()))?; if let Some(user_auth) = user_auth { config .dangerous() .with_custom_certificate_verifier(Arc::new(NoHostnameTlsVerifier { verifier })) .with_client_auth_cert(user_auth.0, user_auth.1) .map_err(Error::tls)? } else { config .dangerous() .with_custom_certificate_verifier(Arc::new(NoHostnameTlsVerifier { verifier })) .with_no_client_auth() } } else if let Some(user_auth) = user_auth { config .with_root_certificates(cert_store) .with_client_auth_cert(user_auth.0, user_auth.1) .map_err(Error::tls)? } else { config .with_root_certificates(cert_store) .with_no_client_auth() } }; let host = ServerName::try_from(tls_config.hostname.to_owned()).map_err(Error::tls)?; let mut socket = RustlsSocket { inner: StdSocket::new(socket), state: ClientConnection::new(Arc::new(config), host).map_err(Error::tls)?, close_notify_sent: false, }; // Performs the TLS handshake or bails socket.complete_io().await?; Ok(socket) } fn certs_from_pem(pem: Vec) -> Result>, Error> { CertificateDer::pem_slice_iter(&pem) .map(|result| result.map_err(|err| Error::Tls(err.into()))) .collect() } fn private_key_from_pem(pem: Vec) -> Result, Error> { match PrivateKeyDer::from_pem_slice(&pem) { Ok(key) => Ok(key), Err(pem::Error::NoItemsFound) => Err(Error::Configuration("no keys found pem file".into())), Err(e) => Err(Error::Configuration(e.to_string().into())), } } #[cfg(all(feature = "webpki-roots", not(feature = "rustls-native-certs")))] fn import_root_certs() -> RootCertStore { RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()) } #[cfg(feature = "rustls-native-certs")] fn import_root_certs() -> RootCertStore { let mut root_cert_store = RootCertStore::empty(); let load_results = rustls_native_certs::load_native_certs(); for e in load_results.errors { log::warn!("Error loading native certificates: {e:?}"); } for cert in load_results.certs { if let Err(e) = root_cert_store.add(cert) { log::warn!("rustls failed to parse native certificate: {e:?}"); } } root_cert_store } // Not currently used but allows for a "tls-rustls-no-roots" feature. #[cfg(not(any(feature = "rustls-native-certs", feature = "webpki-roots")))] fn import_root_certs() -> RootCertStore { RootCertStore::empty() } #[derive(Debug)] struct DummyTlsVerifier { provider: Arc, } impl ServerCertVerifier for DummyTlsVerifier { fn verify_server_cert( &self, _end_entity: &CertificateDer<'_>, _intermediates: &[CertificateDer<'_>], _server_name: &ServerName<'_>, _ocsp_response: &[u8], _now: UnixTime, ) -> Result { Ok(ServerCertVerified::assertion()) } fn verify_tls12_signature( &self, message: &[u8], cert: &CertificateDer<'_>, dss: &rustls::DigitallySignedStruct, ) -> Result { verify_tls12_signature( message, cert, dss, &self.provider.signature_verification_algorithms, ) } fn verify_tls13_signature( &self, message: &[u8], cert: &CertificateDer<'_>, dss: &rustls::DigitallySignedStruct, ) -> Result { verify_tls13_signature( message, cert, dss, &self.provider.signature_verification_algorithms, ) } fn supported_verify_schemes(&self) -> Vec { self.provider .signature_verification_algorithms .supported_schemes() } } #[derive(Debug)] pub struct NoHostnameTlsVerifier { verifier: Arc, } impl ServerCertVerifier for NoHostnameTlsVerifier { fn verify_server_cert( &self, end_entity: &CertificateDer<'_>, intermediates: &[CertificateDer<'_>], server_name: &ServerName<'_>, ocsp_response: &[u8], now: UnixTime, ) -> Result { match self.verifier.verify_server_cert( end_entity, intermediates, server_name, ocsp_response, now, ) { Err(TlsError::InvalidCertificate( CertificateError::NotValidForName | CertificateError::NotValidForNameContext { .. }, )) => Ok(ServerCertVerified::assertion()), res => res, } } fn verify_tls12_signature( &self, message: &[u8], cert: &CertificateDer<'_>, dss: &rustls::DigitallySignedStruct, ) -> Result { self.verifier.verify_tls12_signature(message, cert, dss) } fn verify_tls13_signature( &self, message: &[u8], cert: &CertificateDer<'_>, dss: &rustls::DigitallySignedStruct, ) -> Result { self.verifier.verify_tls13_signature(message, cert, dss) } fn supported_verify_schemes(&self) -> Vec { self.verifier.supported_verify_schemes() } } ================================================ FILE: sqlx-core/src/net/tls/util.rs ================================================ use crate::net::Socket; use std::future; use std::io::{self, Read, Write}; use std::task::{ready, Context, Poll}; pub struct StdSocket { pub socket: S, wants_read: bool, wants_write: bool, } impl StdSocket { pub fn new(socket: S) -> Self { Self { socket, wants_read: false, wants_write: false, } } pub fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { if self.wants_write { ready!(self.socket.poll_write_ready(cx))?; self.wants_write = false; } if self.wants_read { ready!(self.socket.poll_read_ready(cx))?; self.wants_read = false; } Poll::Ready(Ok(())) } pub async fn ready(&mut self) -> io::Result<()> { future::poll_fn(|cx| self.poll_ready(cx)).await } } impl Read for StdSocket { fn read(&mut self, mut buf: &mut [u8]) -> io::Result { self.wants_read = true; let read = self.socket.try_read(&mut buf)?; self.wants_read = false; Ok(read) } } impl Write for StdSocket { fn write(&mut self, buf: &[u8]) -> io::Result { self.wants_write = true; let written = self.socket.try_write(buf)?; self.wants_write = false; Ok(written) } fn flush(&mut self) -> io::Result<()> { // NOTE: TCP sockets and unix sockets are both no-ops for flushes Ok(()) } } ================================================ FILE: sqlx-core/src/pool/connection.rs ================================================ use std::fmt::{self, Debug, Formatter}; use std::future::{self, Future}; use std::ops::{Deref, DerefMut}; use std::sync::Arc; use std::time::{Duration, Instant}; use crate::sync::AsyncSemaphoreReleaser; use crate::connection::Connection; use crate::database::Database; use crate::error::Error; use super::inner::{is_beyond_max_lifetime, DecrementSizeGuard, PoolInner}; use crate::pool::options::PoolConnectionMetadata; const CLOSE_ON_DROP_TIMEOUT: Duration = Duration::from_secs(5); /// A connection managed by a [`Pool`][crate::pool::Pool]. /// /// Will be returned to the pool on-drop. pub struct PoolConnection { live: Option>, close_on_drop: bool, pub(crate) pool: Arc>, } pub(super) struct Live { pub(super) raw: DB::Connection, pub(super) created_at: Instant, } pub(super) struct Idle { pub(super) live: Live, pub(super) idle_since: Instant, } /// RAII wrapper for connections being handled by functions that may drop them pub(super) struct Floating { pub(super) inner: C, pub(super) guard: DecrementSizeGuard, } const EXPECT_MSG: &str = "BUG: inner connection already taken!"; impl Debug for PoolConnection { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { // TODO: Show the type name of the connection ? f.debug_struct("PoolConnection").finish() } } impl Deref for PoolConnection { type Target = DB::Connection; fn deref(&self) -> &Self::Target { &self.live.as_ref().expect(EXPECT_MSG).raw } } impl DerefMut for PoolConnection { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.live.as_mut().expect(EXPECT_MSG).raw } } impl AsRef for PoolConnection { fn as_ref(&self) -> &DB::Connection { self } } impl AsMut for PoolConnection { fn as_mut(&mut self) -> &mut DB::Connection { self } } impl PoolConnection { /// Close this connection, allowing the pool to open a replacement. /// /// Equivalent to calling [`.detach()`] then [`.close()`], but the connection permit is retained /// for the duration so that the pool may not exceed `max_connections`. /// /// [`.detach()`]: PoolConnection::detach /// [`.close()`]: Connection::close pub async fn close(mut self) -> Result<(), Error> { let floating = self.take_live().float(self.pool.clone()); floating.inner.raw.close().await } /// Close this connection on-drop, instead of returning it to the pool. /// /// May be used in cases where waiting for the [`.close()`][Self::close] call /// to complete is unacceptable, but you still want the connection to be closed gracefully /// so that the server can clean up resources. #[inline(always)] pub fn close_on_drop(&mut self) { self.close_on_drop = true; } /// Detach this connection from the pool, allowing it to open a replacement. /// /// Note that if your application uses a single shared pool, this /// effectively lets the application exceed the [`max_connections`] setting. /// /// If [`min_connections`] is nonzero, a task will be spawned to replace this connection. /// /// If you want the pool to treat this connection as permanently checked-out, /// use [`.leak()`][Self::leak] instead. /// /// [`max_connections`]: crate::pool::PoolOptions::max_connections /// [`min_connections`]: crate::pool::PoolOptions::min_connections pub fn detach(mut self) -> DB::Connection { self.take_live().float(self.pool.clone()).detach() } /// Detach this connection from the pool, treating it as permanently checked-out. /// /// This effectively will reduce the maximum capacity of the pool by 1 every time it is used. /// /// If you don't want to impact the pool's capacity, use [`.detach()`][Self::detach] instead. pub fn leak(mut self) -> DB::Connection { self.take_live().raw } fn take_live(&mut self) -> Live { self.live.take().expect(EXPECT_MSG) } /// Test the connection to make sure it is still live before returning it to the pool. /// /// This effectively runs the drop handler eagerly instead of spawning a task to do it. #[doc(hidden)] pub fn return_to_pool(&mut self) -> impl Future + Send + 'static { // float the connection in the pool before we move into the task // in case the returned `Future` isn't executed, like if it's spawned into a dying runtime // https://github.com/launchbadge/sqlx/issues/1396 // Type hints seem to be broken by `Option` combinators in IntelliJ Rust right now (6/22). let floating: Option>> = self.live.take().map(|live| live.float(self.pool.clone())); let pool = self.pool.clone(); async move { let returned_to_pool = if let Some(floating) = floating { floating.return_to_pool().await } else { false }; if !returned_to_pool { pool.min_connections_maintenance(None).await; } } } fn take_and_close(&mut self) -> impl Future + Send + 'static { // float the connection in the pool before we move into the task // in case the returned `Future` isn't executed, like if it's spawned into a dying runtime // https://github.com/launchbadge/sqlx/issues/1396 // Type hints seem to be broken by `Option` combinators in IntelliJ Rust right now (6/22). let floating = self.live.take().map(|live| live.float(self.pool.clone())); let pool = self.pool.clone(); async move { if let Some(floating) = floating { // Don't hold the connection forever if it hangs while trying to close crate::rt::timeout(CLOSE_ON_DROP_TIMEOUT, floating.close()) .await .ok(); } pool.min_connections_maintenance(None).await; } } } impl<'c, DB: Database> crate::acquire::Acquire<'c> for &'c mut PoolConnection { type Database = DB; type Connection = &'c mut ::Connection; #[inline] fn acquire(self) -> futures_core::future::BoxFuture<'c, Result> { Box::pin(future::ready(Ok(&mut **self))) } #[inline] fn begin( self, ) -> futures_core::future::BoxFuture<'c, Result, Error>> { crate::transaction::Transaction::begin(&mut **self, None) } } /// Returns the connection to the [`Pool`][crate::pool::Pool] it was checked-out from. impl Drop for PoolConnection { fn drop(&mut self) { if self.close_on_drop { crate::rt::spawn(self.take_and_close()); return; } // We still need to spawn a task to maintain `min_connections`. if self.live.is_some() || self.pool.options.min_connections > 0 { crate::rt::spawn(self.return_to_pool()); } } } impl Live { pub fn float(self, pool: Arc>) -> Floating { Floating { inner: self, // create a new guard from a previously leaked permit guard: DecrementSizeGuard::new_permit(pool), } } pub fn into_idle(self) -> Idle { Idle { live: self, idle_since: Instant::now(), } } } impl Deref for Idle { type Target = Live; fn deref(&self) -> &Self::Target { &self.live } } impl DerefMut for Idle { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.live } } impl Floating> { pub fn new_live(conn: DB::Connection, guard: DecrementSizeGuard) -> Self { Self { inner: Live { raw: conn, created_at: Instant::now(), }, guard, } } pub fn reattach(self) -> PoolConnection { let Floating { inner, guard } = self; let pool = Arc::clone(&guard.pool); guard.cancel(); PoolConnection { live: Some(inner), close_on_drop: false, pool, } } pub fn release(self) { self.guard.pool.clone().release(self); } /// Return the connection to the pool. /// /// Returns `true` if the connection was successfully returned, `false` if it was closed. async fn return_to_pool(mut self) -> bool { // Immediately close the connection. if self.guard.pool.is_closed() { self.close().await; return false; } // If the connection is beyond max lifetime, close the connection and // immediately create a new connection if is_beyond_max_lifetime(&self.inner, &self.guard.pool.options) { self.close().await; return false; } if let Some(test) = &self.guard.pool.options.after_release { let meta = self.metadata(); match (test)(&mut self.inner.raw, meta).await { Ok(true) => (), Ok(false) => { self.close().await; return false; } Err(error) => { tracing::warn!(%error, "error from `after_release`"); // Connection is broken, don't try to gracefully close as // something weird might happen. self.close_hard().await; return false; } } } // test the connection on-release to ensure it is still viable, // and flush anything time-sensitive like transaction rollbacks // if an Executor future/stream is dropped during an `.await` call, the connection // is likely to be left in an inconsistent state, in which case it should not be // returned to the pool; also of course, if it was dropped due to an error // this is simply a band-aid as SQLx-next connections should be able // to recover from cancellations if let Err(error) = self.raw.ping().await { tracing::warn!( %error, "error occurred while testing the connection on-release", ); // Connection is broken, don't try to gracefully close. self.close_hard().await; false } else { // if the connection is still viable, release it to the pool self.release(); true } } pub async fn close(self) { // This isn't used anywhere that we care about the return value let _ = self.inner.raw.close().await; // `guard` is dropped as intended } pub async fn close_hard(self) { let _ = self.inner.raw.close_hard().await; } pub fn detach(self) -> DB::Connection { self.inner.raw } pub fn into_idle(self) -> Floating> { Floating { inner: self.inner.into_idle(), guard: self.guard, } } pub fn metadata(&self) -> PoolConnectionMetadata { PoolConnectionMetadata { age: self.created_at.elapsed(), idle_for: Duration::ZERO, } } } impl Floating> { pub fn from_idle( idle: Idle, pool: Arc>, permit: AsyncSemaphoreReleaser<'_>, ) -> Self { Self { inner: idle, guard: DecrementSizeGuard::from_permit(pool, permit), } } pub async fn ping(&mut self) -> Result<(), Error> { self.live.raw.ping().await } pub fn into_live(self) -> Floating> { Floating { inner: self.inner.live, guard: self.guard, } } pub async fn close(self) -> DecrementSizeGuard { if let Err(error) = self.inner.live.raw.close().await { tracing::debug!(%error, "error occurred while closing the pool connection"); } self.guard } pub async fn close_hard(self) -> DecrementSizeGuard { let _ = self.inner.live.raw.close_hard().await; self.guard } pub fn metadata(&self) -> PoolConnectionMetadata { // Use a single `now` value for consistency. let now = Instant::now(); PoolConnectionMetadata { // NOTE: the receiver is the later `Instant` and the arg is the earlier // https://github.com/launchbadge/sqlx/issues/1912 age: now.saturating_duration_since(self.created_at), idle_for: now.saturating_duration_since(self.idle_since), } } } impl Deref for Floating { type Target = C; fn deref(&self) -> &Self::Target { &self.inner } } impl DerefMut for Floating { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } ================================================ FILE: sqlx-core/src/pool/executor.rs ================================================ use either::Either; use futures_core::future::BoxFuture; use futures_core::stream::BoxStream; use futures_util::TryStreamExt; use crate::database::Database; use crate::error::Error; use crate::executor::{Execute, Executor}; use crate::pool::Pool; use crate::sql_str::SqlStr; impl<'p, DB: Database> Executor<'p> for &'_ Pool where for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>, { type Database = DB; fn fetch_many<'e, 'q: 'e, E>( self, query: E, ) -> BoxStream<'e, Result, Error>> where E: 'q + Execute<'q, Self::Database>, { let pool = self.clone(); Box::pin(try_stream! { let mut conn = pool.acquire().await?; let mut s = conn.fetch_many(query); while let Some(v) = s.try_next().await? { r#yield!(v); } Ok(()) }) } fn fetch_optional<'e, 'q: 'e, E>( self, query: E, ) -> BoxFuture<'e, Result, Error>> where E: 'q + Execute<'q, Self::Database>, { let pool = self.clone(); Box::pin(async move { pool.acquire().await?.fetch_optional(query).await }) } fn prepare_with<'e>( self, sql: SqlStr, parameters: &'e [::TypeInfo], ) -> BoxFuture<'e, Result<::Statement, Error>> where 'p: 'e, { let pool = self.clone(); Box::pin(async move { pool.acquire().await?.prepare_with(sql, parameters).await }) } #[doc(hidden)] #[cfg(feature = "offline")] fn describe<'e>( self, sql: SqlStr, ) -> BoxFuture<'e, Result, Error>> { let pool = self.clone(); Box::pin(async move { pool.acquire().await?.describe(sql).await }) } } // Causes an overflow when evaluating `&mut DB::Connection: Executor`. // // // impl<'c, DB: Database> crate::executor::Executor<'c> for &'c mut crate::pool::PoolConnection // where // &'c mut DB::Connection: Executor<'c, Database = DB>, // { // type Database = DB; // // // // #[inline] // fn fetch_many<'e, 'q: 'e, E: 'q>( // self, // query: E, // ) -> futures_core::stream::BoxStream< // 'e, // Result< // either::Either<::QueryResult, DB::Row>, // crate::error::Error, // >, // > // where // 'c: 'e, // E: crate::executor::Execute<'q, DB>, // { // (**self).fetch_many(query) // } // // #[inline] // fn fetch_optional<'e, 'q: 'e, E: 'q>( // self, // query: E, // ) -> futures_core::future::BoxFuture<'e, Result, crate::error::Error>> // where // 'c: 'e, // E: crate::executor::Execute<'q, DB>, // { // (**self).fetch_optional(query) // } // // #[inline] // fn prepare_with<'e, 'q: 'e>( // self, // sql: &'q str, // parameters: &'e [::TypeInfo], // ) -> futures_core::future::BoxFuture< // 'e, // Result<::Statement<'q>, crate::error::Error>, // > // where // 'c: 'e, // { // (**self).prepare_with(sql, parameters) // } // // #[doc(hidden)] // #[cfg(feature = "offline")] // #[inline] // fn describe<'e, 'q: 'e>( // self, // sql: &'q str, // ) -> futures_core::future::BoxFuture< // 'e, // Result, crate::error::Error>, // > // where // 'c: 'e, // { // (**self).describe(sql) // } // } ================================================ FILE: sqlx-core/src/pool/inner.rs ================================================ use super::connection::{Floating, Idle, Live}; use crate::connection::ConnectOptions; use crate::connection::Connection; use crate::database::Database; use crate::error::Error; use crate::pool::{deadline_as_timeout, CloseEvent, Pool, PoolOptions}; use crossbeam_queue::ArrayQueue; use crate::sync::{AsyncSemaphore, AsyncSemaphoreReleaser}; use std::cmp; use std::future::{self, Future}; use std::pin::pin; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}; use std::sync::{Arc, RwLock}; use std::task::Poll; use crate::logger::private_level_filter_to_trace_level; use crate::pool::options::PoolConnectionMetadata; use crate::private_tracing_dynamic_event; use futures_util::FutureExt; use std::time::{Duration, Instant}; use tracing::Level; pub(crate) struct PoolInner { pub(super) connect_options: RwLock::Options>>, pub(super) idle_conns: ArrayQueue>, pub(super) semaphore: AsyncSemaphore, pub(super) size: AtomicU32, pub(super) num_idle: AtomicUsize, is_closed: AtomicBool, pub(super) on_closed: event_listener::Event, pub(super) options: PoolOptions, pub(crate) acquire_time_level: Option, pub(crate) acquire_slow_level: Option, } impl PoolInner { pub(super) fn new_arc( options: PoolOptions, connect_options: ::Options, ) -> Arc { let capacity = options.max_connections as usize; let semaphore_capacity = if let Some(parent) = &options.parent_pool { assert!(options.max_connections <= parent.options().max_connections); assert_eq!(options.fair, parent.options().fair); // The child pool must steal permits from the parent 0 } else { capacity }; let pool = Self { connect_options: RwLock::new(Arc::new(connect_options)), idle_conns: ArrayQueue::new(capacity), semaphore: AsyncSemaphore::new(options.fair, semaphore_capacity), size: AtomicU32::new(0), num_idle: AtomicUsize::new(0), is_closed: AtomicBool::new(false), on_closed: event_listener::Event::new(), acquire_time_level: private_level_filter_to_trace_level(options.acquire_time_level), acquire_slow_level: private_level_filter_to_trace_level(options.acquire_slow_level), options, }; let pool = Arc::new(pool); spawn_maintenance_tasks(&pool); pool } pub(super) fn size(&self) -> u32 { self.size.load(Ordering::Acquire) } pub(super) fn num_idle(&self) -> usize { // We don't use `self.idle_conns.len()` as it waits for the internal // head and tail pointers to stop changing for a moment before calculating the length, // which may take a long time at high levels of churn. // // By maintaining our own atomic count, we avoid that issue entirely. self.num_idle.load(Ordering::Acquire) } pub(super) fn is_closed(&self) -> bool { self.is_closed.load(Ordering::Acquire) } fn mark_closed(&self) { self.is_closed.store(true, Ordering::Release); self.on_closed.notify(usize::MAX); } pub(super) fn close(self: &Arc) -> impl Future + '_ { self.mark_closed(); async move { // For child pools, we need to acquire permits we actually have rather than // max_connections let permits_to_acquire = if self.options.parent_pool.is_some() { // Child pools start with 0 permits, so we acquire based on current size self.size() } else { // Parent pools can acquire all max_connections permits self.options.max_connections }; let _permits = self.semaphore.acquire(permits_to_acquire).await; while let Some(idle) = self.idle_conns.pop() { let _ = idle.live.raw.close().await; } self.num_idle.store(0, Ordering::Release); self.size.store(0, Ordering::Release); } } pub(crate) fn close_event(&self) -> CloseEvent { CloseEvent { listener: (!self.is_closed()).then(|| self.on_closed.listen()), } } /// Attempt to pull a permit from `self.semaphore` or steal one from the parent. /// /// If we steal a permit from the parent but *don't* open a connection, /// it should be returned to the parent. async fn acquire_permit(self: &Arc) -> Result, Error> { let parent = self .parent() // If we're already at the max size, we shouldn't try to steal from the parent. // This is just going to cause unnecessary churn in `acquire()`. .filter(|_| self.size() < self.options.max_connections); let mut acquire_self = pin!(self.semaphore.acquire(1).fuse()); let mut close_event = pin!(self.close_event()); if let Some(parent) = parent { let mut acquire_parent = pin!(parent.0.semaphore.acquire(1)); let mut parent_close_event = pin!(parent.0.close_event()); let mut poll_parent = false; future::poll_fn(|cx| { if close_event.as_mut().poll(cx).is_ready() { return Poll::Ready(Err(Error::PoolClosed)); } if parent_close_event.as_mut().poll(cx).is_ready() { // Propagate the parent's close event to the child. self.mark_closed(); return Poll::Ready(Err(Error::PoolClosed)); } if let Poll::Ready(permit) = acquire_self.as_mut().poll(cx) { return Poll::Ready(Ok(permit)); } // Don't try the parent right away. if poll_parent { acquire_parent.as_mut().poll(cx).map(Ok) } else { poll_parent = true; cx.waker().wake_by_ref(); Poll::Pending } }) .await } else { close_event.do_until(acquire_self).await } } fn parent(&self) -> Option<&Pool> { self.options.parent_pool.as_ref() } #[inline] pub(super) fn try_acquire(self: &Arc) -> Option>> { if self.is_closed() { return None; } let permit = self.semaphore.try_acquire(1)?; self.pop_idle(permit).ok() } fn pop_idle<'a>( self: &'a Arc, permit: AsyncSemaphoreReleaser<'a>, ) -> Result>, AsyncSemaphoreReleaser<'a>> { if let Some(idle) = self.idle_conns.pop() { self.num_idle.fetch_sub(1, Ordering::AcqRel); Ok(Floating::from_idle(idle, (*self).clone(), permit)) } else { Err(permit) } } pub(super) fn release(&self, floating: Floating>) { // `options.after_release` and other checks are in `PoolConnection::return_to_pool()`. let Floating { inner: idle, guard } = floating.into_idle(); if self.idle_conns.push(idle).is_err() { panic!("BUG: connection queue overflow in release()"); } // NOTE: we need to make sure we drop the permit *after* we push to the idle queue // don't decrease the size guard.release_permit(); self.num_idle.fetch_add(1, Ordering::AcqRel); } /// Try to atomically increment the pool size for a new connection. /// /// Returns `Err` if the pool is at max capacity already or is closed. pub(super) fn try_increment_size<'a>( self: &'a Arc, permit: AsyncSemaphoreReleaser<'a>, ) -> Result, AsyncSemaphoreReleaser<'a>> { let result = self .size .fetch_update(Ordering::AcqRel, Ordering::Acquire, |size| { if self.is_closed() { return None; } size.checked_add(1) .filter(|size| size <= &self.options.max_connections) }); match result { // we successfully incremented the size Ok(_) => Ok(DecrementSizeGuard::from_permit((*self).clone(), permit)), // the pool is at max capacity or is closed Err(_) => Err(permit), } } pub(super) async fn acquire(self: &Arc) -> Result>, Error> { if self.is_closed() { return Err(Error::PoolClosed); } let acquire_started_at = Instant::now(); let deadline = acquire_started_at + self.options.acquire_timeout; let acquired = crate::rt::timeout( self.options.acquire_timeout, async { loop { // Handles the close-event internally let permit = self.acquire_permit().await?; // First attempt to pop a connection from the idle queue. let guard = match self.pop_idle(permit) { // Then, check that we can use it... Ok(conn) => match check_idle_conn(conn, &self.options).await { // All good! Ok(live) => return Ok(live), // if the connection isn't usable for one reason or another, // we get the `DecrementSizeGuard` back to open a new one Err(guard) => guard, }, Err(permit) => if let Ok(guard) = self.try_increment_size(permit) { // we can open a new connection guard } else { // This can happen for a child pool that's at its connection limit, // or if the pool was closed between `acquire_permit()` and // `try_increment_size()`. tracing::debug!("woke but was unable to acquire idle connection or open new one; retrying"); // If so, we're likely in the current-thread runtime if it's Tokio, // and so we should yield to let any spawned return_to_pool() tasks // execute. crate::rt::yield_now().await; continue; } }; // Attempt to connect... return self.connect(deadline, guard).await; } } ) .await .map_err(|_| Error::PoolTimedOut)??; let acquired_after = acquire_started_at.elapsed(); let acquire_slow_level = self .acquire_slow_level .filter(|_| acquired_after > self.options.acquire_slow_threshold); if let Some(level) = acquire_slow_level { private_tracing_dynamic_event!( target: "sqlx::pool::acquire", level, acquired_after_secs = acquired_after.as_secs_f64(), slow_acquire_threshold_secs = self.options.acquire_slow_threshold.as_secs_f64(), "acquired connection, but time to acquire exceeded slow threshold" ); } else if let Some(level) = self.acquire_time_level { private_tracing_dynamic_event!( target: "sqlx::pool::acquire", level, acquired_after_secs = acquired_after.as_secs_f64(), "acquired connection" ); } Ok(acquired) } pub(super) async fn connect( self: &Arc, deadline: Instant, guard: DecrementSizeGuard, ) -> Result>, Error> { if self.is_closed() { return Err(Error::PoolClosed); } let mut backoff = Duration::from_millis(10); let max_backoff = deadline_as_timeout(deadline)? / 5; loop { let timeout = deadline_as_timeout(deadline)?; // clone the connect options arc so it can be used without holding the RwLockReadGuard // across an async await point let connect_options = self .connect_options .read() .expect("write-lock holder panicked") .clone(); // result here is `Result, TimeoutError>` // if this block does not return, sleep for the backoff timeout and try again match crate::rt::timeout(timeout, connect_options.connect()).await { // successfully established connection Ok(Ok(mut raw)) => { // See comment on `PoolOptions::after_connect` let meta = PoolConnectionMetadata { age: Duration::ZERO, idle_for: Duration::ZERO, }; let res = if let Some(callback) = &self.options.after_connect { callback(&mut raw, meta).await } else { Ok(()) }; match res { Ok(()) => return Ok(Floating::new_live(raw, guard)), Err(error) => { tracing::error!(%error, "error returned from after_connect"); // The connection is broken, don't try to close nicely. let _ = raw.close_hard().await; // Fall through to the backoff. } } } // an IO error while connecting is assumed to be the system starting up Ok(Err(Error::Io(e))) if e.kind() == std::io::ErrorKind::ConnectionRefused => (), // We got a transient database error, retry. Ok(Err(Error::Database(error))) if error.is_transient_in_connect_phase() => (), // Any other error while connection should immediately // terminate and bubble the error up Ok(Err(e)) => return Err(e), // timed out Err(_) => return Err(Error::PoolTimedOut), } // If the connection is refused, wait in exponentially // increasing steps for the server to come up, // capped by a factor of the remaining time until the deadline crate::rt::sleep(backoff).await; backoff = cmp::min(backoff * 2, max_backoff); } } /// Try to maintain `min_connections`, returning any errors (including `PoolTimedOut`). pub async fn try_min_connections(self: &Arc, deadline: Instant) -> Result<(), Error> { while self.size() < self.options.min_connections { // Don't wait for a semaphore permit. // // If no extra permits are available then we shouldn't be trying to spin up // connections anyway. let Some(permit) = self.semaphore.try_acquire(1) else { return Ok(()); }; // We must always obey `max_connections`. let Some(guard) = self.try_increment_size(permit).ok() else { return Ok(()); }; // We skip `after_release` since the connection was never provided to user code // besides `after_connect`, if they set it. self.release(self.connect(deadline, guard).await?); } Ok(()) } /// Attempt to maintain `min_connections`, logging if unable. pub async fn min_connections_maintenance(self: &Arc, deadline: Option) { let deadline = deadline.unwrap_or_else(|| { // Arbitrary default deadline if the caller doesn't care. Instant::now() + Duration::from_secs(300) }); match self.try_min_connections(deadline).await { Ok(()) => (), Err(Error::PoolClosed) => (), Err(Error::PoolTimedOut) => { tracing::debug!("unable to complete `min_connections` maintenance before deadline") } Err(error) => tracing::debug!(%error, "error while maintaining min_connections"), } } } impl Drop for PoolInner { fn drop(&mut self) { self.mark_closed(); if let Some(parent) = &self.options.parent_pool { // Release the stolen permits. parent.0.semaphore.release(self.semaphore.permits()); } } } /// Returns `true` if the connection has exceeded `options.max_lifetime` if set, `false` otherwise. pub(super) fn is_beyond_max_lifetime( live: &Live, options: &PoolOptions, ) -> bool { options .max_lifetime .is_some_and(|max| live.created_at.elapsed() > max) } /// Returns `true` if the connection has exceeded `options.idle_timeout` if set, `false` otherwise. fn is_beyond_idle_timeout(idle: &Idle, options: &PoolOptions) -> bool { options .idle_timeout .is_some_and(|timeout| idle.idle_since.elapsed() > timeout) } async fn check_idle_conn( mut conn: Floating>, options: &PoolOptions, ) -> Result>, DecrementSizeGuard> { if options.test_before_acquire { // Check that the connection is still live if let Err(error) = conn.ping().await { // an error here means the other end has hung up or we lost connectivity // either way we're fine to just discard the connection // the error itself here isn't necessarily unexpected so WARN is too strong tracing::info!(%error, "ping on idle connection returned error"); // connection is broken so don't try to close nicely return Err(conn.close_hard().await); } } if let Some(test) = &options.before_acquire { let meta = conn.metadata(); match test(&mut conn.live.raw, meta).await { Ok(false) => { // connection was rejected by user-defined hook, close nicely return Err(conn.close().await); } Err(error) => { tracing::warn!(%error, "error from `before_acquire`"); // connection is broken so don't try to close nicely return Err(conn.close_hard().await); } Ok(true) => {} } } // No need to re-connect; connection is alive or we don't care Ok(conn.into_live()) } fn spawn_maintenance_tasks(pool: &Arc>) { // NOTE: use `pool_weak` for the maintenance tasks // so they don't keep `PoolInner` from being dropped. let pool_weak = Arc::downgrade(pool); let period = match (pool.options.max_lifetime, pool.options.idle_timeout) { (Some(it), None) | (None, Some(it)) => it, (Some(a), Some(b)) => cmp::min(a, b), (None, None) => { if pool.options.min_connections > 0 { crate::rt::spawn(async move { if let Some(pool) = pool_weak.upgrade() { pool.min_connections_maintenance(None).await; } }); } return; } }; // Immediately cancel this task if the pool is closed. let mut close_event = pool.close_event(); crate::rt::spawn(async move { let _ = close_event .do_until(async { // If the last handle to the pool was dropped while we were sleeping while let Some(pool) = pool_weak.upgrade() { if pool.is_closed() { return; } let next_run = Instant::now() + period; // Go over all idle connections, check for idleness and lifetime, // and if we have fewer than min_connections after reaping a connection, // open a new one immediately. Note that other connections may be popped from // the queue in the meantime - that's fine, there is no harm in checking more for _ in 0..pool.num_idle() { if let Some(conn) = pool.try_acquire() { if is_beyond_idle_timeout(&conn, &pool.options) || is_beyond_max_lifetime(&conn, &pool.options) { let _ = conn.close().await; pool.min_connections_maintenance(Some(next_run)).await; } else { pool.release(conn.into_live()); } } } // Don't hold a reference to the pool while sleeping. drop(pool); if let Some(duration) = next_run.checked_duration_since(Instant::now()) { // `async-std` doesn't have a `sleep_until()` crate::rt::sleep(duration).await; } else { // `next_run` is in the past, just yield. crate::rt::yield_now().await; } } }) .await; }); } /// RAII guard returned by `Pool::try_increment_size()` and others. /// /// Will decrement the pool size if dropped, to avoid semantically "leaking" connections /// (where the pool thinks it has more connections than it does). pub(in crate::pool) struct DecrementSizeGuard { pub(crate) pool: Arc>, cancelled: bool, } impl DecrementSizeGuard { /// Create a new guard that will release a semaphore permit on-drop. pub fn new_permit(pool: Arc>) -> Self { Self { pool, cancelled: false, } } pub fn from_permit(pool: Arc>, permit: AsyncSemaphoreReleaser<'_>) -> Self { // here we effectively take ownership of the permit permit.disarm(); Self::new_permit(pool) } /// Release the semaphore permit without decreasing the pool size. /// /// If the permit was stolen from the pool's parent, it will be returned to the child's semaphore. fn release_permit(self) { self.pool.semaphore.release(1); self.cancel(); } pub fn cancel(mut self) { self.cancelled = true; } } impl Drop for DecrementSizeGuard { fn drop(&mut self) { if !self.cancelled { self.pool.size.fetch_sub(1, Ordering::AcqRel); // and here we release the permit we got on construction self.pool.semaphore.release(1); } } } ================================================ FILE: sqlx-core/src/pool/maybe.rs ================================================ use crate::database::Database; use crate::pool::PoolConnection; use std::ops::{Deref, DerefMut}; pub enum MaybePoolConnection<'c, DB: Database> { #[allow(dead_code)] Connection(&'c mut DB::Connection), PoolConnection(PoolConnection), } impl Deref for MaybePoolConnection<'_, DB> { type Target = DB::Connection; #[inline] fn deref(&self) -> &Self::Target { match self { MaybePoolConnection::Connection(v) => v, MaybePoolConnection::PoolConnection(v) => v, } } } impl DerefMut for MaybePoolConnection<'_, DB> { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { match self { MaybePoolConnection::Connection(v) => v, MaybePoolConnection::PoolConnection(v) => v, } } } impl From> for MaybePoolConnection<'_, DB> { fn from(v: PoolConnection) -> Self { MaybePoolConnection::PoolConnection(v) } } impl<'c, DB: Database> From<&'c mut DB::Connection> for MaybePoolConnection<'c, DB> { fn from(v: &'c mut DB::Connection) -> Self { MaybePoolConnection::Connection(v) } } ================================================ FILE: sqlx-core/src/pool/mod.rs ================================================ //! Provides the connection pool for asynchronous SQLx connections. //! //! Opening a database connection for each and every operation to the database can quickly //! become expensive. Furthermore, sharing a database connection between threads and functions //! can be difficult to express in Rust. //! //! A connection pool is a standard technique that can manage opening and re-using connections. //! Normally it also enforces a maximum number of connections as these are an expensive resource //! on the database server. //! //! SQLx provides a canonical connection pool implementation intended to satisfy the majority //! of use cases. //! //! See [Pool] for details. //! //! Type aliases are provided for each database to make it easier to sprinkle `Pool` through //! your codebase: //! //! * [MssqlPool][crate::mssql::MssqlPool] (MSSQL) //! * [MySqlPool][crate::mysql::MySqlPool] (MySQL) //! * [PgPool][crate::postgres::PgPool] (PostgreSQL) //! * [SqlitePool][crate::sqlite::SqlitePool] (SQLite) //! //! # Opening a connection pool //! //! A new connection pool with a default configuration can be created by supplying `Pool` //! with the database driver and a connection string. //! //! ```rust,ignore //! use sqlx::Pool; //! use sqlx::postgres::Postgres; //! //! let pool = Pool::::connect("postgres://").await?; //! ``` //! //! For convenience, database-specific type aliases are provided: //! //! ```rust,ignore //! use sqlx::mssql::MssqlPool; //! //! let pool = MssqlPool::connect("mssql://").await?; //! ``` //! //! # Using a connection pool //! //! A connection pool implements [`Executor`][crate::executor::Executor] and can be used directly //! when executing a query. Notice that only an immutable reference (`&Pool`) is needed. //! //! ```rust,ignore //! sqlx::query("DELETE FROM articles").execute(&pool).await?; //! ``` //! //! A connection or transaction may also be manually acquired with //! [`Pool::acquire`] or //! [`Pool::begin`]. use std::fmt; use std::future::Future; use std::pin::{pin, Pin}; use std::sync::Arc; use std::task::{ready, Context, Poll}; use std::time::{Duration, Instant}; use event_listener::EventListener; use futures_core::FusedFuture; use futures_util::FutureExt; use crate::connection::Connection; use crate::database::Database; use crate::error::Error; use crate::sql_str::SqlSafeStr; use crate::transaction::Transaction; pub use self::connection::PoolConnection; use self::inner::PoolInner; #[doc(hidden)] pub use self::maybe::MaybePoolConnection; pub use self::options::{PoolConnectionMetadata, PoolOptions}; #[macro_use] mod executor; #[macro_use] pub mod maybe; mod connection; mod inner; mod options; /// An asynchronous pool of SQLx database connections. /// /// Create a pool with [Pool::connect] or [Pool::connect_with] and then call [Pool::acquire] /// to get a connection from the pool; when the connection is dropped it will return to the pool /// so it can be reused. /// /// You can also pass `&Pool` directly anywhere an `Executor` is required; this will automatically /// checkout a connection for you. /// /// See [the module documentation](crate::pool) for examples. /// /// The pool has a maximum connection limit that it will not exceed; if `acquire()` is called /// when at this limit and all connections are checked out, the task will be made to wait until /// a connection becomes available. /// /// You can configure the connection limit, and other parameters, using [PoolOptions]. /// /// Calls to `acquire()` are fair, i.e. fulfilled on a first-come, first-serve basis. /// /// `Pool` is `Send`, `Sync` and `Clone`. It is intended to be created once at the start of your /// application/daemon/web server/etc. and then shared with all tasks throughout the process' /// lifetime. How best to accomplish this depends on your program architecture. /// /// In Actix-Web, for example, you can efficiently share a single pool with all request handlers /// using [web::ThinData]. /// /// Cloning `Pool` is cheap as it is simply a reference-counted handle to the inner pool state. /// When the last remaining handle to the pool is dropped, the connections owned by the pool are /// immediately closed (also by dropping). `PoolConnection` returned by [Pool::acquire] and /// `Transaction` returned by [Pool::begin] both implicitly hold a reference to the pool for /// their lifetimes. /// /// If you prefer to explicitly shutdown the pool and gracefully close its connections (which /// depending on the database type, may include sending a message to the database server that the /// connection is being closed), you can call [Pool::close] which causes all waiting and subsequent /// calls to [Pool::acquire] to return [Error::PoolClosed], and waits until all connections have /// been returned to the pool and gracefully closed. /// /// Type aliases are provided for each database to make it easier to sprinkle `Pool` through /// your codebase: /// /// * [MssqlPool][crate::mssql::MssqlPool] (MSSQL) /// * [MySqlPool][crate::mysql::MySqlPool] (MySQL) /// * [PgPool][crate::postgres::PgPool] (PostgreSQL) /// * [SqlitePool][crate::sqlite::SqlitePool] (SQLite) /// /// [web::ThinData]: https://docs.rs/actix-web/4.9.0/actix_web/web/struct.ThinData.html /// /// ### Note: Drop Behavior /// Due to a lack of async `Drop`, dropping the last `Pool` handle may not immediately clean /// up connections by itself. The connections will be dropped locally, which is sufficient for /// SQLite, but for client/server databases like MySQL and Postgres, that only closes the /// client side of the connection. The server will not know the connection is closed until /// potentially much later: this is usually dictated by the TCP keepalive timeout in the server /// settings. /// /// Because the connection may not be cleaned up immediately on the server side, you may run /// into errors regarding connection limits if you are creating and dropping many pools in short /// order. /// /// We recommend calling [`.close().await`] to gracefully close the pool and its connections /// when you are done using it. This will also wake any tasks that are waiting on an `.acquire()` /// call, so for long-lived applications it's a good idea to call `.close()` during shutdown. /// /// If you're writing tests, consider using `#[sqlx::test]` which handles the lifetime of /// the pool for you. /// /// [`.close().await`]: Pool::close /// /// ### Why Use a Pool? /// /// A single database connection (in general) cannot be used by multiple threads simultaneously /// for various reasons, but an application or web server will typically need to execute numerous /// queries or commands concurrently (think of concurrent requests against a web server; many or all /// of them will probably need to hit the database). /// /// You could place the connection in a `Mutex` but this will make it a huge bottleneck. /// /// Naively, you might also think to just open a new connection per request, but this /// has a number of other caveats, generally due to the high overhead involved in working with /// a fresh connection. Examples to follow. /// /// Connection pools facilitate reuse of connections to _amortize_ these costs, helping to ensure /// that you're not paying for them each time you need a connection. /// /// ##### 1. Overhead of Opening a Connection /// Opening a database connection is not exactly a cheap operation. /// /// For SQLite, it means numerous requests to the filesystem and memory allocations, while for /// server-based databases it involves performing DNS resolution, opening a new TCP connection and /// allocating buffers. /// /// Each connection involves a nontrivial allocation of resources for the database server, usually /// including spawning a new thread or process specifically to handle the connection, both for /// concurrency and isolation of faults. /// /// Additionally, database connections typically involve a complex handshake including /// authentication, negotiation regarding connection parameters (default character sets, timezones, /// locales, supported features) and upgrades to encrypted tunnels. /// /// If `acquire()` is called on a pool with all connections checked out but it is not yet at its /// connection limit (see next section), then a new connection is immediately opened, so this pool /// does not _automatically_ save you from the overhead of creating a new connection. /// /// However, because this pool by design enforces _reuse_ of connections, this overhead cost /// is not paid each and every time you need a connection. In fact, if you set /// [the `min_connections` option in PoolOptions][PoolOptions::min_connections], the pool will /// create that many connections up-front so that they are ready to go when a request comes in, /// and maintain that number on a best-effort basis for consistent performance. /// /// ##### 2. Connection Limits (MySQL, MSSQL, Postgres) /// Database servers usually place hard limits on the number of connections that are allowed open at /// any given time, to maintain performance targets and prevent excessive allocation of resources, /// such as RAM, journal files, disk caches, etc. /// /// These limits have different defaults per database flavor, and may vary between different /// distributions of the same database, but are typically configurable on server start; /// if you're paying for managed database hosting then the connection limit will typically vary with /// your pricing tier. /// /// In MySQL, the default limit is typically 150, plus 1 which is reserved for a user with the /// `CONNECTION_ADMIN` privilege so you can still access the server to diagnose problems even /// with all connections being used. /// /// In MSSQL the only documentation for the default maximum limit is that it depends on the version /// and server configuration. /// /// In Postgres, the default limit is typically 100, minus 3 which are reserved for superusers /// (putting the default limit for unprivileged users at 97 connections). /// /// In any case, exceeding these limits results in an error when opening a new connection, which /// in a web server context will turn into a `500 Internal Server Error` if not handled, but should /// be turned into either `403 Forbidden` or `429 Too Many Requests` depending on your rate-limiting /// scheme. However, in a web context, telling a client "go away, maybe try again later" results in /// a sub-optimal user experience. /// /// Instead, with a connection pool, clients are made to wait in a fair queue for a connection to /// become available; by using a single connection pool for your whole application, you can ensure /// that you don't exceed the connection limit of your database server while allowing response /// time to degrade gracefully at high load. /// /// Of course, if multiple applications are connecting to the same database server, then you /// should ensure that the connection limits for all applications add up to your server's maximum /// connections or less. /// /// ##### 3. Resource Reuse /// The first time you execute a query against your database, the database engine must first turn /// the SQL into an actionable _query plan_ which it may then execute against the database. This /// involves parsing the SQL query, validating and analyzing it, and in the case of Postgres 12+ and /// SQLite, generating code to execute the query plan (native or bytecode, respectively). /// /// These database servers provide a way to amortize this overhead by _preparing_ the query, /// associating it with an object ID and placing its query plan in a cache to be referenced when /// it is later executed. /// /// Prepared statements have other features, like bind parameters, which make them safer and more /// ergonomic to use as well. By design, SQLx pushes you towards using prepared queries/statements /// via the [Query][crate::query::Query] API _et al._ and the `query!()` macro _et al._, for /// reasons of safety, ergonomics, and efficiency. /// /// However, because database connections are typically isolated from each other in the database /// server (either by threads or separate processes entirely), they don't typically share prepared /// statements between connections so this work must be redone _for each connection_. /// /// As with section 1, by facilitating reuse of connections, `Pool` helps to ensure their prepared /// statements (and thus cached query plans) can be reused as much as possible, thus amortizing /// the overhead involved. /// /// Depending on the database server, a connection will have caches for all kinds of other data as /// well and queries will generally benefit from these caches being "warm" (populated with data). pub struct Pool(pub(crate) Arc>); /// A future that resolves when the pool is closed. /// /// See [`Pool::close_event()`] for details. pub struct CloseEvent { listener: Option, } impl Pool { /// Create a new connection pool with a default pool configuration and /// the given connection URL, and immediately establish one connection. /// /// Refer to the relevant `ConnectOptions` impl for your database for the expected URL format: /// /// * Postgres: [`PgConnectOptions`][crate::postgres::PgConnectOptions] /// * MySQL: [`MySqlConnectOptions`][crate::mysql::MySqlConnectOptions] /// * SQLite: [`SqliteConnectOptions`][crate::sqlite::SqliteConnectOptions] /// * MSSQL: [`MssqlConnectOptions`][crate::mssql::MssqlConnectOptions] /// /// The default configuration is mainly suited for testing and light-duty applications. /// For production applications, you'll likely want to make at least few tweaks. /// /// See [`PoolOptions::new()`] for details. pub async fn connect(url: &str) -> Result { PoolOptions::::new().connect(url).await } /// Create a new connection pool with a default pool configuration and /// the given `ConnectOptions`, and immediately establish one connection. /// /// The default configuration is mainly suited for testing and light-duty applications. /// For production applications, you'll likely want to make at least few tweaks. /// /// See [`PoolOptions::new()`] for details. pub async fn connect_with( options: ::Options, ) -> Result { PoolOptions::::new().connect_with(options).await } /// Create a new connection pool with a default pool configuration and /// the given connection URL. /// /// The pool will establish connections only as needed. /// /// Refer to the relevant [`ConnectOptions`][crate::connection::ConnectOptions] impl for your database for the expected URL format: /// /// * Postgres: [`PgConnectOptions`][crate::postgres::PgConnectOptions] /// * MySQL: [`MySqlConnectOptions`][crate::mysql::MySqlConnectOptions] /// * SQLite: [`SqliteConnectOptions`][crate::sqlite::SqliteConnectOptions] /// * MSSQL: [`MssqlConnectOptions`][crate::mssql::MssqlConnectOptions] /// /// The default configuration is mainly suited for testing and light-duty applications. /// For production applications, you'll likely want to make at least few tweaks. /// /// See [`PoolOptions::new()`] for details. pub fn connect_lazy(url: &str) -> Result { PoolOptions::::new().connect_lazy(url) } /// Create a new connection pool with a default pool configuration and /// the given `ConnectOptions`. /// /// The pool will establish connections only as needed. /// /// The default configuration is mainly suited for testing and light-duty applications. /// For production applications, you'll likely want to make at least few tweaks. /// /// See [`PoolOptions::new()`] for details. pub fn connect_lazy_with(options: ::Options) -> Self { PoolOptions::::new().connect_lazy_with(options) } /// Retrieves a connection from the pool. /// /// The total time this method is allowed to execute is capped by /// [`PoolOptions::acquire_timeout`]. /// If that timeout elapses, this will return [`Error::PoolClosed`]. /// /// ### Note: Cancellation/Timeout May Drop Connections /// If `acquire` is cancelled or times out after it acquires a connection from the idle queue or /// opens a new one, it will drop that connection because we don't want to assume it /// is safe to return to the pool, and testing it to see if it's safe to release could introduce /// subtle bugs if not implemented correctly. To avoid that entirely, we've decided to not /// gracefully handle cancellation here. /// /// However, if your workload is sensitive to dropped connections such as using an in-memory /// SQLite database with a pool size of 1, you can pretty easily ensure that a cancelled /// `acquire()` call will never drop connections by tweaking your [`PoolOptions`]: /// /// * Set [`test_before_acquire(false)`][PoolOptions::test_before_acquire] /// * Never set [`before_acquire`][PoolOptions::before_acquire] or /// [`after_connect`][PoolOptions::after_connect]. /// /// This should eliminate any potential `.await` points between acquiring a connection and /// returning it. pub fn acquire(&self) -> impl Future, Error>> + 'static { let shared = self.0.clone(); async move { shared.acquire().await.map(|conn| conn.reattach()) } } /// Attempts to retrieve a connection from the pool if there is one available. /// /// Returns `None` immediately if there are no idle connections available in the pool /// or there are tasks waiting for a connection which have yet to wake. pub fn try_acquire(&self) -> Option> { self.0.try_acquire().map(|conn| conn.into_live().reattach()) } /// Retrieves a connection and immediately begins a new transaction. pub async fn begin(&self) -> Result, Error> { Transaction::begin( MaybePoolConnection::PoolConnection(self.acquire().await?), None, ) .await } /// Attempts to retrieve a connection and immediately begins a new transaction if successful. pub async fn try_begin(&self) -> Result>, Error> { match self.try_acquire() { Some(conn) => Transaction::begin(MaybePoolConnection::PoolConnection(conn), None) .await .map(Some), None => Ok(None), } } /// Retrieves a connection and immediately begins a new transaction using `statement`. pub async fn begin_with( &self, statement: impl SqlSafeStr, ) -> Result, Error> { Transaction::begin( MaybePoolConnection::PoolConnection(self.acquire().await?), Some(statement.into_sql_str()), ) .await } /// Attempts to retrieve a connection and, if successful, immediately begins a new /// transaction using `statement`. pub async fn try_begin_with( &self, statement: impl SqlSafeStr, ) -> Result>, Error> { match self.try_acquire() { Some(conn) => Transaction::begin( MaybePoolConnection::PoolConnection(conn), Some(statement.into_sql_str()), ) .await .map(Some), None => Ok(None), } } /// Shut down the connection pool, immediately waking all tasks waiting for a connection. /// /// Upon calling this method, any currently waiting or subsequent calls to [`Pool::acquire`] and /// the like will immediately return [`Error::PoolClosed`] and no new connections will be opened. /// Checked-out connections are unaffected, but will be gracefully closed on-drop /// rather than being returned to the pool. /// /// Returns a `Future` which can be `.await`ed to ensure all connections are /// gracefully closed. It will first close any idle connections currently waiting in the pool, /// then wait for all checked-out connections to be returned or closed. /// /// Waiting for connections to be gracefully closed is optional, but will allow the database /// server to clean up the resources sooner rather than later. This is especially important /// for tests that create a new pool every time, otherwise you may see errors about connection /// limits being exhausted even when running tests in a single thread. /// /// If the returned `Future` is not run to completion, any remaining connections will be dropped /// when the last handle for the given pool instance is dropped, which could happen in a task /// spawned by `Pool` internally and so may be unpredictable otherwise. /// /// `.close()` may be safely called and `.await`ed on multiple handles concurrently. pub fn close(&self) -> impl Future + '_ { self.0.close() } /// Returns `true` if [`.close()`][Pool::close] has been called on the pool, `false` otherwise. pub fn is_closed(&self) -> bool { self.0.is_closed() } /// Get a future that resolves when [`Pool::close()`] is called. /// /// If the pool is already closed, the future resolves immediately. /// /// This can be used to cancel long-running operations that hold onto a [`PoolConnection`] /// so they don't prevent the pool from closing (which would otherwise wait until all /// connections are returned). /// /// Examples /// ======== /// These examples use Postgres and Tokio, but should suffice to demonstrate the concept. /// /// Do something when the pool is closed: /// ```rust,no_run /// # async fn bleh() -> sqlx::Result<()> { /// use sqlx::PgPool; /// /// let pool = PgPool::connect("postgresql://...").await?; /// /// let pool2 = pool.clone(); /// /// tokio::spawn(async move { /// // Demonstrates that `CloseEvent` is itself a `Future` you can wait on. /// // This lets you implement any kind of on-close event that you like. /// pool2.close_event().await; /// /// println!("Pool is closing!"); /// /// // Imagine maybe recording application statistics or logging a report, etc. /// }); /// /// // The rest of the application executes normally... /// /// // Close the pool before the application exits... /// pool.close().await; /// /// # Ok(()) /// # } /// ``` /// /// Cancel a long-running operation: /// ```rust,no_run /// # async fn bleh() -> sqlx::Result<()> { /// use sqlx::{Executor, PgPool}; /// /// let pool = PgPool::connect("postgresql://...").await?; /// /// let pool2 = pool.clone(); /// /// tokio::spawn(async move { /// // `do_until` yields the inner future's output wrapped in `sqlx::Result`, /// // in this case giving a double-wrapped result. /// let res: sqlx::Result> = pool2.close_event().do_until(async { /// // This statement normally won't return for 30 days! /// // (Assuming the connection doesn't time out first, of course.) /// pool2.execute("SELECT pg_sleep('30 days')").await?; /// /// // If the pool is closed before the statement completes, this won't be printed. /// // This is because `.do_until()` cancels the future it's given if the /// // pool is closed first. /// println!("Waited!"); /// /// Ok(()) /// }).await; /// /// match res { /// Ok(Ok(())) => println!("Wait succeeded"), /// Ok(Err(e)) => println!("Error from inside do_until: {e:?}"), /// Err(e) => println!("Error from do_until: {e:?}"), /// } /// }); /// /// // This normally wouldn't return until the above statement completed and the connection /// // was returned to the pool. However, thanks to `.do_until()`, the operation was /// // cancelled as soon as we called `.close().await`. /// pool.close().await; /// /// # Ok(()) /// # } /// ``` pub fn close_event(&self) -> CloseEvent { self.0.close_event() } /// Returns the number of connections currently active. This includes idle connections. pub fn size(&self) -> u32 { self.0.size() } /// Returns the number of connections active and idle (not in use). pub fn num_idle(&self) -> usize { self.0.num_idle() } /// Gets a clone of the connection options for this pool pub fn connect_options(&self) -> Arc<::Options> { self.0 .connect_options .read() .expect("write-lock holder panicked") .clone() } /// Updates the connection options this pool will use when opening any future connections. Any /// existing open connection in the pool will be left as-is. pub fn set_connect_options(&self, connect_options: ::Options) { // technically write() could also panic if the current thread already holds the lock, // but because this method can't be re-entered by the same thread that shouldn't be a problem let mut guard = self .0 .connect_options .write() .expect("write-lock holder panicked"); *guard = Arc::new(connect_options); } /// Get the options for this pool pub fn options(&self) -> &PoolOptions { &self.0.options } } /// Returns a new [Pool] tied to the same shared connection pool. impl Clone for Pool { fn clone(&self) -> Self { Self(Arc::clone(&self.0)) } } impl fmt::Debug for Pool { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("Pool") .field("size", &self.0.size()) .field("num_idle", &self.0.num_idle()) .field("is_closed", &self.0.is_closed()) .field("options", &self.0.options) .finish() } } impl CloseEvent { /// Execute the given future until it returns or the pool is closed. /// /// Cancels the future and returns `Err(PoolClosed)` if/when the pool is closed. /// If the pool was already closed, the future is never run. pub async fn do_until(&mut self, fut: Fut) -> Result { // Check that the pool wasn't closed already. // // We use `poll_immediate()` as it will use the correct waker instead of // a no-op one like `.now_or_never()`, but it won't actually suspend execution here. futures_util::future::poll_immediate(&mut *self) .await .map_or(Ok(()), |_| Err(Error::PoolClosed))?; let mut fut = pin!(fut); // I find that this is clearer in intent than `futures_util::future::select()` // or `futures_util::select_biased!{}` (which isn't enabled anyway). std::future::poll_fn(|cx| { // Poll `fut` first as the wakeup event is more likely for it than `self`. if let Poll::Ready(ret) = fut.as_mut().poll(cx) { return Poll::Ready(Ok(ret)); } // Can't really factor out mapping to `Err(Error::PoolClosed)` though it seems like // we should because that results in a different `Ok` type each time. // // Ideally we'd map to something like `Result` but using `!` as a type // is not allowed on stable Rust yet. self.poll_unpin(cx).map(|_| Err(Error::PoolClosed)) }) .await } } impl Future for CloseEvent { type Output = (); fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if let Some(listener) = &mut self.listener { ready!(listener.poll_unpin(cx)); } // `EventListener` doesn't like being polled after it yields, and even if it did it // would probably just wait for the next event, neither of which we want. // // So this way, once we get our close event, we fuse this future to immediately return. self.listener = None; Poll::Ready(()) } } impl FusedFuture for CloseEvent { fn is_terminated(&self) -> bool { self.listener.is_none() } } /// get the time between the deadline and now and use that as our timeout /// /// returns `Error::PoolTimedOut` if the deadline is in the past fn deadline_as_timeout(deadline: Instant) -> Result { deadline .checked_duration_since(Instant::now()) .ok_or(Error::PoolTimedOut) } #[test] #[allow(dead_code)] fn assert_pool_traits() { fn assert_send_sync() {} fn assert_clone() {} fn assert_pool() { assert_send_sync::>(); assert_clone::>(); } } ================================================ FILE: sqlx-core/src/pool/options.rs ================================================ use crate::connection::Connection; use crate::database::Database; use crate::error::Error; use crate::pool::inner::PoolInner; use crate::pool::Pool; use futures_core::future::BoxFuture; use log::LevelFilter; use std::fmt::{self, Debug, Formatter}; use std::sync::Arc; use std::time::{Duration, Instant}; /// Configuration options for [`Pool`][super::Pool]. /// /// ### Callback Functions: Why Do I Need `Box::pin()`? /// Essentially, because it's impossible to write generic bounds that describe a closure /// with a higher-ranked lifetime parameter, returning a future with that same lifetime. /// /// Ideally, you could define it like this: /// ```rust,ignore /// async fn takes_foo_callback(f: impl for<'a> Fn(&'a mut Foo) -> impl Future<'a, Output = ()>) /// ``` /// /// However, the compiler does not allow using `impl Trait` in the return type of an `impl Fn`. /// /// And if you try to do it like this: /// ```rust,ignore /// async fn takes_foo_callback(f: F) /// where /// F: for<'a> Fn(&'a mut Foo) -> Fut, /// Fut: for<'a> Future + 'a /// ``` /// /// There's no way to tell the compiler that those two `'a`s should be the same lifetime. /// /// It's possible to make this work with a custom trait, but it's fiddly and requires naming /// the type of the closure parameter. /// /// Having the closure return `BoxFuture` allows us to work around this, as all the type information /// fits into a single generic parameter. /// /// We still need to `Box` the future internally to give it a concrete type to avoid leaking a type /// parameter everywhere, and `Box` is in the prelude so it doesn't need to be manually imported, /// so having the closure return `Pin` directly is the path of least resistance from /// the perspectives of both API designer and consumer. pub struct PoolOptions { pub(crate) test_before_acquire: bool, pub(crate) after_connect: Option< Arc< dyn Fn(&mut DB::Connection, PoolConnectionMetadata) -> BoxFuture<'_, Result<(), Error>> + 'static + Send + Sync, >, >, pub(crate) before_acquire: Option< Arc< dyn Fn( &mut DB::Connection, PoolConnectionMetadata, ) -> BoxFuture<'_, Result> + 'static + Send + Sync, >, >, pub(crate) after_release: Option< Arc< dyn Fn( &mut DB::Connection, PoolConnectionMetadata, ) -> BoxFuture<'_, Result> + 'static + Send + Sync, >, >, pub(crate) max_connections: u32, pub(crate) acquire_time_level: LevelFilter, pub(crate) acquire_slow_level: LevelFilter, pub(crate) acquire_slow_threshold: Duration, pub(crate) acquire_timeout: Duration, pub(crate) min_connections: u32, pub(crate) max_lifetime: Option, pub(crate) idle_timeout: Option, pub(crate) fair: bool, pub(crate) parent_pool: Option>, } // Manually implement `Clone` to avoid a trait bound issue. // // See: https://github.com/launchbadge/sqlx/issues/2548 impl Clone for PoolOptions { fn clone(&self) -> Self { PoolOptions { test_before_acquire: self.test_before_acquire, after_connect: self.after_connect.clone(), before_acquire: self.before_acquire.clone(), after_release: self.after_release.clone(), max_connections: self.max_connections, acquire_time_level: self.acquire_time_level, acquire_slow_threshold: self.acquire_slow_threshold, acquire_slow_level: self.acquire_slow_level, acquire_timeout: self.acquire_timeout, min_connections: self.min_connections, max_lifetime: self.max_lifetime, idle_timeout: self.idle_timeout, fair: self.fair, parent_pool: self.parent_pool.clone(), } } } /// Metadata for the connection being processed by a [`PoolOptions`] callback. #[derive(Debug)] // Don't want to commit to any other trait impls yet. #[non_exhaustive] // So we can safely add fields in the future. pub struct PoolConnectionMetadata { /// The duration since the connection was first opened. /// /// For [`after_connect`][PoolOptions::after_connect], this is [`Duration::ZERO`]. pub age: Duration, /// The duration that the connection spent in the idle queue. /// /// Only relevant for [`before_acquire`][PoolOptions::before_acquire]. /// For other callbacks, this is [`Duration::ZERO`]. pub idle_for: Duration, } impl Default for PoolOptions { fn default() -> Self { Self::new() } } impl PoolOptions { /// Returns a default "sane" configuration, suitable for testing or light-duty applications. /// /// Production applications will likely want to at least modify /// [`max_connections`][Self::max_connections]. /// /// See the source of this method for the current default values. pub fn new() -> Self { Self { // User-specifiable routines after_connect: None, before_acquire: None, after_release: None, test_before_acquire: true, // A production application will want to set a higher limit than this. max_connections: 10, min_connections: 0, // Logging all acquires is opt-in acquire_time_level: LevelFilter::Off, // Default to warning, because an acquire timeout will be an error acquire_slow_level: LevelFilter::Warn, // Fast enough to catch problems (e.g. a full pool); slow enough // to not flag typical time to add a new connection to a pool. acquire_slow_threshold: Duration::from_secs(2), acquire_timeout: Duration::from_secs(30), idle_timeout: Some(Duration::from_secs(10 * 60)), max_lifetime: Some(Duration::from_secs(30 * 60)), fair: true, parent_pool: None, } } /// Set the maximum number of connections that this pool should maintain. /// /// Be mindful of the connection limits for your database as well as other applications /// which may want to connect to the same database (or even multiple instances of the same /// application in high-availability deployments). pub fn max_connections(mut self, max: u32) -> Self { self.max_connections = max; self } /// Get the maximum number of connections that this pool should maintain pub fn get_max_connections(&self) -> u32 { self.max_connections } /// Set the minimum number of connections to maintain at all times. /// /// When the pool is built, this many connections will be automatically spun up. /// /// If any connection is reaped by [`max_lifetime`] or [`idle_timeout`], or explicitly closed, /// and it brings the connection count below this amount, a new connection will be opened to /// replace it. /// /// This is only done on a best-effort basis, however. The routine that maintains this value /// has a deadline so it doesn't wait forever if the database is being slow or returning errors. /// /// This value is clamped internally to not exceed [`max_connections`]. /// /// We've chosen not to assert `min_connections <= max_connections` anywhere /// because it shouldn't break anything internally if the condition doesn't hold, /// and if the application allows either value to be dynamically set /// then it should be checking this condition itself and returning /// a nicer error than a panic anyway. /// /// [`max_lifetime`]: Self::max_lifetime /// [`idle_timeout`]: Self::idle_timeout /// [`max_connections`]: Self::max_connections pub fn min_connections(mut self, min: u32) -> Self { self.min_connections = min; self } /// Get the minimum number of connections to maintain at all times. pub fn get_min_connections(&self) -> u32 { self.min_connections } /// Enable logging of time taken to acquire a connection from the connection pool via /// [`Pool::acquire()`]. /// /// If slow acquire logging is also enabled, this level is used for acquires that are not /// considered slow. pub fn acquire_time_level(mut self, level: LevelFilter) -> Self { self.acquire_time_level = level; self } /// Log excessive time taken to acquire a connection at a different log level than time taken /// for faster connection acquires via [`Pool::acquire()`]. pub fn acquire_slow_level(mut self, level: LevelFilter) -> Self { self.acquire_slow_level = level; self } /// Set a threshold for reporting excessive time taken to acquire a connection from /// the connection pool via [`Pool::acquire()`]. When the threshold is exceeded, a warning is logged. /// /// Defaults to a value that should not typically be exceeded by the pool enlarging /// itself with an additional new connection. pub fn acquire_slow_threshold(mut self, threshold: Duration) -> Self { self.acquire_slow_threshold = threshold; self } /// Get the threshold for reporting excessive time taken to acquire a connection via /// [`Pool::acquire()`]. pub fn get_acquire_slow_threshold(&self) -> Duration { self.acquire_slow_threshold } /// Set the maximum amount of time to spend waiting for a connection in [`Pool::acquire()`]. /// /// Caps the total amount of time `Pool::acquire()` can spend waiting across multiple phases: /// /// * First, it may need to wait for a permit from the semaphore, which grants it the privilege /// of opening a connection or popping one from the idle queue. /// * If an existing idle connection is acquired, by default it will be checked for liveness /// and integrity before being returned, which may require executing a command on the /// connection. This can be disabled with [`test_before_acquire(false)`][Self::test_before_acquire]. /// * If [`before_acquire`][Self::before_acquire] is set, that will also be executed. /// * If a new connection needs to be opened, that will obviously require I/O, handshaking, /// and initialization commands. /// * If [`after_connect`][Self::after_connect] is set, that will also be executed. pub fn acquire_timeout(mut self, timeout: Duration) -> Self { self.acquire_timeout = timeout; self } /// Get the maximum amount of time to spend waiting for a connection in [`Pool::acquire()`]. pub fn get_acquire_timeout(&self) -> Duration { self.acquire_timeout } /// Set the maximum lifetime of individual connections. /// /// Any connection with a lifetime greater than this will be closed. /// /// When set to `None`, all connections live until either reaped by [`idle_timeout`] /// or explicitly disconnected. /// /// Infinite connections are not recommended due to the unfortunate reality of memory/resource /// leaks on the database-side. It is better to retire connections periodically /// (even if only once daily) to allow the database the opportunity to clean up data structures /// (parse trees, query metadata caches, thread-local storage, etc.) that are associated with a /// session. /// /// [`idle_timeout`]: Self::idle_timeout pub fn max_lifetime(mut self, lifetime: impl Into>) -> Self { self.max_lifetime = lifetime.into(); self } /// Get the maximum lifetime of individual connections. pub fn get_max_lifetime(&self) -> Option { self.max_lifetime } /// Set a maximum idle duration for individual connections. /// /// Any connection that remains in the idle queue longer than this will be closed. /// /// For usage-based database server billing, this can be a cost saver. pub fn idle_timeout(mut self, timeout: impl Into>) -> Self { self.idle_timeout = timeout.into(); self } /// Get the maximum idle duration for individual connections. pub fn get_idle_timeout(&self) -> Option { self.idle_timeout } /// If true, the health of a connection will be verified by a call to [`Connection::ping`] /// before returning the connection. /// /// Defaults to `true`. pub fn test_before_acquire(mut self, test: bool) -> Self { self.test_before_acquire = test; self } /// Get whether `test_before_acquire` is currently set. pub fn get_test_before_acquire(&self) -> bool { self.test_before_acquire } /// If set to `true`, calls to `acquire()` are fair and connections are issued /// in first-come-first-serve order. If `false`, "drive-by" tasks may steal idle connections /// ahead of tasks that have been waiting. /// /// According to `sqlx-bench/benches/pg_pool` this may slightly increase time /// to `acquire()` at low pool contention but at very high contention it helps /// avoid tasks at the head of the waiter queue getting repeatedly preempted by /// these "drive-by" tasks and tasks further back in the queue timing out because /// the queue isn't moving. /// /// Currently only exposed for benchmarking; `fair = true` seems to be the superior option /// in most cases. #[doc(hidden)] pub fn __fair(mut self, fair: bool) -> Self { self.fair = fair; self } /// Perform an asynchronous action after connecting to the database. /// /// If the operation returns with an error then the error is logged, the connection is closed /// and a new one is opened in its place and the callback is invoked again. /// /// This occurs in a backoff loop to avoid high CPU usage and spamming logs during a transient /// error condition. /// /// Note that this may be called for internally opened connections, such as when maintaining /// [`min_connections`][Self::min_connections], that are then immediately returned to the pool /// without invoking [`after_release`][Self::after_release]. /// /// # Example: Additional Parameters /// This callback may be used to set additional configuration parameters /// that are not exposed by the database's `ConnectOptions`. /// /// This example is written for PostgreSQL but can likely be adapted to other databases. /// /// ```no_run /// # async fn f() -> Result<(), Box> { /// use sqlx::Executor; /// use sqlx::postgres::PgPoolOptions; /// /// let pool = PgPoolOptions::new() /// .after_connect(|conn, _meta| Box::pin(async move { /// // When directly invoking `Executor` methods, /// // it is possible to execute multiple statements with one call. /// conn.execute("SET application_name = 'your_app'; SET search_path = 'my_schema';") /// .await?; /// /// Ok(()) /// })) /// .connect("postgres:// …").await?; /// # Ok(()) /// # } /// ``` /// /// For a discussion on why `Box::pin()` is required, see [the type-level docs][Self]. pub fn after_connect(mut self, callback: F) -> Self where // We're passing the `PoolConnectionMetadata` here mostly for future-proofing. // `age` and `idle_for` are obviously not useful for fresh connections. for<'c> F: Fn(&'c mut DB::Connection, PoolConnectionMetadata) -> BoxFuture<'c, Result<(), Error>> + 'static + Send + Sync, { self.after_connect = Some(Arc::new(callback)); self } /// Perform an asynchronous action on a previously idle connection before giving it out. /// /// Alongside the connection, the closure gets [`PoolConnectionMetadata`] which contains /// potentially useful information such as the connection's age and the duration it was /// idle. /// /// If the operation returns `Ok(true)`, the connection is returned to the task that called /// [`Pool::acquire`]. /// /// If the operation returns `Ok(false)` or an error, the error is logged (if applicable) /// and then the connection is closed and [`Pool::acquire`] tries again with another idle /// connection. If it runs out of idle connections, it opens a new connection instead. /// /// This is *not* invoked for new connections. Use [`after_connect`][Self::after_connect] /// for those. /// /// # Example: Custom `test_before_acquire` Logic /// If you only want to ping connections if they've been idle a certain amount of time, /// you can implement your own logic here: /// /// This example is written for Postgres but should be trivially adaptable to other databases. /// ```no_run /// # async fn f() -> Result<(), Box> { /// use sqlx::{Connection, Executor}; /// use sqlx::postgres::PgPoolOptions; /// /// let pool = PgPoolOptions::new() /// .test_before_acquire(false) /// .before_acquire(|conn, meta| Box::pin(async move { /// // One minute /// if meta.idle_for.as_secs() > 60 { /// conn.ping().await?; /// } /// /// Ok(true) /// })) /// .connect("postgres:// …").await?; /// # Ok(()) /// # } ///``` /// /// For a discussion on why `Box::pin()` is required, see [the type-level docs][Self]. pub fn before_acquire(mut self, callback: F) -> Self where for<'c> F: Fn(&'c mut DB::Connection, PoolConnectionMetadata) -> BoxFuture<'c, Result> + 'static + Send + Sync, { self.before_acquire = Some(Arc::new(callback)); self } /// Perform an asynchronous action on a connection before it is returned to the pool. /// /// Alongside the connection, the closure gets [`PoolConnectionMetadata`] which contains /// potentially useful information such as the connection's age. /// /// If the operation returns `Ok(true)`, the connection is returned to the pool's idle queue. /// If the operation returns `Ok(false)` or an error, the error is logged (if applicable) /// and the connection is closed, allowing a task waiting on [`Pool::acquire`] to /// open a new one in its place. /// /// # Example (Postgres): Close Memory-Hungry Connections /// Instead of relying on [`max_lifetime`][Self::max_lifetime] to close connections, /// we can monitor their memory usage directly and close any that have allocated too much. /// /// Note that this is purely an example showcasing a possible use for this callback /// and may be flawed as it has not been tested. /// /// This example queries [`pg_backend_memory_contexts`](https://www.postgresql.org/docs/current/view-pg-backend-memory-contexts.html) /// which is only allowed for superusers. /// /// ```no_run /// # async fn f() -> Result<(), Box> { /// use sqlx::{Connection, Executor}; /// use sqlx::postgres::PgPoolOptions; /// /// let pool = PgPoolOptions::new() /// // Let connections live as long as they want. /// .max_lifetime(None) /// .after_release(|conn, meta| Box::pin(async move { /// // Only check connections older than 6 hours. /// if meta.age.as_secs() < 6 * 60 * 60 { /// return Ok(true); /// } /// /// let total_memory_usage: i64 = sqlx::query_scalar( /// "select sum(used_bytes) from pg_backend_memory_contexts" /// ) /// .fetch_one(conn) /// .await?; /// /// // Close the connection if the backend memory usage exceeds 256 MiB. /// Ok(total_memory_usage <= (1 << 28)) /// })) /// .connect("postgres:// …").await?; /// # Ok(()) /// # } pub fn after_release(mut self, callback: F) -> Self where for<'c> F: Fn(&'c mut DB::Connection, PoolConnectionMetadata) -> BoxFuture<'c, Result> + 'static + Send + Sync, { self.after_release = Some(Arc::new(callback)); self } /// Set the parent `Pool` from which the new pool will inherit its semaphore. /// /// This is currently an internal-only API. /// /// ### Panics /// If `self.max_connections` is greater than the setting the given pool was created with, /// or `self.fair` differs from the setting the given pool was created with. #[doc(hidden)] pub fn parent(mut self, pool: Pool) -> Self { self.parent_pool = Some(pool); self } /// Create a new pool from this `PoolOptions` and immediately open at least one connection. /// /// This ensures the configuration is correct. /// /// The total number of connections opened is max(1, [min_connections][Self::min_connections]). /// /// Refer to the relevant `ConnectOptions` impl for your database for the expected URL format: /// /// * Postgres: [`PgConnectOptions`][crate::postgres::PgConnectOptions] /// * MySQL: [`MySqlConnectOptions`][crate::mysql::MySqlConnectOptions] /// * SQLite: [`SqliteConnectOptions`][crate::sqlite::SqliteConnectOptions] /// * MSSQL: [`MssqlConnectOptions`][crate::mssql::MssqlConnectOptions] pub async fn connect(self, url: &str) -> Result, Error> { self.connect_with(url.parse()?).await } /// Create a new pool from this `PoolOptions` and immediately open at least one connection. /// /// This ensures the configuration is correct. /// /// The total number of connections opened is max(1, [min_connections][Self::min_connections]). pub async fn connect_with( self, options: ::Options, ) -> Result, Error> { // Don't take longer than `acquire_timeout` starting from when this is called. let deadline = Instant::now() + self.acquire_timeout; let inner = PoolInner::new_arc(self, options); if inner.options.min_connections > 0 { // If the idle reaper is spawned then this will race with the call from that task // and may not report any connection errors. inner.try_min_connections(deadline).await?; } // If `min_connections` is nonzero then we'll likely just pull a connection // from the idle queue here, but it should at least get tested first. let conn = inner.acquire().await?; inner.release(conn); Ok(Pool(inner)) } /// Create a new pool from this `PoolOptions`, but don't open any connections right now. /// /// If [`min_connections`][Self::min_connections] is set, a background task will be spawned to /// optimistically establish that many connections for the pool. /// /// Refer to the relevant `ConnectOptions` impl for your database for the expected URL format: /// /// * Postgres: [`PgConnectOptions`][crate::postgres::PgConnectOptions] /// * MySQL: [`MySqlConnectOptions`][crate::mysql::MySqlConnectOptions] /// * SQLite: [`SqliteConnectOptions`][crate::sqlite::SqliteConnectOptions] /// * MSSQL: [`MssqlConnectOptions`][crate::mssql::MssqlConnectOptions] pub fn connect_lazy(self, url: &str) -> Result, Error> { Ok(self.connect_lazy_with(url.parse()?)) } /// Create a new pool from this `PoolOptions`, but don't open any connections right now. /// /// If [`min_connections`][Self::min_connections] is set, a background task will be spawned to /// optimistically establish that many connections for the pool. pub fn connect_lazy_with(self, options: ::Options) -> Pool { // `min_connections` is guaranteed by the idle reaper now. Pool(PoolInner::new_arc(self, options)) } } impl Debug for PoolOptions { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("PoolOptions") .field("max_connections", &self.max_connections) .field("min_connections", &self.min_connections) .field("connect_timeout", &self.acquire_timeout) .field("max_lifetime", &self.max_lifetime) .field("idle_timeout", &self.idle_timeout) .field("test_before_acquire", &self.test_before_acquire) .finish() } } ================================================ FILE: sqlx-core/src/query.rs ================================================ use std::{future, marker::PhantomData}; use either::Either; use futures_core::stream::BoxStream; use futures_util::{StreamExt, TryFutureExt, TryStreamExt}; use crate::arguments::{Arguments, IntoArguments}; use crate::database::{Database, HasStatementCache}; use crate::encode::Encode; use crate::error::{BoxDynError, Error}; use crate::executor::{Execute, Executor}; use crate::sql_str::{SqlSafeStr, SqlStr}; use crate::statement::Statement; use crate::types::Type; /// A single SQL query as a prepared statement. Returned by [`query()`]. #[must_use = "query must be executed to affect database"] pub struct Query<'q, DB: Database, A> { pub(crate) statement: Either, pub(crate) arguments: Option>, pub(crate) database: PhantomData, pub(crate) persistent: bool, } /// A single SQL query that will map its results to an owned Rust type. /// /// Executes as a prepared statement. /// /// Returned by [`Query::try_map`], `query!()`, etc. Has most of the same methods as [`Query`] but /// the return types are changed to reflect the mapping. However, there is no equivalent of /// [`Query::execute`] as it doesn't make sense to map the result type and then ignore it. /// /// [`Query::bind`] is also omitted; stylistically we recommend placing your `.bind()` calls /// before `.try_map()`. This is also to prevent adding superfluous binds to the result of /// `query!()` et al. #[must_use = "query must be executed to affect database"] pub struct Map<'q, DB: Database, F, A> { inner: Query<'q, DB, A>, mapper: F, } impl<'q, DB, A> Execute<'q, DB> for Query<'q, DB, A> where DB: Database, A: Send + IntoArguments, { #[inline] fn sql(self) -> SqlStr { match self.statement { Either::Right(statement) => statement.sql().clone(), Either::Left(sql) => sql, } } fn statement(&self) -> Option<&DB::Statement> { match self.statement { Either::Right(statement) => Some(statement), Either::Left(_) => None, } } #[inline] fn take_arguments(&mut self) -> Result::Arguments>, BoxDynError> { self.arguments .take() .transpose() .map(|option| option.map(IntoArguments::into_arguments)) } #[inline] fn persistent(&self) -> bool { self.persistent } } impl Query<'_, DB, ::Arguments> { /// Bind a value for use with this SQL query. /// /// If the number of times this is called does not match the number of bind parameters that /// appear in the query (`?` for most SQL flavors, `$1 .. $N` for Postgres) then an error /// will be returned when this query is executed. /// /// There is no validation that the value is of the type expected by the query. Most SQL /// flavors will perform type coercion (Postgres will return a database error). /// /// If encoding the value fails, the error is stored and later surfaced when executing the query. pub fn bind<'t, T: Encode<'t, DB> + Type>(mut self, value: T) -> Self { let Ok(arguments) = self.get_arguments() else { return self; }; let argument_number = arguments.len() + 1; if let Err(error) = arguments.add(value) { self.arguments = Some(Err(format!( "Encoding argument ${argument_number} failed: {error}" ) .into())); } self } /// Like [`Query::bind`] but immediately returns an error if encoding a value failed. pub fn try_bind<'t, T: Encode<'t, DB> + Type>( &mut self, value: T, ) -> Result<(), BoxDynError> { let arguments = self.get_arguments()?; arguments.add(value) } fn get_arguments(&mut self) -> Result<&mut DB::Arguments, BoxDynError> { let Some(Ok(arguments)) = self.arguments.as_mut().map(Result::as_mut) else { return Err("A previous call to Query::bind produced an error" .to_owned() .into()); }; Ok(arguments) } } impl Query<'_, DB, A> where DB: Database + HasStatementCache, { /// If `true`, the statement will get prepared once and cached to the /// connection's statement cache. /// /// If queried once with the flag set to `true`, all subsequent queries /// matching the one with the flag will use the cached statement until the /// cache is cleared. /// /// If `false`, the prepared statement will be closed after execution. /// /// Default: `true`. pub fn persistent(mut self, value: bool) -> Self { self.persistent = value; self } } impl<'q, DB, A: Send> Query<'q, DB, A> where DB: Database, A: 'q + IntoArguments, { /// Map each row in the result to another type. /// /// See [`try_map`](Query::try_map) for a fallible version of this method. /// /// The [`query_as`](super::query_as::query_as) method will construct a mapped query using /// a [`FromRow`](super::from_row::FromRow) implementation. #[inline] pub fn map( self, mut f: F, ) -> Map<'q, DB, impl FnMut(DB::Row) -> Result + Send, A> where F: FnMut(DB::Row) -> O + Send, O: Unpin, { self.try_map(move |row| Ok(f(row))) } /// Map each row in the result to another type. /// /// The [`query_as`](super::query_as::query_as) method will construct a mapped query using /// a [`FromRow`](super::from_row::FromRow) implementation. #[inline] pub fn try_map(self, f: F) -> Map<'q, DB, F, A> where F: FnMut(DB::Row) -> Result + Send, O: Unpin, { Map { inner: self, mapper: f, } } /// Execute the query and return the total number of rows affected. #[inline] pub async fn execute<'e, 'c: 'e, E>(self, executor: E) -> Result where 'q: 'e, A: 'e, E: Executor<'c, Database = DB>, { executor.execute(self).await } /// Execute multiple queries and return the rows affected from each query, in a stream. #[inline] #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."] pub async fn execute_many<'e, 'c: 'e, E>( self, executor: E, ) -> BoxStream<'e, Result> where 'q: 'e, A: 'e, E: Executor<'c, Database = DB>, { executor.execute_many(self) } /// Execute the query and return the generated results as a stream. #[inline] pub fn fetch<'e, 'c: 'e, E>(self, executor: E) -> BoxStream<'e, Result> where 'q: 'e, A: 'e, E: Executor<'c, Database = DB>, { executor.fetch(self) } /// Execute multiple queries and return the generated results as a stream. /// /// For each query in the stream, any generated rows are returned first, /// then the `QueryResult` with the number of rows affected. #[inline] #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."] // TODO: we'll probably still want a way to get the `DB::QueryResult` at the end of a `fetch()` stream. pub fn fetch_many<'e, 'c: 'e, E>( self, executor: E, ) -> BoxStream<'e, Result, Error>> where 'q: 'e, A: 'e, E: Executor<'c, Database = DB>, { executor.fetch_many(self) } /// Execute the query and return all the resulting rows collected into a [`Vec`]. /// /// ### Note: beware result set size. /// This will attempt to collect the full result set of the query into memory. /// /// To avoid exhausting available memory, ensure the result set has a known upper bound, /// e.g. using `LIMIT`. #[inline] pub async fn fetch_all<'e, 'c: 'e, E>(self, executor: E) -> Result, Error> where 'q: 'e, A: 'e, E: Executor<'c, Database = DB>, { executor.fetch_all(self).await } /// Execute the query, returning the first row or [`Error::RowNotFound`] otherwise. /// /// ### Note: for best performance, ensure the query returns at most one row. /// Depending on the driver implementation, if your query can return more than one row, /// it may lead to wasted CPU time and bandwidth on the database server. /// /// Even when the driver implementation takes this into account, ensuring the query returns at most one row /// can result in a more optimal query plan. /// /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. /// /// Otherwise, you might want to add `LIMIT 1` to your query. #[inline] pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> Result where 'q: 'e, A: 'e, E: Executor<'c, Database = DB>, { executor.fetch_one(self).await } /// Execute the query, returning the first row or `None` otherwise. /// /// ### Note: for best performance, ensure the query returns at most one row. /// Depending on the driver implementation, if your query can return more than one row, /// it may lead to wasted CPU time and bandwidth on the database server. /// /// Even when the driver implementation takes this into account, ensuring the query returns at most one row /// can result in a more optimal query plan. /// /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. /// /// Otherwise, you might want to add `LIMIT 1` to your query. #[inline] pub async fn fetch_optional<'e, 'c: 'e, E>(self, executor: E) -> Result, Error> where 'q: 'e, A: 'e, E: Executor<'c, Database = DB>, { executor.fetch_optional(self).await } } impl<'q, DB, F: Send, A: Send> Execute<'q, DB> for Map<'q, DB, F, A> where DB: Database, A: IntoArguments, { #[inline] fn sql(self) -> SqlStr { self.inner.sql() } #[inline] fn statement(&self) -> Option<&DB::Statement> { self.inner.statement() } #[inline] fn take_arguments(&mut self) -> Result::Arguments>, BoxDynError> { self.inner.take_arguments() } #[inline] fn persistent(&self) -> bool { self.inner.arguments.is_some() } } impl<'q, DB, F, O, A> Map<'q, DB, F, A> where DB: Database, F: FnMut(DB::Row) -> Result + Send, O: Send + Unpin, A: 'q + Send + IntoArguments, { /// Map each row in the result to another type. /// /// See [`try_map`](Map::try_map) for a fallible version of this method. /// /// The [`query_as`](super::query_as::query_as) method will construct a mapped query using /// a [`FromRow`](super::from_row::FromRow) implementation. #[inline] pub fn map( self, mut g: G, ) -> Map<'q, DB, impl FnMut(DB::Row) -> Result + Send, A> where G: FnMut(O) -> P + Send, P: Unpin, { self.try_map(move |data| Ok(g(data))) } /// Map each row in the result to another type. /// /// The [`query_as`](super::query_as::query_as) method will construct a mapped query using /// a [`FromRow`](super::from_row::FromRow) implementation. #[inline] pub fn try_map( self, mut g: G, ) -> Map<'q, DB, impl FnMut(DB::Row) -> Result + Send, A> where G: FnMut(O) -> Result + Send, P: Unpin, { let mut f = self.mapper; Map { inner: self.inner, mapper: move |row| f(row).and_then(&mut g), } } /// Execute the query and return the generated results as a stream. pub fn fetch<'e, 'c: 'e, E>(self, executor: E) -> BoxStream<'e, Result> where 'q: 'e, E: 'e + Executor<'c, Database = DB>, DB: 'e, F: 'e, O: 'e, { // FIXME: this should have used `executor.fetch()` but that's a breaking change // because this technically allows multiple statements in one query string. #[allow(deprecated)] self.fetch_many(executor) .try_filter_map(|step| async move { Ok(match step { Either::Left(_) => None, Either::Right(o) => Some(o), }) }) .boxed() } /// Execute multiple queries and return the generated results as a stream /// from each query, in a stream. #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead."] pub fn fetch_many<'e, 'c: 'e, E>( mut self, executor: E, ) -> BoxStream<'e, Result, Error>> where 'q: 'e, E: 'e + Executor<'c, Database = DB>, DB: 'e, F: 'e, O: 'e, { Box::pin(try_stream! { let mut s = executor.fetch_many(self.inner); while let Some(v) = s.try_next().await? { r#yield!(match v { Either::Left(v) => Either::Left(v), Either::Right(row) => { Either::Right((self.mapper)(row)?) } }); } Ok(()) }) } /// Execute the query and return all the resulting rows collected into a [`Vec`]. /// /// ### Note: beware result set size. /// This will attempt to collect the full result set of the query into memory. /// /// To avoid exhausting available memory, ensure the result set has a known upper bound, /// e.g. using `LIMIT`. pub async fn fetch_all<'e, 'c: 'e, E>(self, executor: E) -> Result, Error> where 'q: 'e, E: 'e + Executor<'c, Database = DB>, DB: 'e, F: 'e, O: 'e, { self.fetch(executor).try_collect().await } /// Execute the query, returning the first row or [`Error::RowNotFound`] otherwise. /// /// ### Note: for best performance, ensure the query returns at most one row. /// Depending on the driver implementation, if your query can return more than one row, /// it may lead to wasted CPU time and bandwidth on the database server. /// /// Even when the driver implementation takes this into account, ensuring the query returns at most one row /// can result in a more optimal query plan. /// /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. /// /// Otherwise, you might want to add `LIMIT 1` to your query. pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> Result where 'q: 'e, E: 'e + Executor<'c, Database = DB>, DB: 'e, F: 'e, O: 'e, { self.fetch_optional(executor) .and_then(|row| { future::ready(match row { Some(row) => Ok(row), None => Err(Error::RowNotFound), }) }) .await } /// Execute the query, returning the first row or `None` otherwise. /// /// ### Note: for best performance, ensure the query returns at most one row. /// Depending on the driver implementation, if your query can return more than one row, /// it may lead to wasted CPU time and bandwidth on the database server. /// /// Even when the driver implementation takes this into account, ensuring the query returns at most one row /// can result in a more optimal query plan. /// /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. /// /// Otherwise, you might want to add `LIMIT 1` to your query. pub async fn fetch_optional<'e, 'c: 'e, E>(mut self, executor: E) -> Result, Error> where 'q: 'e, E: 'e + Executor<'c, Database = DB>, DB: 'e, F: 'e, O: 'e, { let row = executor.fetch_optional(self.inner).await?; if let Some(row) = row { (self.mapper)(row).map(Some) } else { Ok(None) } } } /// Execute a single SQL query as a prepared statement (explicitly created). pub fn query_statement(statement: &DB::Statement) -> Query<'_, DB, ::Arguments> where DB: Database, { Query { database: PhantomData, arguments: Some(Ok(Default::default())), statement: Either::Right(statement), persistent: true, } } /// Execute a single SQL query as a prepared statement (explicitly created), with the given arguments. pub fn query_statement_with(statement: &DB::Statement, arguments: A) -> Query<'_, DB, A> where DB: Database, A: IntoArguments, { Query { database: PhantomData, arguments: Some(Ok(arguments)), statement: Either::Right(statement), persistent: true, } } /// Execute a single SQL query as a prepared statement (transparently cached). /// /// The query string may only contain a single DML statement: `SELECT`, `INSERT`, `UPDATE`, `DELETE` and variants. /// The SQLite driver does not currently follow this restriction, but that behavior is deprecated. /// /// The connection will transparently prepare and cache the statement, which means it only needs to be parsed once /// in the connection's lifetime, and any generated query plans can be retained. /// Thus, the overhead of executing the statement is amortized. /// /// Some third-party databases that speak a supported protocol, e.g. CockroachDB or PGBouncer that speak Postgres, /// may have issues with the transparent caching of prepared statements. If you are having trouble, /// try setting [`.persistent(false)`][Query::persistent]. /// /// See the [`Query`] type for the methods you may call. /// /// ### Dynamic Input: Use Query Parameters (Prevents SQL Injection) /// At some point, you'll likely want to include some form of dynamic input in your query, possibly from the user. /// /// Your first instinct might be to do something like this: /// ```rust,no_run /// # async fn example() -> sqlx::Result<()> { /// # let mut conn: sqlx::PgConnection = unimplemented!(); /// // Imagine this is input from the user, e.g. a search form on a website. /// let user_input = "possibly untrustworthy input!"; /// /// // DO NOT DO THIS unless you're ABSOLUTELY CERTAIN it's what you need! /// let query = format!("SELECT * FROM articles WHERE content LIKE '%{user_input}%'"); /// // where `conn` is `PgConnection` or `MySqlConnection` /// // or some other type that implements `Executor`. /// let results = sqlx::query(sqlx::AssertSqlSafe(query)).fetch_all(&mut conn).await?; /// # Ok(()) /// # } /// ``` /// /// The example above showcases a **SQL injection vulnerability**, because it's trivial for a malicious user to craft /// an input that can "break out" of the string literal. /// /// For example, if they send the input `foo'; DELETE FROM articles; --` /// then your application would send the following to the database server (line breaks added for clarity): /// /// ```sql /// SELECT * FROM articles WHERE content LIKE '%foo'; /// DELETE FROM articles; /// --%' /// ``` /// /// In this case, because this interface *always* uses prepared statements, you would likely be fine because prepared /// statements _generally_ (see above) are only allowed to contain a single query. This would simply return an error. /// /// However, it would also break on legitimate user input. /// What if someone wanted to search for the string `Alice's Apples`? It would also return an error because /// the database would receive a query with a broken string literal (line breaks added for clarity): /// /// ```sql /// SELECT * FROM articles WHERE content LIKE '%Alice' /// s Apples%' /// ``` /// /// Of course, it's possible to make this syntactically valid by escaping the apostrophe, but there's a better way. /// /// ##### You should always prefer query parameters for dynamic input. /// /// When using query parameters, you add placeholders to your query where a value /// should be substituted at execution time, then call [`.bind()`][Query::bind] with that value. /// /// The syntax for placeholders is unfortunately not standardized and depends on the database: /// /// * Postgres and SQLite: use `$1`, `$2`, `$3`, etc. /// * The number is the Nth bound value, starting from one. /// * The same placeholder can be used arbitrarily many times to refer to the same bound value. /// * SQLite technically supports MySQL's syntax as well as others, but we recommend using this syntax /// as SQLx's SQLite driver is written with it in mind. /// * MySQL and MariaDB: use `?`. /// * Placeholders are purely positional, similar to `println!("{}, {}", foo, bar)`. /// * The order of bindings must match the order of placeholders in the query. /// * To use a value in multiple places, you must bind it multiple times. /// /// In both cases, the placeholder syntax acts as a variable expression representing the bound value: /// /// ```rust,no_run /// # async fn example2() -> sqlx::Result<()> { /// # let mut conn: sqlx::PgConnection = unimplemented!(); /// let user_input = "Alice's Apples"; /// /// // Postgres and SQLite /// let results = sqlx::query( /// // Notice how we only have to bind the argument once and we can use it multiple times: /// "SELECT * FROM articles /// WHERE title LIKE '%' || $1 || '%' /// OR content LIKE '%' || $1 || '%'" /// ) /// .bind(user_input) /// .fetch_all(&mut conn) /// .await?; /// /// // MySQL and MariaDB /// let results = sqlx::query( /// "SELECT * FROM articles /// WHERE title LIKE CONCAT('%', ?, '%') /// OR content LIKE CONCAT('%', ?, '%')" /// ) /// // If we want to reference the same value multiple times, we have to bind it multiple times: /// .bind(user_input) /// .bind(user_input) /// .fetch_all(&mut conn) /// .await?; /// # Ok(()) /// # } /// ``` /// ##### The value bound to a query parameter is entirely separate from the query and does not affect its syntax. /// Thus, SQL injection is impossible (barring shenanigans like calling a SQL function that lets you execute a string /// as a statement) and *all* strings are valid. /// /// This also means you cannot use query parameters to add conditional SQL fragments. /// /// **SQLx does not substitute placeholders on the client side**. It is done by the database server itself. /// /// ##### SQLx supports many different types for parameter binding, not just strings. /// Any type that implements [`Encode`][Encode] and [`Type`] can be bound as a parameter. /// /// See [the `types` module][crate::types] (links to `sqlx_core::types` but you should use `sqlx::types`) for details. /// /// As an additional benefit, query parameters are usually sent in a compact binary encoding instead of a human-readable /// text encoding, which saves bandwidth. pub fn query<'a, DB>(sql: impl SqlSafeStr) -> Query<'a, DB, ::Arguments> where DB: Database, { Query { database: PhantomData, arguments: Some(Ok(Default::default())), statement: Either::Left(sql.into_sql_str()), persistent: true, } } /// Execute a SQL query as a prepared statement (transparently cached), with the given arguments. /// /// See [`query()`][query] for details, such as supported syntax. pub fn query_with<'q, DB, A>(sql: impl SqlSafeStr, arguments: A) -> Query<'q, DB, A> where DB: Database, A: IntoArguments, { query_with_result(sql, Ok(arguments)) } /// Same as [`query_with`] but is initialized with a Result of arguments instead pub fn query_with_result<'q, DB, A>( sql: impl SqlSafeStr, arguments: Result, ) -> Query<'q, DB, A> where DB: Database, A: IntoArguments, { Query { database: PhantomData, arguments: Some(arguments), statement: Either::Left(sql.into_sql_str()), persistent: true, } } ================================================ FILE: sqlx-core/src/query_as.rs ================================================ use std::marker::PhantomData; use either::Either; use futures_core::stream::BoxStream; use futures_util::{StreamExt, TryStreamExt}; use crate::arguments::IntoArguments; use crate::database::{Database, HasStatementCache}; use crate::encode::Encode; use crate::error::{BoxDynError, Error}; use crate::executor::{Execute, Executor}; use crate::from_row::FromRow; use crate::query::{query, query_statement, query_statement_with, query_with_result, Query}; use crate::sql_str::{SqlSafeStr, SqlStr}; use crate::types::Type; /// A single SQL query as a prepared statement, mapping results using [`FromRow`]. /// Returned by [`query_as()`]. #[must_use = "query must be executed to affect database"] pub struct QueryAs<'q, DB: Database, O, A> { pub(crate) inner: Query<'q, DB, A>, pub(crate) output: PhantomData, } impl<'q, DB, O: Send, A: Send> Execute<'q, DB> for QueryAs<'q, DB, O, A> where DB: Database, A: 'q + IntoArguments, { #[inline] fn sql(self) -> SqlStr { self.inner.sql() } #[inline] fn statement(&self) -> Option<&DB::Statement> { self.inner.statement() } #[inline] fn take_arguments(&mut self) -> Result::Arguments>, BoxDynError> { self.inner.take_arguments() } #[inline] fn persistent(&self) -> bool { self.inner.persistent() } } impl<'q, DB: Database, O> QueryAs<'q, DB, O, ::Arguments> { /// Bind a value for use with this SQL query. /// /// See [`Query::bind`](Query::bind). pub fn bind + Type>(mut self, value: T) -> Self { self.inner = self.inner.bind(value); self } } impl QueryAs<'_, DB, O, A> where DB: Database + HasStatementCache, { /// If `true`, the statement will get prepared once and cached to the /// connection's statement cache. /// /// If queried once with the flag set to `true`, all subsequent queries /// matching the one with the flag will use the cached statement until the /// cache is cleared. /// /// If `false`, the prepared statement will be closed after execution. /// /// Default: `true`. pub fn persistent(mut self, value: bool) -> Self { self.inner = self.inner.persistent(value); self } } // FIXME: This is very close, nearly 1:1 with `Map` // noinspection DuplicatedCode impl<'q, DB, O, A> QueryAs<'q, DB, O, A> where DB: Database, A: 'q + IntoArguments, O: Send + Unpin + for<'r> FromRow<'r, DB::Row>, { /// Execute the query and return the generated results as a stream. pub fn fetch<'e, 'c: 'e, E>(self, executor: E) -> BoxStream<'e, Result> where 'q: 'e, E: 'e + Executor<'c, Database = DB>, DB: 'e, O: 'e, A: 'e, { executor .fetch(self.inner) .map(|row| O::from_row(&row?)) .boxed() } /// Execute multiple queries and return the generated results as a stream /// from each query, in a stream. #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."] pub fn fetch_many<'e, 'c: 'e, E>( self, executor: E, ) -> BoxStream<'e, Result, Error>> where 'q: 'e, E: 'e + Executor<'c, Database = DB>, DB: 'e, O: 'e, A: 'e, { executor .fetch_many(self.inner) .map(|v| match v { Ok(Either::Right(row)) => O::from_row(&row).map(Either::Right), Ok(Either::Left(v)) => Ok(Either::Left(v)), Err(e) => Err(e), }) .boxed() } /// Execute the query and return all the resulting rows collected into a [`Vec`]. /// /// ### Note: beware result set size. /// This will attempt to collect the full result set of the query into memory. /// /// To avoid exhausting available memory, ensure the result set has a known upper bound, /// e.g. using `LIMIT`. #[inline] pub async fn fetch_all<'e, 'c: 'e, E>(self, executor: E) -> Result, Error> where 'q: 'e, E: 'e + Executor<'c, Database = DB>, DB: 'e, O: 'e, A: 'e, { self.fetch(executor).try_collect().await } /// Execute the query, returning the first row or [`Error::RowNotFound`] otherwise. /// /// ### Note: for best performance, ensure the query returns at most one row. /// Depending on the driver implementation, if your query can return more than one row, /// it may lead to wasted CPU time and bandwidth on the database server. /// /// Even when the driver implementation takes this into account, ensuring the query returns at most one row /// can result in a more optimal query plan. /// /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. /// /// Otherwise, you might want to add `LIMIT 1` to your query. pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> Result where 'q: 'e, E: 'e + Executor<'c, Database = DB>, DB: 'e, O: 'e, A: 'e, { self.fetch_optional(executor) .await .and_then(|row| row.ok_or(Error::RowNotFound)) } /// Execute the query, returning the first row or `None` otherwise. /// /// ### Note: for best performance, ensure the query returns at most one row. /// Depending on the driver implementation, if your query can return more than one row, /// it may lead to wasted CPU time and bandwidth on the database server. /// /// Even when the driver implementation takes this into account, ensuring the query returns at most one row /// can result in a more optimal query plan. /// /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. /// /// Otherwise, you might want to add `LIMIT 1` to your query. pub async fn fetch_optional<'e, 'c: 'e, E>(self, executor: E) -> Result, Error> where 'q: 'e, E: 'e + Executor<'c, Database = DB>, DB: 'e, O: 'e, A: 'e, { let row = executor.fetch_optional(self.inner).await?; if let Some(row) = row { O::from_row(&row).map(Some) } else { Ok(None) } } } /// Execute a single SQL query as a prepared statement (transparently cached). /// Maps rows to Rust types using [`FromRow`]. /// /// For details about prepared statements and allowed SQL syntax, see [`query()`][crate::query::query]. /// /// ### Example: Map Rows using Tuples /// [`FromRow`] is implemented for tuples of up to 16 elements1. /// Using a tuple of N elements will extract the first N columns from each row using [`Decode`][crate::decode::Decode]. /// Any extra columns are ignored. /// /// See [`sqlx::types`][crate::types] for the types that can be used. /// /// The `FromRow` implementation will check [`Type::compatible()`] for each column to ensure a compatible type mapping /// is used. If an incompatible mapping is detected, an error is returned. /// To statically assert compatible types at compile time, see the `query!()` family of macros. /// /// **NOTE**: `SELECT *` is not recommended with this approach because the ordering of returned columns may be different /// than expected, especially when using joins. /// /// ```rust,no_run /// # async fn example1() -> sqlx::Result<()> { /// use sqlx::Connection; /// use sqlx::PgConnection; /// /// // This example can be applied to any database as it only uses standard types and syntax. /// let mut conn: PgConnection = PgConnection::connect("").await?; /// /// sqlx::raw_sql( /// "CREATE TABLE users(id INTEGER PRIMARY KEY, username TEXT UNIQUE, created_at TIMESTAMPTZ DEFAULT (now()))" /// ) /// .execute(&mut conn) /// .await?; /// /// sqlx::query("INSERT INTO users(id, username) VALUES (1, 'alice'), (2, 'bob');") /// .execute(&mut conn) /// .await?; /// /// // Get the first row of the result (note the `LIMIT 1` for efficiency) /// // This assumes the `time` feature of SQLx is enabled. /// let oldest_user: (i32, String, time::OffsetDateTime) = sqlx::query_as( /// "SELECT id, username, created_at FROM users ORDER BY created_at LIMIT 1" /// ) /// .fetch_one(&mut conn) /// .await?; /// /// assert_eq!(oldest_user.0, 1); /// assert_eq!(oldest_user.1, "alice"); /// /// // Get at most one row /// let maybe_charlie: Option<(i32, String, time::OffsetDateTime)> = sqlx::query_as( /// "SELECT id, username, created_at FROM users WHERE username = 'charlie'" /// ) /// .fetch_optional(&mut conn) /// .await?; /// /// assert_eq!(maybe_charlie, None); /// /// // Get all rows in result (Beware of the size of the result set! Consider using `LIMIT`) /// let users: Vec<(i32, String, time::OffsetDateTime)> = sqlx::query_as( /// "SELECT id, username, created_at FROM users ORDER BY id" /// ) /// .fetch_all(&mut conn) /// .await?; /// /// println!("{users:?}"); /// # Ok(()) /// # } /// ``` /// /// 1: It's impossible in Rust to implement a trait for tuples of arbitrary size. /// For larger result sets, either use an explicit struct (see below) or use [`query()`][crate::query::query] /// instead and extract columns dynamically. /// /// ### Example: Map Rows using `#[derive(FromRow)]` /// Using `#[derive(FromRow)]`, we can create a Rust struct to represent our row type /// so we can look up fields by name instead of tuple index. /// /// When querying this way, columns will be matched up to the corresponding fields by name, so `SELECT *` is safe to use. /// However, you will still want to be aware of duplicate column names in your query when using joins. /// /// The derived `FromRow` implementation will check [`Type::compatible()`] for each column to ensure a compatible type /// mapping is used. If an incompatible mapping is detected, an error is returned. /// To statically assert compatible types at compile time, see the `query!()` family of macros. /// /// An error will also be returned if an expected column is missing from the result set. /// /// `#[derive(FromRow)]` supports several control attributes which can be used to change how column names and types /// are mapped. See [`FromRow`] for details. /// /// Using our previous table definition, we can convert our queries like so: /// ```rust,no_run /// # async fn example2() -> sqlx::Result<()> { /// use sqlx::Connection; /// use sqlx::PgConnection; /// /// use time::OffsetDateTime; /// /// #[derive(sqlx::FromRow, Debug, PartialEq, Eq)] /// struct User { /// id: i64, /// username: String, /// // Note: the derive won't compile if the `time` feature of SQLx is not enabled. /// created_at: OffsetDateTime, /// } /// /// let mut conn: PgConnection = PgConnection::connect("").await?; /// /// // Get the first row of the result (note the `LIMIT 1` for efficiency) /// let oldest_user: User = sqlx::query_as( /// "SELECT id, username, created_at FROM users ORDER BY created_at LIMIT 1" /// ) /// .fetch_one(&mut conn) /// .await?; /// /// assert_eq!(oldest_user.id, 1); /// assert_eq!(oldest_user.username, "alice"); /// /// // Get at most one row /// let maybe_charlie: Option = sqlx::query_as( /// "SELECT id, username, created_at FROM users WHERE username = 'charlie'" /// ) /// .fetch_optional(&mut conn) /// .await?; /// /// assert_eq!(maybe_charlie, None); /// /// // Get all rows in result (Beware of the size of the result set! Consider using `LIMIT`) /// let users: Vec = sqlx::query_as( /// "SELECT id, username, created_at FROM users ORDER BY id" /// ) /// .fetch_all(&mut conn) /// .await?; /// /// assert_eq!(users[1].id, 2); /// assert_eq!(users[1].username, "bob"); /// # Ok(()) /// # } /// /// ``` #[inline] pub fn query_as<'q, DB, O>(sql: impl SqlSafeStr) -> QueryAs<'q, DB, O, ::Arguments> where DB: Database, O: for<'r> FromRow<'r, DB::Row>, { QueryAs { inner: query(sql), output: PhantomData, } } /// Execute a single SQL query, with the given arguments as a prepared statement (transparently cached). /// Maps rows to Rust types using [`FromRow`]. /// /// For details about prepared statements and allowed SQL syntax, see [`query()`][crate::query::query]. /// /// For details about type mapping from [`FromRow`], see [`query_as()`]. #[inline] pub fn query_as_with<'q, DB, O, A>(sql: impl SqlSafeStr, arguments: A) -> QueryAs<'q, DB, O, A> where DB: Database, A: IntoArguments, O: for<'r> FromRow<'r, DB::Row>, { query_as_with_result(sql, Ok(arguments)) } /// Same as [`query_as_with`] but takes arguments as a Result #[inline] pub fn query_as_with_result<'q, DB, O, A>( sql: impl SqlSafeStr, arguments: Result, ) -> QueryAs<'q, DB, O, A> where DB: Database, A: IntoArguments, O: for<'r> FromRow<'r, DB::Row>, { QueryAs { inner: query_with_result(sql, arguments), output: PhantomData, } } // Make a SQL query from a statement, that is mapped to a concrete type. pub fn query_statement_as( statement: &DB::Statement, ) -> QueryAs<'_, DB, O, ::Arguments> where DB: Database, O: for<'r> FromRow<'r, DB::Row>, { QueryAs { inner: query_statement(statement), output: PhantomData, } } // Make a SQL query from a statement, with the given arguments, that is mapped to a concrete type. pub fn query_statement_as_with<'q, DB, O, A>( statement: &'q DB::Statement, arguments: A, ) -> QueryAs<'q, DB, O, A> where DB: Database, A: IntoArguments, O: for<'r> FromRow<'r, DB::Row>, { QueryAs { inner: query_statement_with(statement, arguments), output: PhantomData, } } ================================================ FILE: sqlx-core/src/query_builder.rs ================================================ //! Runtime query-builder API. use std::fmt::Display; use std::fmt::Write; use std::marker::PhantomData; use std::sync::Arc; use crate::arguments::{Arguments, IntoArguments}; use crate::database::Database; use crate::encode::Encode; use crate::from_row::FromRow; use crate::query::Query; use crate::query_as::QueryAs; use crate::query_scalar::QueryScalar; use crate::sql_str::AssertSqlSafe; use crate::sql_str::SqlSafeStr; use crate::sql_str::SqlStr; use crate::types::Type; use crate::Either; /// A builder type for constructing queries at runtime. /// /// See [`.push_values()`][Self::push_values] for an example of building a bulk `INSERT` statement. /// Note, however, that with Postgres you can get much better performance by using arrays /// and `UNNEST()`. [See our FAQ] for details. /// /// [See our FAQ]: https://github.com/launchbadge/sqlx/blob/master/FAQ.md#how-can-i-bind-an-array-to-a-values-clause-how-can-i-do-bulk-inserts pub struct QueryBuilder where DB: Database, { query: Arc, init_len: usize, arguments: Option<::Arguments>, } impl Default for QueryBuilder { fn default() -> Self { QueryBuilder { init_len: 0, query: Default::default(), arguments: Some(Default::default()), } } } const ERROR: &str = "BUG: query must not be shared at this point in time"; impl QueryBuilder where DB: Database, { // `init` is provided because a query will almost always start with a constant fragment // such as `INSERT INTO ...` or `SELECT ...`, etc. /// Start building a query with an initial SQL fragment, which may be an empty string. pub fn new(init: impl Into) -> Self where ::Arguments: Default, { let init = init.into(); QueryBuilder { init_len: init.len(), query: init.into(), arguments: Some(Default::default()), } } /// Construct a `QueryBuilder` with existing SQL and arguments. /// /// ### Note /// This does *not* check if `arguments` is valid for the given SQL. pub fn with_arguments(init: impl Into, arguments: A) -> Self where DB: Database, A: IntoArguments, { let init = init.into(); QueryBuilder { init_len: init.len(), query: init.into(), arguments: Some(arguments.into_arguments()), } } #[inline] fn sanity_check(&self) { assert!( self.arguments.is_some(), "QueryBuilder must be reset before reuse after `.build()`" ); } /// Append a SQL fragment to the query. /// /// May be a string or anything that implements `Display`. /// You can also use `format_args!()` here to push a formatted string without an intermediate /// allocation. /// /// ### Warning: Beware SQL Injection Vulnerabilities and Untrusted Input! /// You should *not* use this to insert input directly into the query from an untrusted user as /// this can be used by an attacker to extract sensitive data or take over your database. /// /// Security breaches due to SQL injection can cost your organization a lot of money from /// damage control and lost clients, betray the trust of your users in your system, and are just /// plain embarrassing. If you are unfamiliar with the threat that SQL injection imposes, you /// should take some time to learn more about it before proceeding: /// /// * [SQL Injection on OWASP.org](https://owasp.org/www-community/attacks/SQL_Injection) /// * [SQL Injection on Wikipedia](https://en.wikipedia.org/wiki/SQL_injection) /// * See "Examples" for notable instances of security breaches due to SQL injection. /// /// This method does *not* perform sanitization. Instead, you should use /// [`.push_bind()`][Self::push_bind] which inserts a placeholder into the query and then /// sends the possibly untrustworthy value separately (called a "bind argument") so that it /// cannot be misinterpreted by the database server. /// /// Note that you should still at least have some sort of sanity checks on the values you're /// sending as that's just good practice and prevent other types of attacks against your system, /// e.g. check that strings aren't too long, numbers are within expected ranges, etc. pub fn push(&mut self, sql: impl Display) -> &mut Self { self.sanity_check(); let query: &mut String = Arc::get_mut(&mut self.query).expect(ERROR); write!(query, "{sql}").expect("error formatting `sql`"); self } /// Push a bind argument placeholder (`?` or `$N` for Postgres) and bind a value to it. /// /// ### Note: Database-specific Limits /// Note that every database has a practical limit on the number of bind parameters /// you can add to a single query. This varies by database. /// /// While you should consult the manual of your specific database version and/or current /// configuration for the exact value as it may be different than listed here, /// the defaults for supported databases as of writing are as follows: /// /// * Postgres and MySQL: 65535 /// * You may find sources that state that Postgres has a limit of 32767, /// but that is a misinterpretation of the specification by the JDBC driver implementation /// as discussed in [this Github issue][postgres-limit-issue]. Postgres itself /// asserts that the number of parameters is in the range `[0, 65535)`. /// * SQLite: 32766 (configurable by [`SQLITE_LIMIT_VARIABLE_NUMBER`]) /// * SQLite prior to 3.32.0: 999 /// * MSSQL: 2100 /// /// Exceeding these limits may panic (as a sanity check) or trigger a database error at runtime /// depending on the implementation. /// /// [`SQLITE_LIMIT_VARIABLE_NUMBER`]: https://www.sqlite.org/limits.html#max_variable_number /// [postgres-limit-issue]: https://github.com/launchbadge/sqlx/issues/671#issuecomment-687043510 pub fn push_bind<'t, T>(&mut self, value: T) -> &mut Self where T: Encode<'t, DB> + Type, { self.sanity_check(); let arguments = self .arguments .as_mut() .expect("BUG: Arguments taken already"); arguments.add(value).expect("Failed to add argument"); let query: &mut String = Arc::get_mut(&mut self.query).expect(ERROR); arguments .format_placeholder(query) .expect("error in format_placeholder"); self } /// Start a list separated by `separator`. /// /// The returned type exposes identical [`.push()`][Separated::push] and /// [`.push_bind()`][Separated::push_bind] methods which push `separator` to the query /// before their normal behavior. [`.push_unseparated()`][Separated::push_unseparated] and [`.push_bind_unseparated()`][Separated::push_bind_unseparated] are also /// provided to push a SQL fragment without the separator. /// /// ```rust /// # #[cfg(feature = "mysql")] { /// use sqlx::{Execute, MySql, QueryBuilder}; /// let foods = vec!["pizza".to_string(), "chips".to_string()]; /// let mut query_builder: QueryBuilder = QueryBuilder::new( /// "SELECT * from food where name in (" /// ); /// // One element vector is handled correctly but an empty vector /// // would cause a sql syntax error /// let mut separated = query_builder.separated(", "); /// for value_type in foods.iter() { /// separated.push_bind(value_type); /// } /// separated.push_unseparated(") "); /// /// let mut query = query_builder.build(); /// let sql = query.sql(); /// assert!(sql.ends_with("in (?, ?) ")); /// # } /// ``` pub fn separated(&mut self, separator: Sep) -> Separated<'_, DB, Sep> where Sep: Display, { self.sanity_check(); Separated { query_builder: self, separator, push_separator: false, } } // Most of the `QueryBuilder` API is purposefully very low-level but this was a commonly // requested use-case so it made sense to support. /// Push a `VALUES` clause where each item in `tuples` represents a tuple/row in the clause. /// /// This can be used to construct a bulk `INSERT` statement, although keep in mind that all /// databases have some practical limit on the number of bind arguments in a single query. /// See [`.push_bind()`][Self::push_bind] for details. /// /// To be safe, you can do `tuples.into_iter().take(N)` where `N` is the limit for your database /// divided by the number of fields in each tuple; since integer division always rounds down, /// this will ensure that you don't exceed the limit. /// /// ### Notes /// /// If `tuples` is empty, this will likely produce a syntactically invalid query as `VALUES` /// generally expects to be followed by at least 1 tuple. /// /// If `tuples` can have many different lengths, you may want to call /// [`.persistent(false)`][Query::persistent] after [`.build()`][Self::build] to avoid /// filling up the connection's prepared statement cache. /// /// Because the `Arguments` API has a lifetime that must live longer than `Self`, you cannot /// bind by-reference from an iterator unless that iterator yields references that live /// longer than `Self`, even if the specific `Arguments` implementation doesn't actually /// borrow the values (like `MySqlArguments` and `PgArguments` immediately encode the arguments /// and don't borrow them past the `.add()` call). /// /// So basically, if you want to bind by-reference you need an iterator that yields references, /// e.g. if you have values in a `Vec` you can do `.iter()` instead of `.into_iter()`. The /// example below uses an iterator that creates values on the fly /// and so cannot bind by-reference. /// /// ### Example (MySQL) /// /// ```rust /// # #[cfg(feature = "mysql")] /// # { /// use sqlx::{Execute, MySql, QueryBuilder}; /// /// struct User { /// id: i32, /// username: String, /// email: String, /// password: String, /// } /// /// // The number of parameters in MySQL must fit in a `u16`. /// const BIND_LIMIT: usize = 65535; /// /// // This would normally produce values forever! /// let users = (0..).map(|i| User { /// id: i, /// username: format!("test_user_{i}"), /// email: format!("test-user-{i}@example.com"), /// password: format!("Test!User@Password#{i}"), /// }); /// /// let mut query_builder: QueryBuilder = QueryBuilder::new( /// // Note the trailing space; most calls to `QueryBuilder` don't automatically insert /// // spaces as that might interfere with identifiers or quoted strings where exact /// // values may matter. /// "INSERT INTO users(id, username, email, password) " /// ); /// /// // Note that `.into_iter()` wasn't needed here since `users` is already an iterator. /// query_builder.push_values(users.take(BIND_LIMIT / 4), |mut b, user| { /// // If you wanted to bind these by-reference instead of by-value, /// // you'd need an iterator that yields references that live as long as `query_builder`, /// // e.g. collect it to a `Vec` first. /// b.push_bind(user.id) /// .push_bind(user.username) /// .push_bind(user.email) /// .push_bind(user.password); /// }); /// /// let mut query = query_builder.build(); /// /// // You can then call `query.execute()`, `.fetch_one()`, `.fetch_all()`, etc. /// // For the sake of demonstration though, we're just going to assert the contents /// // of the query. /// /// // These are methods of the `Execute` trait, not normally meant to be called in user code. /// let sql = query.sql(); /// let arguments = query.take_arguments().unwrap(); /// /// assert!(sql.starts_with( /// "INSERT INTO users(id, username, email, password) VALUES (?, ?, ?, ?), (?, ?, ?, ?)" /// )); /// /// assert!(sql.ends_with("(?, ?, ?, ?)")); /// /// // Not a normally exposed function, only used for this doctest. /// // 65535 / 4 = 16383 (rounded down) /// // 16383 * 4 = 65532 /// assert_eq!(arguments.len(), 65532); /// # } /// ``` pub fn push_values(&mut self, tuples: I, mut push_tuple: F) -> &mut Self where I: IntoIterator, F: FnMut(Separated<'_, DB, &'static str>, I::Item), { self.sanity_check(); self.push("VALUES "); let mut separated = self.separated(", "); for tuple in tuples { separated.push("("); // use a `Separated` with a separate (hah) internal state push_tuple(separated.query_builder.separated(", "), tuple); separated.push_unseparated(")"); } debug_assert!( separated.push_separator, "No value being pushed. QueryBuilder may not build correct sql query!" ); separated.query_builder } /// Creates `((a, b), (..)` statements, from `tuples`. /// /// This can be used to construct a bulk `SELECT` statement like this: /// ```sql /// SELECT * FROM users WHERE (id, username) IN ((1, "test_user_1"), (2, "test_user_2")) /// ``` /// /// Although keep in mind that all /// databases have some practical limit on the number of bind arguments in a single query. /// See [`.push_bind()`][Self::push_bind] for details. /// /// To be safe, you can do `tuples.into_iter().take(N)` where `N` is the limit for your database /// divided by the number of fields in each tuple; since integer division always rounds down, /// this will ensure that you don't exceed the limit. /// /// ### Notes /// /// If `tuples` is empty, this will likely produce a syntactically invalid query /// /// ### Example (MySQL) /// /// ```rust /// # #[cfg(feature = "mysql")] /// # { /// use sqlx::{Execute, MySql, QueryBuilder}; /// /// struct User { /// id: i32, /// username: String, /// email: String, /// password: String, /// } /// /// // The number of parameters in MySQL must fit in a `u16`. /// const BIND_LIMIT: usize = 65535; /// /// // This would normally produce values forever! /// let users = (0..).map(|i| User { /// id: i, /// username: format!("test_user_{i}"), /// email: format!("test-user-{i}@example.com"), /// password: format!("Test!User@Password#{i}"), /// }); /// /// let mut query_builder: QueryBuilder = QueryBuilder::new( /// // Note the trailing space; most calls to `QueryBuilder` don't automatically insert /// // spaces as that might interfere with identifiers or quoted strings where exact /// // values may matter. /// "SELECT * FROM users WHERE (id, username, email, password) in" /// ); /// /// // Note that `.into_iter()` wasn't needed here since `users` is already an iterator. /// query_builder.push_tuples(users.take(BIND_LIMIT / 4), |mut b, user| { /// // If you wanted to bind these by-reference instead of by-value, /// // you'd need an iterator that yields references that live as long as `query_builder`, /// // e.g. collect it to a `Vec` first. /// b.push_bind(user.id) /// .push_bind(user.username) /// .push_bind(user.email) /// .push_bind(user.password); /// }); /// /// let mut query = query_builder.build(); /// /// // You can then call `query.execute()`, `.fetch_one()`, `.fetch_all()`, etc. /// // For the sake of demonstration though, we're just going to assert the contents /// // of the query. /// /// // These are methods of the `Execute` trait, not normally meant to be called in user code. /// let sql = query.sql(); /// let arguments = query.take_arguments().unwrap(); /// /// assert!(sql.starts_with( /// "SELECT * FROM users WHERE (id, username, email, password) in ((?, ?, ?, ?), (?, ?, ?, ?), " /// )); /// /// assert!(sql.ends_with("(?, ?, ?, ?)) ")); /// /// // Not a normally exposed function, only used for this doctest. /// // 65535 / 4 = 16383 (rounded down) /// // 16383 * 4 = 65532 /// assert_eq!(arguments.len(), 65532); /// } /// ``` pub fn push_tuples(&mut self, tuples: I, mut push_tuple: F) -> &mut Self where I: IntoIterator, F: FnMut(Separated<'_, DB, &'static str>, I::Item), { self.sanity_check(); self.push(" ("); let mut separated = self.separated(", "); for tuple in tuples { separated.push("("); push_tuple(separated.query_builder.separated(", "), tuple); separated.push_unseparated(")"); } separated.push_unseparated(") "); separated.query_builder } /// Produce an executable query from this builder. /// /// ### Note: Query is not Checked /// It is your responsibility to ensure that you produce a syntactically correct query here, /// this API has no way to check it for you. /// /// ### Note: Reuse /// You can reuse this builder afterwards to amortize the allocation overhead of the query /// string, however you must call [`.reset()`][Self::reset] first, which returns `Self` /// to the state it was in immediately after [`new()`][Self::new]. /// /// Calling any other method but `.reset()` after `.build()` will panic for sanity reasons. pub fn build(&mut self) -> Query<'_, DB, ::Arguments> { self.sanity_check(); Query { statement: Either::Left(self.sql()), arguments: self.arguments.take().map(Ok), database: PhantomData, persistent: true, } } /// Produce an executable query from this builder. /// /// ### Note: Query is not Checked /// It is your responsibility to ensure that you produce a syntactically correct query here, /// this API has no way to check it for you. /// /// ### Note: Reuse /// You can reuse this builder afterwards to amortize the allocation overhead of the query /// string, however you must call [`.reset()`][Self::reset] first, which returns `Self` /// to the state it was in immediately after [`new()`][Self::new]. /// /// Calling any other method but `.reset()` after `.build()` will panic for sanity reasons. pub fn build_query_as<'q, T: FromRow<'q, DB::Row>>( &'q mut self, ) -> QueryAs<'q, DB, T, ::Arguments> { QueryAs { inner: self.build(), output: PhantomData, } } /// Produce an executable query from this builder. /// /// ### Note: Query is not Checked /// It is your responsibility to ensure that you produce a syntactically correct query here, /// this API has no way to check it for you. /// /// ### Note: Reuse /// You can reuse this builder afterwards to amortize the allocation overhead of the query /// string, however you must call [`.reset()`][Self::reset] first, which returns `Self` /// to the state it was in immediately after [`new()`][Self::new]. /// /// Calling any other method but `.reset()` after `.build()` will panic for sanity reasons. pub fn build_query_scalar<'q, T>( &'q mut self, ) -> QueryScalar<'q, DB, T, ::Arguments> where DB: Database, (T,): for<'r> FromRow<'r, DB::Row>, { QueryScalar { inner: self.build_query_as(), } } /// Reset this `QueryBuilder` back to its initial state. /// /// The query is truncated to the initial fragment provided to [`new()`][Self::new] and /// the bind arguments are reset. pub fn reset(&mut self) -> &mut Self { // Someone can hold onto a clone of `self.query`, to avoid panicking here we should just // allocate a new `String`. let query: &mut String = Arc::make_mut(&mut self.query); query.truncate(self.init_len); self.arguments = Some(Default::default()); self } /// Get the current build SQL; **note**: may not be syntactically correct. pub fn sql(&self) -> SqlStr { AssertSqlSafe(self.query.clone()).into_sql_str() } /// Deconstruct this `QueryBuilder`, returning the built SQL. May not be syntactically correct. pub fn into_string(self) -> String { Arc::unwrap_or_clone(self.query) } /// Deconstruct this `QueryBuilder`, returning the built SQL. May not be syntactically correct. pub fn into_sql(self) -> SqlStr { AssertSqlSafe(self.query).into_sql_str() } } /// A wrapper around `QueryBuilder` for creating comma(or other token)-separated lists. /// /// See [`QueryBuilder::separated()`] for details. #[allow(explicit_outlives_requirements)] pub struct Separated<'qb, DB, Sep> where DB: Database, { query_builder: &'qb mut QueryBuilder, separator: Sep, push_separator: bool, } impl Separated<'_, DB, Sep> where DB: Database, Sep: Display, { /// Push the separator if applicable, and then the given SQL fragment. /// /// See [`QueryBuilder::push()`] for details. pub fn push(&mut self, sql: impl Display) -> &mut Self { if self.push_separator { self.query_builder .push(format_args!("{}{}", self.separator, sql)); } else { self.query_builder.push(sql); self.push_separator = true; } self } /// Push a SQL fragment without a separator. /// /// Simply calls [`QueryBuilder::push()`] directly. pub fn push_unseparated(&mut self, sql: impl Display) -> &mut Self { self.query_builder.push(sql); self } /// Push the separator if applicable, then append a bind argument. /// /// See [`QueryBuilder::push_bind()`] for details. pub fn push_bind<'t, T>(&mut self, value: T) -> &mut Self where T: Encode<'t, DB> + Type, { if self.push_separator { self.query_builder.push(&self.separator); } self.query_builder.push_bind(value); self.push_separator = true; self } /// Push a bind argument placeholder (`?` or `$N` for Postgres) and bind a value to it /// without a separator. /// /// Simply calls [`QueryBuilder::push_bind()`] directly. pub fn push_bind_unseparated<'t, T>(&mut self, value: T) -> &mut Self where T: Encode<'t, DB> + Type, { self.query_builder.push_bind(value); self } } ================================================ FILE: sqlx-core/src/query_scalar.rs ================================================ use either::Either; use futures_core::stream::BoxStream; use futures_util::{StreamExt, TryFutureExt, TryStreamExt}; use crate::arguments::IntoArguments; use crate::database::{Database, HasStatementCache}; use crate::encode::Encode; use crate::error::{BoxDynError, Error}; use crate::executor::{Execute, Executor}; use crate::from_row::FromRow; use crate::query_as::{ query_as, query_as_with_result, query_statement_as, query_statement_as_with, QueryAs, }; use crate::sql_str::{SqlSafeStr, SqlStr}; use crate::types::Type; /// A single SQL query as a prepared statement which extracts only the first column of each row. /// Returned by [`query_scalar()`]. #[must_use = "query must be executed to affect database"] pub struct QueryScalar<'q, DB: Database, O, A> { pub(crate) inner: QueryAs<'q, DB, (O,), A>, } impl<'q, DB: Database, O: Send, A: Send> Execute<'q, DB> for QueryScalar<'q, DB, O, A> where A: 'q + IntoArguments, { #[inline] fn sql(self) -> SqlStr { self.inner.sql() } fn statement(&self) -> Option<&DB::Statement> { self.inner.statement() } #[inline] fn take_arguments(&mut self) -> Result::Arguments>, BoxDynError> { self.inner.take_arguments() } #[inline] fn persistent(&self) -> bool { Execute::persistent(&self.inner) } } impl<'q, DB: Database, O> QueryScalar<'q, DB, O, ::Arguments> { /// Bind a value for use with this SQL query. /// /// See [`Query::bind`](crate::query::Query::bind). pub fn bind + Type>(mut self, value: T) -> Self { self.inner = self.inner.bind(value); self } } impl QueryScalar<'_, DB, O, A> where DB: Database + HasStatementCache, { /// If `true`, the statement will get prepared once and cached to the /// connection's statement cache. /// /// If queried once with the flag set to `true`, all subsequent queries /// matching the one with the flag will use the cached statement until the /// cache is cleared. /// /// If `false`, the prepared statement will be closed after execution. /// /// Default: `true`. pub fn persistent(mut self, value: bool) -> Self { self.inner = self.inner.persistent(value); self } } // FIXME: This is very close, nearly 1:1 with `Map` // noinspection DuplicatedCode impl<'q, DB, O, A> QueryScalar<'q, DB, O, A> where DB: Database, O: Send + Unpin, A: 'q + IntoArguments, (O,): Send + Unpin + for<'r> FromRow<'r, DB::Row>, { /// Execute the query and return the generated results as a stream. #[inline] pub fn fetch<'e, 'c: 'e, E>(self, executor: E) -> BoxStream<'e, Result> where 'q: 'e, E: 'e + Executor<'c, Database = DB>, DB: 'e, A: 'e, O: 'e, { self.inner.fetch(executor).map_ok(|it| it.0).boxed() } /// Execute multiple queries and return the generated results as a stream /// from each query, in a stream. #[inline] #[deprecated = "Only the SQLite driver supports multiple statements in one prepared statement and that behavior is deprecated. Use `sqlx::raw_sql()` instead. See https://github.com/launchbadge/sqlx/issues/3108 for discussion."] pub fn fetch_many<'e, 'c: 'e, E>( self, executor: E, ) -> BoxStream<'e, Result, Error>> where 'q: 'e, E: 'e + Executor<'c, Database = DB>, DB: 'e, A: 'e, O: 'e, { #[allow(deprecated)] self.inner .fetch_many(executor) .map_ok(|v| v.map_right(|it| it.0)) .boxed() } /// Execute the query and return all the resulting rows collected into a [`Vec`]. /// /// ### Note: beware result set size. /// This will attempt to collect the full result set of the query into memory. /// /// To avoid exhausting available memory, ensure the result set has a known upper bound, /// e.g. using `LIMIT`. #[inline] pub async fn fetch_all<'e, 'c: 'e, E>(self, executor: E) -> Result, Error> where 'q: 'e, E: 'e + Executor<'c, Database = DB>, DB: 'e, (O,): 'e, A: 'e, { self.inner .fetch(executor) .map_ok(|it| it.0) .try_collect() .await } /// Execute the query, returning the first row or [`Error::RowNotFound`] otherwise. /// /// ### Note: for best performance, ensure the query returns at most one row. /// Depending on the driver implementation, if your query can return more than one row, /// it may lead to wasted CPU time and bandwidth on the database server. /// /// Even when the driver implementation takes this into account, ensuring the query returns at most one row /// can result in a more optimal query plan. /// /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. /// /// Otherwise, you might want to add `LIMIT 1` to your query. #[inline] pub async fn fetch_one<'e, 'c: 'e, E>(self, executor: E) -> Result where 'q: 'e, E: 'e + Executor<'c, Database = DB>, DB: 'e, O: 'e, A: 'e, { self.inner.fetch_one(executor).map_ok(|it| it.0).await } /// Execute the query, returning the first row or `None` otherwise. /// /// ### Note: for best performance, ensure the query returns at most one row. /// Depending on the driver implementation, if your query can return more than one row, /// it may lead to wasted CPU time and bandwidth on the database server. /// /// Even when the driver implementation takes this into account, ensuring the query returns at most one row /// can result in a more optimal query plan. /// /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. /// /// Otherwise, you might want to add `LIMIT 1` to your query. #[inline] pub async fn fetch_optional<'e, 'c: 'e, E>(self, executor: E) -> Result, Error> where 'q: 'e, E: 'e + Executor<'c, Database = DB>, DB: 'e, O: 'e, A: 'e, { Ok(self.inner.fetch_optional(executor).await?.map(|it| it.0)) } } /// Execute a single SQL query as a prepared statement (transparently cached) and extract the first /// column of each row. /// /// Extracts the first column of each row. Additional columns are ignored. /// Any type that implements `Type + Decode` may be used. /// /// For details about prepared statements and allowed SQL syntax, see [`query()`][crate::query::query]. /// /// ### Example: Simple Lookup /// If you just want to look up a single value with little fanfare, this API is perfect for you: /// /// ```rust,no_run /// # async fn example_lookup() -> Result<(), Box> { /// # let mut conn: sqlx::PgConnection = unimplemented!(); /// use uuid::Uuid; /// /// // MySQL and MariaDB: use `?` /// let user_id: Option = sqlx::query_scalar("SELECT user_id FROM users WHERE username = $1") /// .bind("alice") /// // Use `&mut` where `conn` is a connection or a transaction, or use `&` for a `Pool`. /// .fetch_optional(&mut conn) /// .await?; /// /// let user_id = user_id.ok_or("unknown user")?; /// /// # Ok(()) /// # } /// ``` /// /// Note how we're using `.fetch_optional()` because the lookup may return no results, /// in which case we need to be able to handle an empty result set. /// Any rows after the first are ignored. /// /// ### Example: `COUNT` /// This API is the easiest way to invoke an aggregate query like `SELECT COUNT(*)`, because you /// can conveniently extract the result: /// /// ```rust,no_run /// # async fn example_count() -> sqlx::Result<()> { /// # let mut conn: sqlx::PgConnection = unimplemented!(); /// // Note that `usize` is not used here because unsigned integers are generally not supported, /// // and `usize` doesn't even make sense as a mapping because the database server may have /// // a completely different architecture. /// // /// // `i64` is generally a safe choice for `COUNT`. /// let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users WHERE accepted_tos IS TRUE") /// // Use `&mut` where `conn` is a connection or a transaction, or use `&` for a `Pool`. /// .fetch_one(&mut conn) /// .await?; /// /// // The above is functionally equivalent to the following: /// // Note the trailing comma, required for the compiler to recognize a 1-element tuple. /// let (count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM users WHERE accepted_tos IS TRUE") /// .fetch_one(&mut conn) /// .await?; /// # Ok(()) /// # } /// ``` /// /// ### Example: `EXISTS` /// To test if a row exists or not, use `SELECT EXISTS()`: /// /// ```rust,no_run /// # async fn example_exists() -> sqlx::Result<()> { /// # let mut conn: sqlx::PgConnection = unimplemented!(); /// // MySQL and MariaDB: use `?` /// let username_taken: bool = sqlx::query_scalar( /// "SELECT EXISTS(SELECT 1 FROM users WHERE username = $1)" /// ) /// .bind("alice") /// // Use `&mut` where `conn` is a connection or a transaction, or use `&` for a `Pool`. /// .fetch_one(&mut conn) /// .await?; /// # Ok(()) /// # } /// ``` /// /// ### Example: Other Aggregates /// Be aware that most other aggregate functions return `NULL` if the query yields an empty set: /// /// ```rust,no_run /// # async fn example_aggregate() -> sqlx::Result<()> { /// # let mut conn: sqlx::PgConnection = unimplemented!(); /// let max_upvotes: Option = sqlx::query_scalar("SELECT MAX(upvotes) FROM posts") /// // Use `&mut` where `conn` is a connection or a transaction, or use `&` for a `Pool`. /// .fetch_one(&mut conn) /// .await?; /// # Ok(()) /// # } /// ``` /// /// Note how we're using `Option` with `.fetch_one()`, because we're always expecting one row /// but the column value may be `NULL`. If no rows are returned, this will error. /// /// This is in contrast to using `.fetch_optional()` with `Option`, which implies that /// we're expecting _either_ a row with a `i64` (`BIGINT`), _or_ no rows at all. /// /// Either way, any rows after the first are ignored. /// /// ### Example: `Vec` of Scalars /// If you want to collect a single column from a query into a vector, /// try `.fetch_all()`: /// /// ```rust,no_run /// # async fn example_vec() -> sqlx::Result<()> { /// # let mut conn: sqlx::PgConnection = unimplemented!(); /// let top_users: Vec = sqlx::query_scalar( /// // Note the `LIMIT` to ensure that this doesn't return *all* users: /// "SELECT username /// FROM ( /// SELECT SUM(upvotes) total, user_id /// FROM posts /// GROUP BY user_id /// ) top_users /// INNER JOIN users USING (user_id) /// ORDER BY total DESC /// LIMIT 10" /// ) /// // Use `&mut` where `conn` is a connection or a transaction, or use `&` for a `Pool`. /// .fetch_all(&mut conn) /// .await?; /// /// // `top_users` could be empty, too. /// assert!(top_users.len() <= 10); /// # Ok(()) /// # } /// ``` #[inline] pub fn query_scalar<'q, DB, O>( sql: impl SqlSafeStr, ) -> QueryScalar<'q, DB, O, ::Arguments> where DB: Database, (O,): for<'r> FromRow<'r, DB::Row>, { QueryScalar { inner: query_as(sql), } } /// Execute a SQL query as a prepared statement (transparently cached), with the given arguments, /// and extract the first column of each row. /// /// See [`query_scalar()`] for details. /// /// For details about prepared statements and allowed SQL syntax, see [`query()`][crate::query::query]. #[inline] pub fn query_scalar_with<'q, DB, O, A>( sql: impl SqlSafeStr, arguments: A, ) -> QueryScalar<'q, DB, O, A> where DB: Database, A: IntoArguments, (O,): for<'r> FromRow<'r, DB::Row>, { query_scalar_with_result(sql, Ok(arguments)) } /// Same as [`query_scalar_with`] but takes arguments as Result #[inline] pub fn query_scalar_with_result<'q, DB, O, A>( sql: impl SqlSafeStr, arguments: Result, ) -> QueryScalar<'q, DB, O, A> where DB: Database, A: IntoArguments, (O,): for<'r> FromRow<'r, DB::Row>, { QueryScalar { inner: query_as_with_result(sql, arguments), } } // Make a SQL query from a statement, that is mapped to a concrete value. pub fn query_statement_scalar( statement: &DB::Statement, ) -> QueryScalar<'_, DB, O, ::Arguments> where DB: Database, (O,): for<'r> FromRow<'r, DB::Row>, { QueryScalar { inner: query_statement_as(statement), } } // Make a SQL query from a statement, with the given arguments, that is mapped to a concrete value. pub fn query_statement_scalar_with<'q, DB, O, A>( statement: &'q DB::Statement, arguments: A, ) -> QueryScalar<'q, DB, O, A> where DB: Database, A: IntoArguments, (O,): for<'r> FromRow<'r, DB::Row>, { QueryScalar { inner: query_statement_as_with(statement, arguments), } } ================================================ FILE: sqlx-core/src/raw_sql.rs ================================================ use either::Either; use futures_core::future::BoxFuture; use futures_core::stream::BoxStream; use crate::database::Database; use crate::error::BoxDynError; use crate::executor::{Execute, Executor}; use crate::sql_str::{SqlSafeStr, SqlStr}; use crate::Error; // AUTHOR'S NOTE: I was just going to call this API `sql()` and `Sql`, respectively, // but realized that would be extremely annoying to deal with as a SQLite user // because IDE smart completion would always recommend the `Sql` type first. // // It doesn't really need a super convenient name anyway as it's not meant to be used very often. /// One or more raw SQL statements, separated by semicolons (`;`). /// /// See [`raw_sql()`] for details. pub struct RawSql(SqlStr); /// Execute one or more statements as raw SQL, separated by semicolons (`;`). /// /// This interface can be used to execute both DML /// (Data Manipulation Language: `SELECT`, `INSERT`, `UPDATE`, `DELETE` and variants) /// as well as DDL (Data Definition Language: `CREATE TABLE`, `ALTER TABLE`, etc). /// /// This will not create or cache any prepared statements. /// /// ### Note: singular DML queries, prefer `query()` /// This API does not use prepared statements, so usage of it is missing out on their benefits. /// /// Prefer [`query()`][crate::query::query] instead if executing a single query. /// /// It's also possible to combine multiple DML queries into one for use with `query()`: /// /// ##### Common Table Expressions (CTEs: i.e The `WITH` Clause) /// Common Table Expressions effectively allow you to define aliases for queries /// that can be referenced like temporary tables: /// /// ```sql /// WITH inserted_foos AS ( /// -- Note that only Postgres allows data-modifying statements in CTEs /// INSERT INTO foo (bar_id) VALUES ($1) /// RETURNING foo_id, bar_id /// ) /// SELECT foo_id, bar_id, bar /// FROM inserted_foos /// INNER JOIN bar USING (bar_id) /// ``` /// /// It's important to note that data modifying statements (`INSERT`, `UPDATE`, `DELETE`) may /// behave differently than expected. In Postgres, all data-modifying subqueries in a `WITH` /// clause execute with the same view of the data; they *cannot* see each other's modifications. /// /// MySQL, MariaDB and SQLite appear to *only* allow `SELECT` statements in CTEs. /// /// See the appropriate entry in your database's manual for details: /// * [MySQL](https://dev.mysql.com/doc/refman/8.0/en/with.html) /// * [MariaDB](https://mariadb.com/kb/en/with/) /// * [Postgres](https://www.postgresql.org/docs/current/queries-with.html) /// * [SQLite](https://www.sqlite.org/lang_with.html) /// /// ##### `UNION`/`INTERSECT`/`EXCEPT` /// You can also use various set-theory operations on queries, /// including `UNION ALL` which simply concatenates their results. /// /// See the appropriate entry in your database's manual for details: /// * [MySQL](https://dev.mysql.com/doc/refman/8.0/en/set-operations.html) /// * [MariaDB](https://mariadb.com/kb/en/joins-subqueries/) /// * [Postgres](https://www.postgresql.org/docs/current/queries-union.html) /// * [SQLite](https://www.sqlite.org/lang_select.html#compound_select_statements) /// /// ### Note: query parameters are not supported. /// Query parameters require the use of prepared statements which this API does support. /// /// If you require dynamic input data in your SQL, you can use `format!()` but **be very careful /// doing this with user input**. SQLx does **not** provide escaping or sanitization for inserting /// dynamic input into queries this way. /// /// See [`query()`][crate::query::query] for details. /// /// ### Note: multiple statements and autocommit. /// By default, when you use this API to execute a SQL string containing multiple statements /// separated by semicolons (`;`), the database server will treat those statements as all executing /// within the same transaction block, i.e. wrapped in `BEGIN` and `COMMIT`: /// /// ```rust,no_run /// # async fn example() -> sqlx::Result<()> { /// let mut conn: sqlx::PgConnection = todo!("e.g. PgConnection::connect()"); /// /// sqlx::raw_sql( /// // Imagine we're moving data from one table to another: /// // Implicit `BEGIN;` /// "UPDATE foo SET bar = foobar.bar FROM foobar WHERE foobar.foo_id = foo.id;\ /// DELETE FROM foobar;" /// // Implicit `COMMIT;` /// ) /// .execute(&mut conn) /// .await?; /// /// # Ok(()) /// # } /// ``` /// /// If one statement triggers an error, the whole script aborts and rolls back. /// You can include explicit `BEGIN` and `COMMIT` statements in the SQL string /// to designate units that can be committed or rolled back piecemeal. /// /// This also allows for a rudimentary form of pipelining as the whole SQL string is sent in one go. /// /// ##### MySQL and MariaDB: DDL implicitly commits! /// MySQL and MariaDB do not support DDL in transactions. Instead, any active transaction is /// immediately and implicitly committed by the database server when executing a DDL statement. /// Beware of this behavior. /// /// See [MySQL manual, section 13.3.3: Statements That Cause an Implicit Commit](https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html) for details. /// See also: [MariaDB manual: SQL statements That Cause an Implicit Commit](https://mariadb.com/kb/en/sql-statements-that-cause-an-implicit-commit/). pub fn raw_sql(sql: impl SqlSafeStr) -> RawSql { RawSql(sql.into_sql_str()) } impl Execute<'_, DB> for RawSql { fn sql(self) -> SqlStr { self.0 } fn statement(&self) -> Option<&::Statement> { None } fn take_arguments(&mut self) -> Result::Arguments>, BoxDynError> { Ok(None) } fn persistent(&self) -> bool { false } } impl RawSql { /// Execute the SQL string and return the total number of rows affected. #[inline] pub async fn execute<'e, E, DB>(self, executor: E) -> crate::Result where DB: Database, E: Executor<'e, Database = DB>, { executor.execute(self).await } /// Execute the SQL string. Returns a stream which gives the number of rows affected for each statement in the string. #[inline] pub fn execute_many<'e, E, DB>( self, executor: E, ) -> BoxStream<'e, crate::Result> where DB: Database, E: Executor<'e, Database = DB>, { executor.execute_many(self) } /// Execute the SQL string and return the generated results as a stream. /// /// If the string contains multiple statements, their results will be concatenated together. #[inline] pub fn fetch<'e, E, DB>(self, executor: E) -> BoxStream<'e, Result> where DB: Database, E: Executor<'e, Database = DB>, { executor.fetch(self) } /// Execute the SQL string and return the generated results as a stream. /// /// For each query in the stream, any generated rows are returned first, /// then the `QueryResult` with the number of rows affected. #[inline] pub fn fetch_many<'e, E, DB>( self, executor: E, ) -> BoxStream<'e, Result, Error>> where DB: Database, E: Executor<'e, Database = DB>, { executor.fetch_many(self) } /// Execute the SQL string and return all the resulting rows collected into a [`Vec`]. /// /// ### Note: beware result set size. /// This will attempt to collect the full result set of the query into memory. /// /// To avoid exhausting available memory, ensure the result set has a known upper bound, /// e.g. using `LIMIT`. #[inline] pub fn fetch_all<'e, E, DB>(self, executor: E) -> BoxFuture<'e, crate::Result>> where DB: Database, E: Executor<'e, Database = DB>, { executor.fetch_all(self) } /// Execute the SQL string, returning the first row or [`Error::RowNotFound`] otherwise. /// /// ### Note: for best performance, ensure the query returns at most one row. /// Depending on the driver implementation, if your query can return more than one row, /// it may lead to wasted CPU time and bandwidth on the database server. /// /// Even when the driver implementation takes this into account, ensuring the query returns /// at most one row can result in a more optimal query plan. /// /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. /// /// Otherwise, you might want to add `LIMIT 1` to your query. #[inline] pub fn fetch_one<'e, E, DB>(self, executor: E) -> BoxFuture<'e, crate::Result> where DB: Database, E: Executor<'e, Database = DB>, { executor.fetch_one(self) } /// Execute the SQL string, returning the first row or [`None`] otherwise. /// /// ### Note: for best performance, ensure the query returns at most one row. /// Depending on the driver implementation, if your query can return more than one row, /// it may lead to wasted CPU time and bandwidth on the database server. /// /// Even when the driver implementation takes this into account, ensuring the query returns /// at most one row can result in a more optimal query plan. /// /// If your query has a `WHERE` clause filtering a unique column by a single value, you're good. /// /// Otherwise, you might want to add `LIMIT 1` to your query. #[inline] pub async fn fetch_optional<'e, E, DB>(self, executor: E) -> crate::Result> where DB: Database, E: Executor<'e, Database = DB>, { executor.fetch_optional(self).await } } ================================================ FILE: sqlx-core/src/row.rs ================================================ use crate::column::{Column, ColumnIndex}; use crate::database::Database; use crate::decode::Decode; use crate::error::{mismatched_types, Error}; use crate::type_checking::TypeChecking; use crate::type_info::TypeInfo; use crate::types::Type; use crate::value::ValueRef; /// Represents a single row from the database. /// /// [`FromRow`]: crate::row::FromRow /// [`Query::fetch`]: crate::query::Query::fetch pub trait Row: Unpin + Send + Sync + 'static { type Database: Database; /// Returns `true` if this row has no columns. #[inline] fn is_empty(&self) -> bool { self.len() == 0 } /// Returns the number of columns in this row. #[inline] fn len(&self) -> usize { self.columns().len() } /// Gets the column information at `index`. /// /// A string index can be used to access a column by name and a `usize` index /// can be used to access a column by position. /// /// # Panics /// /// Panics if `index` is out of bounds. /// See [`try_column`](Self::try_column) for a non-panicking version. fn column(&self, index: I) -> &::Column where I: ColumnIndex, { self.try_column(index).unwrap() } /// Gets the column information at `index` or a `ColumnIndexOutOfBounds` error if out of bounds. fn try_column(&self, index: I) -> Result<&::Column, Error> where I: ColumnIndex, { Ok(&self.columns()[index.index(self)?]) } /// Gets all columns in this statement. fn columns(&self) -> &[::Column]; /// Index into the database row and decode a single value. /// /// A string index can be used to access a column by name and a `usize` index /// can be used to access a column by position. /// /// # Panics /// /// Panics if the column does not exist or its value cannot be decoded into the requested type. /// See [`try_get`](Self::try_get) for a non-panicking version. /// #[inline] #[track_caller] fn get<'r, T, I>(&'r self, index: I) -> T where I: ColumnIndex, T: Decode<'r, Self::Database> + Type, { self.try_get::(index).unwrap() } /// Index into the database row and decode a single value. /// /// Unlike [`get`](Self::get), this method does not check that the type /// being returned from the database is compatible with the Rust type and blindly tries /// to decode the value. /// /// # Panics /// /// Panics if the column does not exist or its value cannot be decoded into the requested type. /// See [`try_get_unchecked`](Self::try_get_unchecked) for a non-panicking version. /// #[inline] fn get_unchecked<'r, T, I>(&'r self, index: I) -> T where I: ColumnIndex, T: Decode<'r, Self::Database>, { self.try_get_unchecked::(index).unwrap() } /// Index into the database row and decode a single value. /// /// A string index can be used to access a column by name and a `usize` index /// can be used to access a column by position. /// /// # Errors /// /// * [`ColumnNotFound`] if the column by the given name was not found. /// * [`ColumnIndexOutOfBounds`] if the `usize` index was greater than the number of columns in the row. /// * [`ColumnDecode`] if the value could not be decoded into the requested type. /// /// [`ColumnDecode`]: Error::ColumnDecode /// [`ColumnNotFound`]: Error::ColumnNotFound /// [`ColumnIndexOutOfBounds`]: Error::ColumnIndexOutOfBounds /// fn try_get<'r, T, I>(&'r self, index: I) -> Result where I: ColumnIndex, T: Decode<'r, Self::Database> + Type, { let value = self.try_get_raw(&index)?; if !value.is_null() { let ty = value.type_info(); if !ty.is_null() && !T::compatible(&ty) { return Err(Error::ColumnDecode { index: format!("{index:?}"), source: mismatched_types::(&ty), }); } } T::decode(value).map_err(|source| Error::ColumnDecode { index: format!("{index:?}"), source, }) } /// Index into the database row and decode a single value. /// /// Unlike [`try_get`](Self::try_get), this method does not check that the type /// being returned from the database is compatible with the Rust type and blindly tries /// to decode the value. /// /// # Errors /// /// * [`ColumnNotFound`] if the column by the given name was not found. /// * [`ColumnIndexOutOfBounds`] if the `usize` index was greater than the number of columns in the row. /// * [`ColumnDecode`] if the value could not be decoded into the requested type. /// /// [`ColumnDecode`]: Error::ColumnDecode /// [`ColumnNotFound`]: Error::ColumnNotFound /// [`ColumnIndexOutOfBounds`]: Error::ColumnIndexOutOfBounds /// #[inline] fn try_get_unchecked<'r, T, I>(&'r self, index: I) -> Result where I: ColumnIndex, T: Decode<'r, Self::Database>, { let value = self.try_get_raw(&index)?; T::decode(value).map_err(|source| Error::ColumnDecode { index: format!("{index:?}"), source, }) } /// Index into the database row and decode a single value. /// /// # Errors /// /// * [`ColumnNotFound`] if the column by the given name was not found. /// * [`ColumnIndexOutOfBounds`] if the `usize` index was greater than the number of columns in the row. /// /// [`ColumnNotFound`]: Error::ColumnNotFound /// [`ColumnIndexOutOfBounds`]: Error::ColumnIndexOutOfBounds /// fn try_get_raw(&self, index: I) -> Result<::ValueRef<'_>, Error> where I: ColumnIndex; } pub fn debug_row(row: &R, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result where R: Row, usize: ColumnIndex, ::Database: TypeChecking, { write!(f, "{} ", std::any::type_name::())?; let mut debug_map = f.debug_map(); for column in row.columns().iter() { match row.try_get_raw(column.ordinal()) { Ok(value) => { debug_map.entry( &column.name(), &::Database::fmt_value_debug( &<::Database as Database>::ValueRef::to_owned(&value), ), ); } Err(error) => { debug_map.entry(&column.name(), &format!("decode error: {error:?}")); } } } debug_map.finish() } ================================================ FILE: sqlx-core/src/rt/mod.rs ================================================ use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; use std::time::Duration; use cfg_if::cfg_if; #[cfg(feature = "_rt-async-io")] pub mod rt_async_io; #[cfg(feature = "_rt-tokio")] pub mod rt_tokio; #[derive(Debug, thiserror::Error)] #[error("operation timed out")] pub struct TimeoutError; pub enum JoinHandle { #[cfg(feature = "_rt-async-std")] AsyncStd(async_std::task::JoinHandle), #[cfg(feature = "_rt-tokio")] Tokio(tokio::task::JoinHandle), // Implementation shared by `smol` and `async-global-executor` #[cfg(feature = "_rt-async-task")] AsyncTask(Option>), // `PhantomData` requires `T: Unpin` _Phantom(PhantomData T>), } pub async fn timeout(duration: Duration, f: F) -> Result { #[cfg(debug_assertions)] let f = Box::pin(f); #[cfg(feature = "_rt-tokio")] if rt_tokio::available() { return tokio::time::timeout(duration, f) .await .map_err(|_| TimeoutError); } cfg_if! { if #[cfg(feature = "_rt-async-io")] { rt_async_io::timeout(duration, f).await } else { missing_rt((duration, f)) } } } pub async fn sleep(duration: Duration) { #[cfg(feature = "_rt-tokio")] if rt_tokio::available() { return tokio::time::sleep(duration).await; } cfg_if! { if #[cfg(feature = "_rt-async-io")] { rt_async_io::sleep(duration).await } else { missing_rt(duration) } } } #[track_caller] pub fn spawn(fut: F) -> JoinHandle where F: Future + Send + 'static, F::Output: Send + 'static, { #[cfg(feature = "_rt-tokio")] if let Ok(handle) = tokio::runtime::Handle::try_current() { return JoinHandle::Tokio(handle.spawn(fut)); } cfg_if! { if #[cfg(feature = "_rt-async-global-executor")] { JoinHandle::AsyncTask(Some(async_global_executor::spawn(fut))) } else if #[cfg(feature = "_rt-smol")] { JoinHandle::AsyncTask(Some(smol::spawn(fut))) } else if #[cfg(feature = "_rt-async-std")] { JoinHandle::AsyncStd(async_std::task::spawn(fut)) } else { missing_rt(fut) } } } #[track_caller] pub fn spawn_blocking(f: F) -> JoinHandle where F: FnOnce() -> R + Send + 'static, R: Send + 'static, { #[cfg(feature = "_rt-tokio")] if let Ok(handle) = tokio::runtime::Handle::try_current() { return JoinHandle::Tokio(handle.spawn_blocking(f)); } cfg_if! { if #[cfg(feature = "_rt-async-global-executor")] { JoinHandle::AsyncTask(Some(async_global_executor::spawn_blocking(f))) } else if #[cfg(feature = "_rt-smol")] { JoinHandle::AsyncTask(Some(smol::unblock(f))) } else if #[cfg(feature = "_rt-async-std")] { JoinHandle::AsyncStd(async_std::task::spawn_blocking(f)) } else { missing_rt(f) } } } pub async fn yield_now() { #[cfg(feature = "_rt-tokio")] if rt_tokio::available() { return tokio::task::yield_now().await; } // `smol`, `async-global-executor`, and `async-std` all have the same implementation for this. // // By immediately signaling the waker and then returning `Pending`, // this essentially just moves the task to the back of the runnable queue. // // There isn't any special integration with the runtime, so we can save code by rolling our own. // // (Tokio's implementation is nearly identical too, // but has additional integration with `tracing` which may be useful for debugging.) let mut yielded = false; std::future::poll_fn(|cx| { if !yielded { yielded = true; cx.waker().wake_by_ref(); Poll::Pending } else { Poll::Ready(()) } }) .await } #[track_caller] pub fn test_block_on(f: F) -> F::Output { cfg_if! { if #[cfg(feature = "_rt-async-io")] { async_io::block_on(f) } else if #[cfg(feature = "_rt-tokio")] { tokio::runtime::Builder::new_current_thread() .enable_all() .build() .expect("failed to start Tokio runtime") .block_on(f) } else { missing_rt(f) } } } #[track_caller] pub const fn missing_rt(_unused: T) -> ! { if cfg!(feature = "_rt-tokio") { panic!("this functionality requires a Tokio context") } panic!("one of the `runtime` features of SQLx must be enabled") } impl Future for JoinHandle { type Output = T; #[track_caller] fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match &mut *self { #[cfg(feature = "_rt-async-std")] Self::AsyncStd(handle) => Pin::new(handle).poll(cx), #[cfg(feature = "_rt-async-task")] Self::AsyncTask(task) => Pin::new(task) .as_pin_mut() .expect("BUG: task taken") .poll(cx), #[cfg(feature = "_rt-tokio")] Self::Tokio(handle) => Pin::new(handle) .poll(cx) .map(|res| res.expect("spawned task panicked")), Self::_Phantom(_) => { let _ = cx; unreachable!("runtime should have been checked on spawn") } } } } impl Drop for JoinHandle { fn drop(&mut self) { match self { // `async_task` cancels on-drop by default. // We need to explicitly detach to match Tokio and `async-std`. #[cfg(feature = "_rt-async-task")] Self::AsyncTask(task) => { if let Some(task) = task.take() { task.detach(); } } _ => (), } } } ================================================ FILE: sqlx-core/src/rt/rt_async_io/mod.rs ================================================ mod socket; mod timeout; pub use timeout::*; ================================================ FILE: sqlx-core/src/rt/rt_async_io/socket.rs ================================================ use crate::net::Socket; use std::io; use std::io::{Read, Write}; use std::net::{Shutdown, TcpStream}; use std::task::{Context, Poll}; use async_io::Async; use crate::io::ReadBuf; impl Socket for Async { fn try_read(&mut self, buf: &mut dyn ReadBuf) -> io::Result { self.get_ref().read(buf.init_mut()) } fn try_write(&mut self, buf: &[u8]) -> io::Result { self.get_ref().write(buf) } fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.poll_readable(cx) } fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.poll_writable(cx) } fn poll_shutdown(&mut self, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(self.get_ref().shutdown(Shutdown::Both)) } } #[cfg(unix)] impl Socket for Async { fn try_read(&mut self, buf: &mut dyn ReadBuf) -> io::Result { self.get_ref().read(buf.init_mut()) } fn try_write(&mut self, buf: &[u8]) -> io::Result { self.get_ref().write(buf) } fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.poll_readable(cx) } fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.poll_writable(cx) } fn poll_shutdown(&mut self, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(self.get_ref().shutdown(Shutdown::Both)) } } ================================================ FILE: sqlx-core/src/rt/rt_async_io/timeout.rs ================================================ use std::{future::Future, pin::pin, time::Duration}; use futures_util::future::{select, Either}; use crate::rt::TimeoutError; pub async fn sleep(duration: Duration) { timeout_future(duration).await; } pub async fn timeout(duration: Duration, future: F) -> Result { match select(pin!(future), timeout_future(duration)).await { Either::Left((result, _)) => Ok(result), Either::Right(_) => Err(TimeoutError), } } fn timeout_future(duration: Duration) -> impl Future { async_io::Timer::after(duration) } ================================================ FILE: sqlx-core/src/rt/rt_tokio/mod.rs ================================================ mod socket; pub fn available() -> bool { tokio::runtime::Handle::try_current().is_ok() } ================================================ FILE: sqlx-core/src/rt/rt_tokio/socket.rs ================================================ use std::io; use std::pin::Pin; use std::task::{Context, Poll}; use tokio::io::AsyncWrite; use tokio::net::TcpStream; use crate::io::ReadBuf; use crate::net::Socket; impl Socket for TcpStream { fn try_read(&mut self, mut buf: &mut dyn ReadBuf) -> io::Result { // Requires `&mut impl BufMut` self.try_read_buf(&mut buf) } fn try_write(&mut self, buf: &[u8]) -> io::Result { (*self).try_write(buf) } fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { (*self).poll_read_ready(cx) } fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { (*self).poll_write_ready(cx) } fn poll_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> { Pin::new(self).poll_shutdown(cx) } } #[cfg(unix)] impl Socket for tokio::net::UnixStream { fn try_read(&mut self, mut buf: &mut dyn ReadBuf) -> io::Result { self.try_read_buf(&mut buf) } fn try_write(&mut self, buf: &[u8]) -> io::Result { (*self).try_write(buf) } fn poll_read_ready(&mut self, cx: &mut Context<'_>) -> Poll> { (*self).poll_read_ready(cx) } fn poll_write_ready(&mut self, cx: &mut Context<'_>) -> Poll> { (*self).poll_write_ready(cx) } fn poll_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> { Pin::new(self).poll_shutdown(cx) } } ================================================ FILE: sqlx-core/src/sql_str.rs ================================================ use std::borrow::{Borrow, Cow}; use std::hash::{Hash, Hasher}; use std::sync::Arc; /// A SQL string that is safe to execute on a database connection. /// /// A "safe" SQL string is one that is unlikely to contain a [SQL injection vulnerability][injection]. /// /// In practice, this means a string type that is unlikely to contain dynamic data or user input. /// /// `&'static str` is the only string type that satisfies the requirements of this trait /// (ignoring [`String::leak()`] which has niche use-cases) and so is the only string type that /// natively implements this trait by default. /// /// For other string types, use [`AssertSqlSafe`] to assert this property. /// This is the only intended way to pass an owned `String` to [`query()`] and its related functions /// as well as [`raw_sql()`]. /// /// The maintainers of SQLx take no responsibility for any data leaks or loss resulting from misuse /// of this API. /// /// ### Motivation /// This is designed to act as a speed bump against naively using `format!()` to add dynamic data /// or user input to a query, which is a classic vector for SQL injection as SQLx does not /// provide any sort of escaping or sanitization (which would have to be specially implemented /// for each database flavor/locale). /// /// The recommended way to incorporate dynamic data or user input in a query is to use /// bind parameters, which requires the query to execute as a prepared statement. /// See [`query()`] for details. /// /// This trait and [`AssertSqlSafe`] are intentionally analogous to /// [`std::panic::UnwindSafe`] and [`std::panic::AssertUnwindSafe`], respectively. /// /// [injection]: https://en.wikipedia.org/wiki/SQL_injection /// [`query()`]: crate::query::query /// [`raw_sql()`]: crate::raw_sql::raw_sql #[diagnostic::on_unimplemented( label = "dynamic SQL string", message = "dynamic SQL strings should be audited for possible injections", note = "prefer literal SQL strings with bind parameters or `QueryBuilder` to add dynamic data to a query. To bypass this error, manually audit for potential injection vulnerabilities and wrap with `AssertSqlSafe()`. For details, see the docs for `SqlSafeStr`.\n", note = "this trait is only implemented for `&'static str`, not all `&str` like the compiler error may suggest" )] pub trait SqlSafeStr { /// Convert `self` to a [`SqlStr`]. fn into_sql_str(self) -> SqlStr; } impl SqlSafeStr for &'static str { #[inline] fn into_sql_str(self) -> SqlStr { SqlStr(Repr::Static(self)) } } /// Assert that a query string is safe to execute on a database connection. /// /// Using this API means that **you** have made sure that the string contents do not contain a /// [SQL injection vulnerability][injection]. It means that, if the string was constructed /// dynamically, and/or from user input, you have taken care to sanitize the input yourself. /// SQLx does not provide any sort of sanitization; the design of SQLx prefers the use /// of prepared statements for dynamic input. /// /// The maintainers of SQLx take no responsibility for any data leaks or loss resulting from misuse /// of this API. **Use at your own risk.** /// /// Note that `&'static str` implements [`SqlSafeStr`] directly and so does not need to be wrapped /// with this type. /// /// [injection]: https://en.wikipedia.org/wiki/SQL_injection pub struct AssertSqlSafe(pub T); /// Note: copies the string. /// /// It is recommended to pass one of the supported owned string types instead. impl SqlSafeStr for AssertSqlSafe<&str> { #[inline] fn into_sql_str(self) -> SqlStr { SqlStr(Repr::Arced(self.0.into())) } } impl SqlSafeStr for AssertSqlSafe { #[inline] fn into_sql_str(self) -> SqlStr { SqlStr(Repr::Owned(self.0)) } } impl SqlSafeStr for AssertSqlSafe> { #[inline] fn into_sql_str(self) -> SqlStr { SqlStr(Repr::Boxed(self.0)) } } // Note: this is not implemented for `Rc` because it would make `QueryString: !Send`. impl SqlSafeStr for AssertSqlSafe> { #[inline] fn into_sql_str(self) -> SqlStr { SqlStr(Repr::Arced(self.0)) } } impl SqlSafeStr for AssertSqlSafe> { #[inline] fn into_sql_str(self) -> SqlStr { SqlStr(Repr::ArcString(self.0)) } } impl SqlSafeStr for AssertSqlSafe> { fn into_sql_str(self) -> SqlStr { match self.0 { Cow::Borrowed(str) => str.into_sql_str(), Cow::Owned(str) => AssertSqlSafe(str).into_sql_str(), } } } /// A SQL string that is ready to execute on a database connection. /// /// This is essentially `Cow<'static, str>` but which can be constructed from additional types /// without copying. /// /// See [`SqlSafeStr`] for details. #[derive(Debug)] pub struct SqlStr(Repr); #[derive(Debug)] enum Repr { /// We need a variant to memoize when we already have a static string, so we don't copy it. Static(&'static str), /// Thanks to the new niche in `String`, this doesn't increase the size beyond 3 words. /// We essentially get all these variants for free. Owned(String), Boxed(Box), Arced(Arc), /// Allows for dynamic shared ownership with `query_builder`. ArcString(Arc), } impl Clone for SqlStr { fn clone(&self) -> Self { Self(match &self.0 { Repr::Static(s) => Repr::Static(s), Repr::Arced(s) => Repr::Arced(s.clone()), // If `.clone()` gets called once, assume it might get called again. _ => Repr::Arced(self.as_str().into()), }) } } impl SqlSafeStr for SqlStr { #[inline] fn into_sql_str(self) -> SqlStr { self } } impl SqlStr { /// Borrow the inner query string. #[inline] pub fn as_str(&self) -> &str { match &self.0 { Repr::Static(s) => s, Repr::Owned(s) => s, Repr::Boxed(s) => s, Repr::Arced(s) => s, Repr::ArcString(s) => s, } } pub const fn from_static(sql: &'static str) -> Self { SqlStr(Repr::Static(sql)) } } impl AsRef for SqlStr { #[inline] fn as_ref(&self) -> &str { self.as_str() } } impl Borrow for SqlStr { #[inline] fn borrow(&self) -> &str { self.as_str() } } impl PartialEq for SqlStr where T: AsRef, { fn eq(&self, other: &T) -> bool { self.as_str() == other.as_ref() } } impl Eq for SqlStr {} impl Hash for SqlStr { fn hash(&self, state: &mut H) { self.as_str().hash(state) } } ================================================ FILE: sqlx-core/src/statement.rs ================================================ use crate::arguments::IntoArguments; use crate::column::ColumnIndex; use crate::database::Database; use crate::error::Error; use crate::from_row::FromRow; use crate::query::Query; use crate::query_as::QueryAs; use crate::query_scalar::QueryScalar; use crate::sql_str::SqlStr; use either::Either; /// An explicitly prepared statement. /// /// Statements are prepared and cached by default, per connection. This type allows you to /// look at that cache in-between the statement being prepared and it being executed. This contains /// the expected columns to be returned and the expected parameter types (if available). /// /// Statements can be re-used with any connection and on first-use it will be re-prepared and /// cached within the connection. pub trait Statement: Send + Sync + Clone { type Database: Database; /// Get the original SQL text used to create this statement. fn into_sql(self) -> SqlStr; /// Get the original SQL text used to create this statement. fn sql(&self) -> &SqlStr; /// Get the expected parameters for this statement. /// /// The information returned depends on what is available from the driver. SQLite can /// only tell us the number of parameters. PostgreSQL can give us full type information. fn parameters(&self) -> Option::TypeInfo], usize>>; /// Get the columns expected to be returned by executing this statement. fn columns(&self) -> &[::Column]; /// Gets the column information at `index`. /// /// A string index can be used to access a column by name and a `usize` index /// can be used to access a column by position. /// /// # Panics /// /// Panics if `index` is out of bounds. /// See [`try_column`](Self::try_column) for a non-panicking version. fn column(&self, index: I) -> &::Column where I: ColumnIndex, { self.try_column(index).unwrap() } /// Gets the column information at `index` or a `ColumnIndexOutOfBounds` error if out of bounds. fn try_column(&self, index: I) -> Result<&::Column, Error> where I: ColumnIndex, { Ok(&self.columns()[index.index(self)?]) } fn query(&self) -> Query<'_, Self::Database, ::Arguments>; fn query_with(&self, arguments: A) -> Query<'_, Self::Database, A> where A: IntoArguments; fn query_as( &self, ) -> QueryAs<'_, Self::Database, O, ::Arguments> where O: for<'r> FromRow<'r, ::Row>; fn query_as_with<'s, O, A>(&'s self, arguments: A) -> QueryAs<'s, Self::Database, O, A> where O: for<'r> FromRow<'r, ::Row>, A: IntoArguments; fn query_scalar( &self, ) -> QueryScalar<'_, Self::Database, O, ::Arguments> where (O,): for<'r> FromRow<'r, ::Row>; fn query_scalar_with<'s, O, A>(&'s self, arguments: A) -> QueryScalar<'s, Self::Database, O, A> where (O,): for<'r> FromRow<'r, ::Row>, A: IntoArguments; } #[macro_export] macro_rules! impl_statement_query { ($A:ty) => { #[inline] fn query(&self) -> $crate::query::Query<'_, Self::Database, $A> { $crate::query::query_statement(self) } #[inline] fn query_with(&self, arguments: A) -> $crate::query::Query<'_, Self::Database, A> where A: $crate::arguments::IntoArguments, { $crate::query::query_statement_with(self, arguments) } #[inline] fn query_as( &self, ) -> $crate::query_as::QueryAs< '_, Self::Database, O, ::Arguments, > where O: for<'r> $crate::from_row::FromRow< 'r, ::Row, >, { $crate::query_as::query_statement_as(self) } #[inline] fn query_as_with<'s, O, A>( &'s self, arguments: A, ) -> $crate::query_as::QueryAs<'s, Self::Database, O, A> where O: for<'r> $crate::from_row::FromRow< 'r, ::Row, >, A: $crate::arguments::IntoArguments, { $crate::query_as::query_statement_as_with(self, arguments) } #[inline] fn query_scalar( &self, ) -> $crate::query_scalar::QueryScalar< '_, Self::Database, O, ::Arguments, > where (O,): for<'r> $crate::from_row::FromRow< 'r, ::Row, >, { $crate::query_scalar::query_statement_scalar(self) } #[inline] fn query_scalar_with<'s, O, A>( &'s self, arguments: A, ) -> $crate::query_scalar::QueryScalar<'s, Self::Database, O, A> where (O,): for<'r> $crate::from_row::FromRow< 'r, ::Row, >, A: $crate::arguments::IntoArguments, { $crate::query_scalar::query_statement_scalar_with(self, arguments) } }; } ================================================ FILE: sqlx-core/src/sync.rs ================================================ use cfg_if::cfg_if; // For types with identical signatures that don't require runtime support, // we can just arbitrarily pick one to use based on what's enabled. // // We'll generally lean towards Tokio's types as those are more featureful // (including `tokio-console` support) and more widely deployed. pub struct AsyncSemaphore { // We use the semaphore from futures-intrusive as the one from async-lock // is missing the ability to add arbitrary permits, and is not guaranteed to be fair: // * https://github.com/smol-rs/async-lock/issues/22 // * https://github.com/smol-rs/async-lock/issues/23 // // We're on the look-out for a replacement, however, as futures-intrusive is not maintained // and there are some soundness concerns (although it turns out any intrusive future is unsound // in MIRI due to the necessitated mutable aliasing): // https://github.com/launchbadge/sqlx/issues/1668 #[cfg(all( any( feature = "_rt-async-global-executor", feature = "_rt-async-std", feature = "_rt-smol" ), not(feature = "_rt-tokio") ))] inner: futures_intrusive::sync::Semaphore, #[cfg(feature = "_rt-tokio")] inner: tokio::sync::Semaphore, } impl AsyncSemaphore { #[track_caller] pub fn new(fair: bool, permits: usize) -> Self { if cfg!(not(any( feature = "_rt-async-global-executor", feature = "_rt-async-std", feature = "_rt-smol", feature = "_rt-tokio" ))) { crate::rt::missing_rt((fair, permits)); } AsyncSemaphore { #[cfg(all( any( feature = "_rt-async-global-executor", feature = "_rt-async-std", feature = "_rt-smol" ), not(feature = "_rt-tokio") ))] inner: futures_intrusive::sync::Semaphore::new(fair, permits), #[cfg(feature = "_rt-tokio")] inner: { debug_assert!(fair, "Tokio only has fair permits"); tokio::sync::Semaphore::new(permits) }, } } pub fn permits(&self) -> usize { cfg_if! { if #[cfg(all( any( feature = "_rt-async-global-executor", feature = "_rt-async-std", feature = "_rt-smol" ), not(feature = "_rt-tokio") ))] { self.inner.permits() } else if #[cfg(feature = "_rt-tokio")] { self.inner.available_permits() } else { crate::rt::missing_rt(()) } } } pub async fn acquire(&self, permits: u32) -> AsyncSemaphoreReleaser<'_> { cfg_if! { if #[cfg(all( any( feature = "_rt-async-global-executor", feature = "_rt-async-std", feature = "_rt-smol" ), not(feature = "_rt-tokio") ))] { AsyncSemaphoreReleaser { inner: self.inner.acquire(permits as usize).await, } } else if #[cfg(feature = "_rt-tokio")] { AsyncSemaphoreReleaser { inner: self .inner // Weird quirk: `tokio::sync::Semaphore` mostly uses `usize` for permit counts, // but `u32` for this and `try_acquire_many()`. .acquire_many(permits) .await .expect("BUG: we do not expose the `.close()` method"), } } else { crate::rt::missing_rt(permits) } } } pub fn try_acquire(&self, permits: u32) -> Option> { cfg_if! { if #[cfg(all( any( feature = "_rt-async-global-executor", feature = "_rt-async-std", feature = "_rt-smol" ), not(feature = "_rt-tokio") ))] { Some(AsyncSemaphoreReleaser { inner: self.inner.try_acquire(permits as usize)?, }) } else if #[cfg(feature = "_rt-tokio")] { Some(AsyncSemaphoreReleaser { inner: self.inner.try_acquire_many(permits).ok()?, }) } else { crate::rt::missing_rt(permits) } } } pub fn release(&self, permits: usize) { cfg_if! { if #[cfg(all( any( feature = "_rt-async-global-executor", feature = "_rt-async-std", feature = "_rt-smol" ), not(feature = "_rt-tokio") ))] { self.inner.release(permits); } else if #[cfg(feature = "_rt-tokio")] { self.inner.add_permits(permits); } else { crate::rt::missing_rt(permits); } } } } pub struct AsyncSemaphoreReleaser<'a> { // We use the semaphore from futures-intrusive as the one from async-std // is missing the ability to add arbitrary permits, and is not guaranteed to be fair: // * https://github.com/smol-rs/async-lock/issues/22 // * https://github.com/smol-rs/async-lock/issues/23 // // We're on the look-out for a replacement, however, as futures-intrusive is not maintained // and there are some soundness concerns (although it turns out any intrusive future is unsound // in MIRI due to the necessitated mutable aliasing): // https://github.com/launchbadge/sqlx/issues/1668 #[cfg(all( any( feature = "_rt-async-global-executor", feature = "_rt-async-std", feature = "_rt-smol" ), not(feature = "_rt-tokio") ))] inner: futures_intrusive::sync::SemaphoreReleaser<'a>, #[cfg(feature = "_rt-tokio")] inner: tokio::sync::SemaphorePermit<'a>, #[cfg(not(any( feature = "_rt-async-global-executor", feature = "_rt-async-std", feature = "_rt-smol", feature = "_rt-tokio" )))] _phantom: std::marker::PhantomData<&'a ()>, } impl AsyncSemaphoreReleaser<'_> { pub fn disarm(self) { cfg_if! { if #[cfg(all( any( feature = "_rt-async-global-executor", feature = "_rt-async-std", feature = "_rt-smol" ), not(feature = "_rt-tokio") ))] { let mut this = self; this.inner.disarm(); } else if #[cfg(feature = "_rt-tokio")] { self.inner.forget(); } else { crate::rt::missing_rt(()); } } } } ================================================ FILE: sqlx-core/src/testing/fixtures.rs ================================================ //! TODO: automatic test fixture capture use crate::database::Database; use crate::query_builder::QueryBuilder; use indexmap::set::IndexSet; use std::cmp; use std::collections::{BTreeMap, HashMap}; use std::marker::PhantomData; use std::sync::Arc; pub type Result = std::result::Result; /// A snapshot of the current state of the database. /// /// Can be used to generate an `INSERT` fixture for populating an empty database, /// or in the future it may be possible to generate a fixture from the difference between /// two snapshots. pub struct FixtureSnapshot { tables: BTreeMap, db: PhantomData, } #[derive(Debug, thiserror::Error)] #[error("could not create fixture: {0}")] pub struct FixtureError(String); pub struct Fixture { ops: Vec, db: PhantomData, } enum FixtureOp { Insert { table: TableName, columns: Vec, rows: Vec>, }, // TODO: handle updates and deletes by diffing two snapshots } type TableName = Arc; type ColumnName = Arc; type Value = String; struct Table { name: TableName, columns: IndexSet, rows: Vec>, foreign_keys: HashMap, } macro_rules! fixture_assert ( ($cond:expr, $msg:literal $($arg:tt)*) => { if !($cond) { return Err(FixtureError(format!($msg $($arg)*))) } } ); impl FixtureSnapshot { /// Generate a fixture to reproduce this snapshot from an empty database using `INSERT`s. /// /// Note that this doesn't take into account any triggers that might modify the data before /// it's stored. /// /// The `INSERT` statements are ordered on a best-effort basis to satisfy any foreign key /// constraints (data from tables with no foreign keys are inserted first, then the tables /// that reference those tables, and so on). /// /// If a cycle in foreign-key constraints is detected, this returns with an error. pub fn additive_fixture(&self) -> Result> { let visit_order = self.calculate_visit_order()?; let mut ops = Vec::new(); for table_name in visit_order { let table = self.tables.get(&table_name).unwrap(); ops.push(FixtureOp::Insert { table: table_name, columns: table.columns.iter().cloned().collect(), rows: table.rows.clone(), }); } Ok(Fixture { ops, db: self.db }) } /// Determine an order for outputting `INSERTS` for each table by calculating the max /// length of all its foreign key chains. /// /// This should hopefully ensure that there are no foreign-key errors. fn calculate_visit_order(&self) -> Result> { let mut table_depths = HashMap::with_capacity(self.tables.len()); let mut visited_set = IndexSet::with_capacity(self.tables.len()); for table in self.tables.values() { foreign_key_depth(&self.tables, table, &mut table_depths, &mut visited_set)?; visited_set.clear(); } let mut table_names: Vec = table_depths.keys().cloned().collect(); table_names.sort_by_key(|name| table_depths.get(name).unwrap()); Ok(table_names) } } /// Implements `ToString` but not `Display` because it uses [`QueryBuilder`] internally, /// which appends to an internal string. #[allow(clippy::to_string_trait_impl)] impl ToString for Fixture where for<'a> ::Arguments: Default, { fn to_string(&self) -> String { let mut query = QueryBuilder::::new(""); for op in &self.ops { match op { FixtureOp::Insert { table, columns, rows, } => { // Sanity check, empty tables shouldn't appear in snapshots anyway. if columns.is_empty() || rows.is_empty() { continue; } query.push(format_args!("INSERT INTO {table} (")); let mut separated = query.separated(", "); for column in columns { separated.push(column); } query.push(")\n"); query.push_values(rows, |mut separated, row| { for value in row { separated.push(value); } }); query.push(";\n"); } } } query.into_string() } } fn foreign_key_depth( tables: &BTreeMap, table: &Table, depths: &mut HashMap, visited_set: &mut IndexSet, ) -> Result { if let Some(&depth) = depths.get(&table.name) { return Ok(depth); } // This keeps us from looping forever. fixture_assert!( visited_set.insert(table.name.clone()), "foreign key cycle detected: {:?} -> {:?}", visited_set, table.name ); let mut refdepth = 0; for (colname, (refname, refcol)) in &table.foreign_keys { let referenced = tables.get(refname).ok_or_else(|| { FixtureError(format!( "table {:?} in foreign key `{}.{} references {}.{}` does not exist", refname, table.name, colname, refname, refcol )) })?; refdepth = cmp::max( refdepth, foreign_key_depth(tables, referenced, depths, visited_set)?, ); } let depth = refdepth + 1; depths.insert(table.name.clone(), depth); Ok(depth) } #[test] #[cfg(feature = "any")] fn test_additive_fixture() -> Result<()> { // Just need something that implements `Database` use crate::any::Any; let mut snapshot = FixtureSnapshot { tables: BTreeMap::new(), db: PhantomData::, }; snapshot.tables.insert( "foo".into(), Table { name: "foo".into(), columns: ["foo_id", "foo_a", "foo_b"] .into_iter() .map(Arc::::from) .collect(), rows: vec![vec!["1".into(), "'asdf'".into(), "true".into()]], foreign_keys: HashMap::new(), }, ); // foreign-keyed to `foo` // since `tables` is a `BTreeMap` we would expect a naive algorithm to visit this first. snapshot.tables.insert( "bar".into(), Table { name: "bar".into(), columns: ["bar_id", "foo_id", "bar_a", "bar_b"] .into_iter() .map(Arc::::from) .collect(), rows: vec![vec![ "1234".into(), "1".into(), "'2022-07-22 23:27:48.775113301+00:00'".into(), "3.14".into(), ]], foreign_keys: [("foo_id".into(), ("foo".into(), "foo_id".into()))] .into_iter() .collect(), }, ); // foreign-keyed to both `foo` and `bar` snapshot.tables.insert( "baz".into(), Table { name: "baz".into(), columns: ["baz_id", "bar_id", "foo_id", "baz_a", "baz_b"] .into_iter() .map(Arc::::from) .collect(), rows: vec![vec![ "5678".into(), "1234".into(), "1".into(), "'2022-07-22 23:27:48.775113301+00:00'".into(), "3.14".into(), ]], foreign_keys: [ ("foo_id".into(), ("foo".into(), "foo_id".into())), ("bar_id".into(), ("bar".into(), "bar_id".into())), ] .into_iter() .collect(), }, ); let fixture = snapshot.additive_fixture()?; assert_eq!( fixture.to_string(), "INSERT INTO foo (foo_id, foo_a, foo_b)\n\ VALUES (1, 'asdf', true);\n\ INSERT INTO bar (bar_id, foo_id, bar_a, bar_b)\n\ VALUES (1234, 1, '2022-07-22 23:27:48.775113301+00:00', 3.14);\n\ INSERT INTO baz (baz_id, bar_id, foo_id, baz_a, baz_b)\n\ VALUES (5678, 1234, 1, '2022-07-22 23:27:48.775113301+00:00', 3.14);\n" ); Ok(()) } ================================================ FILE: sqlx-core/src/testing/mod.rs ================================================ use std::future::Future; use std::time::Duration; use base64::{engine::general_purpose::URL_SAFE, Engine as _}; pub use fixtures::FixtureSnapshot; use sha2::{Digest, Sha512}; use crate::connection::{ConnectOptions, Connection}; use crate::database::Database; use crate::error::Error; use crate::executor::Executor; use crate::migrate::{Migrate, Migrator}; use crate::pool::{Pool, PoolConnection, PoolOptions}; mod fixtures; pub trait TestSupport: Database { /// Get parameters to construct a `Pool` suitable for testing. /// /// This `Pool` instance will behave somewhat specially: /// * all handles share a single global semaphore to avoid exceeding the connection limit /// on the database server. /// * each invocation results in a different temporary database. /// /// The implementation may require `DATABASE_URL` to be set in order to manage databases. /// The user credentials it contains must have the privilege to create and drop databases. fn test_context( args: &TestArgs, ) -> impl Future, Error>> + Send + '_; fn cleanup_test(db_name: &str) -> impl Future> + Send + '_; /// Cleanup any test databases that are no longer in-use. /// /// Returns a count of the databases deleted, if possible. /// /// The implementation may require `DATABASE_URL` to be set in order to manage databases. /// The user credentials it contains must have the privilege to create and drop databases. fn cleanup_test_dbs() -> impl Future, Error>> + Send + 'static; /// Take a snapshot of the current state of the database (data only). /// /// This snapshot can then be used to generate test fixtures. fn snapshot( conn: &mut Self::Connection, ) -> impl Future, Error>> + Send + '_; /// Generate a unique database name for the given test path. fn db_name(args: &TestArgs) -> String { let mut hasher = Sha512::new(); hasher.update(args.test_path.as_bytes()); let hash = hasher.finalize(); let hash = URL_SAFE.encode(&hash[..39]); let db_name = format!("_sqlx_test_{}", hash).replace('-', "_"); debug_assert!(db_name.len() == 63); db_name } } pub struct TestFixture { pub path: &'static str, pub contents: &'static str, } pub struct TestArgs { pub test_path: &'static str, pub migrator: Option<&'static Migrator>, pub fixtures: &'static [TestFixture], } pub trait TestFn { type Output; fn run_test(self, args: TestArgs) -> Self::Output; } pub trait TestTermination { fn is_success(&self) -> bool; } pub struct TestContext { pub pool_opts: PoolOptions, pub connect_opts: ::Options, pub db_name: String, } impl TestFn for fn(Pool) -> Fut where DB: TestSupport + Database, DB::Connection: Migrate, for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>, Fut: Future, Fut::Output: TestTermination, { type Output = Fut::Output; fn run_test(self, args: TestArgs) -> Self::Output { run_test_with_pool(args, self) } } impl TestFn for fn(PoolConnection) -> Fut where DB: TestSupport + Database, DB::Connection: Migrate, for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>, Fut: Future, Fut::Output: TestTermination, { type Output = Fut::Output; fn run_test(self, args: TestArgs) -> Self::Output { run_test_with_pool(args, |pool| async move { let conn = pool .acquire() .await .expect("failed to acquire test pool connection"); let res = (self)(conn).await; pool.close().await; res }) } } impl TestFn for fn(PoolOptions, ::Options) -> Fut where DB: Database + TestSupport, DB::Connection: Migrate, for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>, Fut: Future, Fut::Output: TestTermination, { type Output = Fut::Output; fn run_test(self, args: TestArgs) -> Self::Output { run_test(args, self) } } impl TestFn for fn() -> Fut where Fut: Future, { type Output = Fut::Output; fn run_test(self, args: TestArgs) -> Self::Output { assert!( args.fixtures.is_empty(), "fixtures cannot be applied for a bare function" ); crate::rt::test_block_on(self()) } } impl TestArgs { pub fn new(test_path: &'static str) -> Self { TestArgs { test_path, migrator: None, fixtures: &[], } } pub fn migrator(&mut self, migrator: &'static Migrator) { self.migrator = Some(migrator); } pub fn fixtures(&mut self, fixtures: &'static [TestFixture]) { self.fixtures = fixtures; } } impl TestTermination for () { fn is_success(&self) -> bool { true } } impl TestTermination for Result { fn is_success(&self) -> bool { self.is_ok() } } fn run_test_with_pool(args: TestArgs, test_fn: F) -> Fut::Output where DB: TestSupport, DB::Connection: Migrate, for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>, F: FnOnce(Pool) -> Fut, Fut: Future, Fut::Output: TestTermination, { let test_path = args.test_path; run_test::(args, |pool_opts, connect_opts| async move { let pool = pool_opts .connect_with(connect_opts) .await .expect("failed to connect test pool"); let res = test_fn(pool.clone()).await; let close_timed_out = crate::rt::timeout(Duration::from_secs(10), pool.close()) .await .is_err(); if close_timed_out { eprintln!("test {test_path} held onto Pool after exiting"); } res }) } fn run_test(args: TestArgs, test_fn: F) -> Fut::Output where DB: TestSupport, DB::Connection: Migrate, for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>, F: FnOnce(PoolOptions, ::Options) -> Fut, Fut: Future, Fut::Output: TestTermination, { crate::rt::test_block_on(async move { let test_context = DB::test_context(&args) .await .expect("failed to connect to setup test database"); setup_test_db::(&test_context.connect_opts, &args).await; let res = test_fn(test_context.pool_opts, test_context.connect_opts).await; if res.is_success() { if let Err(e) = DB::cleanup_test(&DB::db_name(&args)).await { eprintln!( "failed to delete database {:?}: {}", test_context.db_name, e ); } } res }) } async fn setup_test_db( copts: &::Options, args: &TestArgs, ) where DB::Connection: Migrate + Sized, for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>, { let mut conn = copts .connect() .await .expect("failed to connect to test database"); if let Some(migrator) = args.migrator { migrator .run_direct(None, &mut conn) .await .expect("failed to apply migrations"); } for fixture in args.fixtures { (&mut conn) .execute(fixture.contents) .await .unwrap_or_else(|e| panic!("failed to apply test fixture {:?}: {:?}", fixture.path, e)); } conn.close() .await .expect("failed to close setup connection"); } ================================================ FILE: sqlx-core/src/transaction.rs ================================================ use std::fmt::{self, Debug, Formatter}; use std::future::{self, Future}; use std::ops::{Deref, DerefMut}; use futures_core::future::BoxFuture; use crate::database::Database; use crate::error::Error; use crate::pool::MaybePoolConnection; use crate::sql_str::{AssertSqlSafe, SqlSafeStr, SqlStr}; /// Generic management of database transactions. /// /// This trait should not be used, except when implementing [`Connection`]. pub trait TransactionManager { type Database: Database; /// Begin a new transaction or establish a savepoint within the active transaction. /// /// If this is a new transaction, `statement` may be used instead of the /// default "BEGIN" statement. /// /// If we are already inside a transaction and `statement.is_some()`, then /// `Error::InvalidSavePoint` is returned without running any statements. fn begin( conn: &mut ::Connection, statement: Option, ) -> impl Future> + Send + '_; /// Commit the active transaction or release the most recent savepoint. fn commit( conn: &mut ::Connection, ) -> impl Future> + Send + '_; /// Abort the active transaction or restore from the most recent savepoint. fn rollback( conn: &mut ::Connection, ) -> impl Future> + Send + '_; /// Starts to abort the active transaction or restore from the most recent snapshot. fn start_rollback(conn: &mut ::Connection); /// Returns the current transaction depth. /// /// Transaction depth indicates the level of nested transactions: /// - Level 0: No active transaction. /// - Level 1: A transaction is active. /// - Level 2 or higher: A transaction is active and one or more SAVEPOINTs have been created within it. fn get_transaction_depth(conn: &::Connection) -> usize; } /// An in-progress database transaction or savepoint. /// /// A transaction starts with a call to [`Pool::begin`] or [`Connection::begin`]. /// /// A transaction should end with a call to [`commit`] or [`rollback`]. If neither are called /// before the transaction goes out-of-scope, [`rollback`] is called. In other /// words, [`rollback`] is called on `drop` if the transaction is still in-progress. /// /// A savepoint is a special mark inside a transaction that allows all commands that are /// executed after it was established to be rolled back, restoring the transaction state to /// what it was at the time of the savepoint. /// /// A transaction can be used as an [`Executor`] when performing queries: /// ```rust,no_run /// # use sqlx_core::acquire::Acquire; /// # async fn example() -> sqlx::Result<()> { /// # let id = 1; /// # let mut conn: sqlx::PgConnection = unimplemented!(); /// let mut tx = conn.begin().await?; /// /// let result = sqlx::query("DELETE FROM \"testcases\" WHERE id = $1") /// .bind(id) /// .execute(&mut *tx) /// .await? /// .rows_affected(); /// /// tx.commit().await /// # } /// ``` /// [`Executor`]: crate::executor::Executor /// [`Connection::begin`]: crate::connection::Connection::begin() /// [`Pool::begin`]: crate::pool::Pool::begin() /// [`commit`]: Self::commit() /// [`rollback`]: Self::rollback() pub struct Transaction<'c, DB> where DB: Database, { connection: MaybePoolConnection<'c, DB>, open: bool, } impl<'c, DB> Transaction<'c, DB> where DB: Database, { #[doc(hidden)] pub fn begin( conn: impl Into>, statement: Option, ) -> BoxFuture<'c, Result> { let conn = conn.into(); Box::pin(async move { let mut tx = Self { connection: conn, // If the call to `begin` fails or doesn't complete we want to attempt a rollback in case the transaction was started. open: true, }; DB::TransactionManager::begin(&mut tx.connection, statement).await?; Ok(tx) }) } /// Commits this transaction or savepoint. pub async fn commit(mut self) -> Result<(), Error> { DB::TransactionManager::commit(&mut self.connection).await?; self.open = false; Ok(()) } /// Aborts this transaction or savepoint. pub async fn rollback(mut self) -> Result<(), Error> { DB::TransactionManager::rollback(&mut self.connection).await?; self.open = false; Ok(()) } } // NOTE: fails to compile due to lack of lazy normalization // impl<'c, 't, DB: Database> crate::executor::Executor<'t> // for &'t mut crate::transaction::Transaction<'c, DB> // where // &'c mut DB::Connection: Executor<'c, Database = DB>, // { // type Database = DB; // // // // fn fetch_many<'e, 'q: 'e, E: 'q>( // self, // query: E, // ) -> futures_core::stream::BoxStream< // 'e, // Result< // crate::Either<::QueryResult, DB::Row>, // crate::error::Error, // >, // > // where // 't: 'e, // E: crate::executor::Execute<'q, Self::Database>, // { // (&mut **self).fetch_many(query) // } // // fn fetch_optional<'e, 'q: 'e, E: 'q>( // self, // query: E, // ) -> futures_core::future::BoxFuture<'e, Result, crate::error::Error>> // where // 't: 'e, // E: crate::executor::Execute<'q, Self::Database>, // { // (&mut **self).fetch_optional(query) // } // // fn prepare_with<'e, 'q: 'e>( // self, // sql: &'q str, // parameters: &'e [::TypeInfo], // ) -> futures_core::future::BoxFuture< // 'e, // Result< // ::Statement<'q>, // crate::error::Error, // >, // > // where // 't: 'e, // { // (&mut **self).prepare_with(sql, parameters) // } // // #[doc(hidden)] // #[cfg(feature = "offline")] // fn describe<'e, 'q: 'e>( // self, // query: &'q str, // ) -> futures_core::future::BoxFuture< // 'e, // Result, crate::error::Error>, // > // where // 't: 'e, // { // (&mut **self).describe(query) // } // } impl Debug for Transaction<'_, DB> where DB: Database, { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { // TODO: Show the full type <..<..<.. f.debug_struct("Transaction").finish() } } impl Deref for Transaction<'_, DB> where DB: Database, { type Target = DB::Connection; #[inline] fn deref(&self) -> &Self::Target { &self.connection } } impl DerefMut for Transaction<'_, DB> where DB: Database, { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.connection } } // Implement `AsMut` so `Transaction` can be given to a // `PgAdvisoryLockGuard`. // // See: https://github.com/launchbadge/sqlx/issues/2520 impl AsMut for Transaction<'_, DB> { fn as_mut(&mut self) -> &mut DB::Connection { &mut self.connection } } impl<'t, DB: Database> crate::acquire::Acquire<'t> for &'t mut Transaction<'_, DB> { type Database = DB; type Connection = &'t mut ::Connection; #[inline] fn acquire(self) -> BoxFuture<'t, Result> { Box::pin(future::ready(Ok(&mut **self))) } #[inline] fn begin(self) -> BoxFuture<'t, Result, Error>> { Transaction::begin(&mut **self, None) } } impl Drop for Transaction<'_, DB> where DB: Database, { fn drop(&mut self) { if self.open { // starts a rollback operation // what this does depends on the database but generally this means we queue a rollback // operation that will happen on the next asynchronous invocation of the underlying // connection (including if the connection is returned to a pool) DB::TransactionManager::start_rollback(&mut self.connection); } } } pub fn begin_ansi_transaction_sql(depth: usize) -> SqlStr { if depth == 0 { "BEGIN".into_sql_str() } else { AssertSqlSafe(format!("SAVEPOINT _sqlx_savepoint_{depth}")).into_sql_str() } } pub fn commit_ansi_transaction_sql(depth: usize) -> SqlStr { if depth == 1 { "COMMIT".into_sql_str() } else { AssertSqlSafe(format!("RELEASE SAVEPOINT _sqlx_savepoint_{}", depth - 1)).into_sql_str() } } pub fn rollback_ansi_transaction_sql(depth: usize) -> SqlStr { if depth == 1 { "ROLLBACK".into_sql_str() } else { AssertSqlSafe(format!( "ROLLBACK TO SAVEPOINT _sqlx_savepoint_{}", depth - 1 )) .into_sql_str() } } ================================================ FILE: sqlx-core/src/type_checking.rs ================================================ use crate::config::macros::PreferredCrates; use crate::database::Database; use crate::decode::Decode; use crate::type_info::TypeInfo; use crate::value::Value; use std::any::Any; use std::fmt; use std::fmt::{Debug, Formatter}; /// The type of query parameter checking done by a SQL database. #[derive(PartialEq, Eq)] pub enum ParamChecking { /// Parameter checking is weak or nonexistent (uses coercion or allows mismatches). Weak, /// Parameter checking is strong (types must match exactly). Strong, } /// Type-checking extensions for the `Database` trait. /// /// Mostly supporting code for the macros, and for `Debug` impls. pub trait TypeChecking: Database { /// Describes how the database in question typechecks query parameters. const PARAM_CHECKING: ParamChecking; /// Get the full path of the Rust type that corresponds to the given `TypeInfo`, if applicable. /// /// If the type has a borrowed equivalent suitable for query parameters, /// this is that borrowed type. fn param_type_for_id( id: &Self::TypeInfo, preferred_crates: &PreferredCrates, ) -> Result<&'static str, Error>; /// Get the full path of the Rust type that corresponds to the given `TypeInfo`, if applicable. /// /// Always returns the owned version of the type, suitable for decoding from `Row`. fn return_type_for_id( id: &Self::TypeInfo, preferred_crates: &PreferredCrates, ) -> Result<&'static str, Error>; /// Get the name of the Cargo feature gate that must be enabled to process the given `TypeInfo`, /// if applicable. fn get_feature_gate(info: &Self::TypeInfo) -> Option<&'static str>; /// If `value` is a well-known type, decode and format it using `Debug`. /// /// If `value` is not a well-known type or could not be decoded, the reason is printed instead. fn fmt_value_debug(value: &::Value) -> FmtValue<'_, Self>; } pub type Result = std::result::Result; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("no built-in mapping found for SQL type; a type override may be required")] NoMappingFound, #[error("Cargo feature for configured `macros.preferred-crates.date-time` not enabled")] DateTimeCrateFeatureNotEnabled, #[error("Cargo feature for configured `macros.preferred-crates.numeric` not enabled")] NumericCrateFeatureNotEnabled, #[error("multiple date-time types are possible; falling back to `{fallback}`")] AmbiguousDateTimeType { fallback: &'static str }, #[error("multiple numeric types are possible; falling back to `{fallback}`")] AmbiguousNumericType { fallback: &'static str }, } /// An adapter for [`Value`] which attempts to decode the value and format it when printed using [`Debug`]. pub struct FmtValue<'v, DB> where DB: Database, { value: &'v ::Value, fmt: fn(&'v ::Value, &mut Formatter<'_>) -> fmt::Result, } impl<'v, DB> FmtValue<'v, DB> where DB: Database, { // This API can't take `ValueRef` directly as it would need to pass it to `Decode` by-value, // which means taking ownership of it. We cannot rely on a `Clone` impl because `SqliteValueRef` doesn't have one. /// When printed with [`Debug`], attempt to decode `value` as the given type `T` and format it using [`Debug`]. /// /// If `value` could not be decoded as `T`, the reason is printed instead. pub fn debug(value: &'v ::Value) -> Self where T: Decode<'v, DB> + Debug + Any, { Self { value, fmt: |value, f| { let info = value.type_info(); match T::decode(value.as_ref()) { Ok(value) => Debug::fmt(&value, f), Err(e) => { if e.is::() { f.write_str("NULL") } else { f.write_fmt(format_args!( "(error decoding SQL type {} as {}: {e:?})", info.name(), std::any::type_name::() )) } } } }, } } /// If the type to be decoded is not known or not supported, print the SQL type instead, /// as well as any applicable SQLx feature that needs to be enabled. pub fn unknown(value: &'v ::Value) -> Self where DB: TypeChecking, { Self { value, fmt: |value, f| { let info = value.type_info(); if let Some(feature_gate) = ::get_feature_gate(&info) { return f.write_fmt(format_args!( "(unknown SQL type {}: SQLx feature {feature_gate} not enabled)", info.name() )); } f.write_fmt(format_args!("(unknown SQL type {})", info.name())) }, } } } impl Debug for FmtValue<'_, DB> where DB: Database, { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { (self.fmt)(self.value, f) } } #[doc(hidden)] #[macro_export] macro_rules! select_input_type { ($ty:ty, $input:ty) => { stringify!($input) }; ($ty:ty) => { stringify!($ty) }; } #[macro_export] macro_rules! impl_type_checking { ( $database:path { $($(#[$meta:meta])? $ty:ty $(| $input:ty)?),*$(,)? }, ParamChecking::$param_checking:ident, feature-types: $ty_info:ident => $get_gate:expr, datetime-types: { chrono: { $($chrono_ty:ty $(| $chrono_input:ty)?),*$(,)? }, time: { $($time_ty:ty $(| $time_input:ty)?),*$(,)? }, }, numeric-types: { bigdecimal: { $($bigdecimal_ty:ty $(| $bigdecimal_input:ty)?),*$(,)? }, rust_decimal: { $($rust_decimal_ty:ty $(| $rust_decimal_input:ty)?),*$(,)? }, }, ) => { impl $crate::type_checking::TypeChecking for $database { const PARAM_CHECKING: $crate::type_checking::ParamChecking = $crate::type_checking::ParamChecking::$param_checking; fn param_type_for_id( info: &Self::TypeInfo, preferred_crates: &$crate::config::macros::PreferredCrates, ) -> Result<&'static str, $crate::type_checking::Error> { use $crate::config::macros::{DateTimeCrate, NumericCrate}; use $crate::type_checking::Error; // Check non-special types // --------------------- $( $(#[$meta])? if <$ty as sqlx_core::types::Type<$database>>::type_info() == *info { return Ok($crate::select_input_type!($ty $(, $input)?)); } )* $( $(#[$meta])? if <$ty as sqlx_core::types::Type<$database>>::compatible(info) { return Ok($crate::select_input_type!($ty $(, $input)?)); } )* // Check `macros.preferred-crates.date-time` // // Due to legacy reasons, `time` takes precedent over `chrono` if both are enabled. // Any crates added later should be _lower_ priority than `chrono` to avoid breakages. // ---------------------------------------- #[cfg(feature = "time")] if matches!(preferred_crates.date_time, DateTimeCrate::Time | DateTimeCrate::Inferred) { $( if <$time_ty as sqlx_core::types::Type<$database>>::type_info() == *info { if cfg!(feature = "chrono") { return Err($crate::type_checking::Error::AmbiguousDateTimeType { fallback: $crate::select_input_type!($time_ty $(, $time_input)?), }); } return Ok($crate::select_input_type!($time_ty $(, $time_input)?)); } )* $( if <$time_ty as sqlx_core::types::Type<$database>>::compatible(info) { if cfg!(feature = "chrono") { return Err($crate::type_checking::Error::AmbiguousDateTimeType { fallback: $crate::select_input_type!($time_ty $(, $time_input)?), }); } return Ok($crate::select_input_type!($time_ty $(, $time_input)?)); } )* } #[cfg(not(feature = "time"))] if preferred_crates.date_time == DateTimeCrate::Time { return Err(Error::DateTimeCrateFeatureNotEnabled); } #[cfg(feature = "chrono")] if matches!(preferred_crates.date_time, DateTimeCrate::Chrono | DateTimeCrate::Inferred) { $( if <$chrono_ty as sqlx_core::types::Type<$database>>::type_info() == *info { return Ok($crate::select_input_type!($chrono_ty $(, $chrono_input)?)); } )* $( if <$chrono_ty as sqlx_core::types::Type<$database>>::compatible(info) { return Ok($crate::select_input_type!($chrono_ty $(, $chrono_input)?)); } )* } #[cfg(not(feature = "chrono"))] if preferred_crates.date_time == DateTimeCrate::Chrono { return Err(Error::DateTimeCrateFeatureNotEnabled); } // Check `macros.preferred-crates.numeric` // // Due to legacy reasons, `bigdecimal` takes precedent over `rust_decimal` if // both are enabled. // ---------------------------------------- #[cfg(feature = "bigdecimal")] if matches!(preferred_crates.numeric, NumericCrate::BigDecimal | NumericCrate::Inferred) { $( if <$bigdecimal_ty as sqlx_core::types::Type<$database>>::type_info() == *info { if cfg!(feature = "rust_decimal") { return Err($crate::type_checking::Error::AmbiguousNumericType { fallback: $crate::select_input_type!($bigdecimal_ty $(, $bigdecimal_input)?), }); } return Ok($crate::select_input_type!($bigdecimal_ty $(, $bigdecimal_input)?)); } )* $( if <$bigdecimal_ty as sqlx_core::types::Type<$database>>::compatible(info) { if cfg!(feature = "rust_decimal") { return Err($crate::type_checking::Error::AmbiguousNumericType { fallback: $crate::select_input_type!($bigdecimal_ty $(, $bigdecimal_input)?), }); } return Ok($crate::select_input_type!($bigdecimal_ty $(, $bigdecimal_input)?)); } )* } #[cfg(not(feature = "bigdecimal"))] if preferred_crates.numeric == NumericCrate::BigDecimal { return Err(Error::NumericCrateFeatureNotEnabled); } #[cfg(feature = "rust_decimal")] if matches!(preferred_crates.numeric, NumericCrate::RustDecimal | NumericCrate::Inferred) { $( if <$rust_decimal_ty as sqlx_core::types::Type<$database>>::type_info() == *info { return Ok($crate::select_input_type!($rust_decimal_ty $(, $rust_decimal_input)?)); } )* $( if <$rust_decimal_ty as sqlx_core::types::Type<$database>>::compatible(info) { return Ok($crate::select_input_type!($rust_decimal_ty $(, $rust_decimal_input)?)); } )* } #[cfg(not(feature = "rust_decimal"))] if preferred_crates.numeric == NumericCrate::RustDecimal { return Err(Error::NumericCrateFeatureNotEnabled); } Err(Error::NoMappingFound) } fn return_type_for_id( info: &Self::TypeInfo, preferred_crates: &$crate::config::macros::PreferredCrates, ) -> Result<&'static str, $crate::type_checking::Error> { use $crate::config::macros::{DateTimeCrate, NumericCrate}; use $crate::type_checking::Error; // Check non-special types // --------------------- $( $(#[$meta])? if <$ty as sqlx_core::types::Type<$database>>::type_info() == *info { return Ok(stringify!($ty)); } )* $( $(#[$meta])? if <$ty as sqlx_core::types::Type<$database>>::compatible(info) { return Ok(stringify!($ty)); } )* // Check `macros.preferred-crates.date-time` // // Due to legacy reasons, `time` takes precedent over `chrono` if both are enabled. // Any crates added later should be _lower_ priority than `chrono` to avoid breakages. // ---------------------------------------- #[cfg(feature = "time")] if matches!(preferred_crates.date_time, DateTimeCrate::Time | DateTimeCrate::Inferred) { $( if <$time_ty as sqlx_core::types::Type<$database>>::type_info() == *info { if cfg!(feature = "chrono") { return Err($crate::type_checking::Error::AmbiguousDateTimeType { fallback: stringify!($time_ty), }); } return Ok(stringify!($time_ty)); } )* $( if <$time_ty as sqlx_core::types::Type<$database>>::compatible(info) { if cfg!(feature = "chrono") { return Err($crate::type_checking::Error::AmbiguousDateTimeType { fallback: stringify!($time_ty), }); } return Ok(stringify!($time_ty)); } )* } #[cfg(not(feature = "time"))] if preferred_crates.date_time == DateTimeCrate::Time { return Err(Error::DateTimeCrateFeatureNotEnabled); } #[cfg(feature = "chrono")] if matches!(preferred_crates.date_time, DateTimeCrate::Chrono | DateTimeCrate::Inferred) { $( if <$chrono_ty as sqlx_core::types::Type<$database>>::type_info() == *info { return Ok(stringify!($chrono_ty)); } )* $( if <$chrono_ty as sqlx_core::types::Type<$database>>::compatible(info) { return Ok(stringify!($chrono_ty)); } )* } #[cfg(not(feature = "chrono"))] if preferred_crates.date_time == DateTimeCrate::Chrono { return Err(Error::DateTimeCrateFeatureNotEnabled); } // Check `macros.preferred-crates.numeric` // // Due to legacy reasons, `bigdecimal` takes precedent over `rust_decimal` if // both are enabled. // ---------------------------------------- #[cfg(feature = "bigdecimal")] if matches!(preferred_crates.numeric, NumericCrate::BigDecimal | NumericCrate::Inferred) { $( if <$bigdecimal_ty as sqlx_core::types::Type<$database>>::type_info() == *info { if cfg!(feature = "rust_decimal") { return Err($crate::type_checking::Error::AmbiguousNumericType { fallback: stringify!($bigdecimal_ty), }); } return Ok(stringify!($bigdecimal_ty)); } )* $( if <$bigdecimal_ty as sqlx_core::types::Type<$database>>::compatible(info) { if cfg!(feature = "rust_decimal") { return Err($crate::type_checking::Error::AmbiguousNumericType { fallback: stringify!($bigdecimal_ty), }); } return Ok(stringify!($bigdecimal_ty)); } )* } #[cfg(not(feature = "bigdecimal"))] if preferred_crates.numeric == NumericCrate::BigDecimal { return Err(Error::NumericCrateFeatureNotEnabled); } #[cfg(feature = "rust_decimal")] if matches!(preferred_crates.numeric, NumericCrate::RustDecimal | NumericCrate::Inferred) { $( if <$rust_decimal_ty as sqlx_core::types::Type<$database>>::type_info() == *info { return Ok($crate::select_input_type!($rust_decimal_ty $(, $rust_decimal_input)?)); } )* $( if <$rust_decimal_ty as sqlx_core::types::Type<$database>>::compatible(info) { return Ok($crate::select_input_type!($rust_decimal_ty $(, $rust_decimal_input)?)); } )* } #[cfg(not(feature = "rust_decimal"))] if preferred_crates.numeric == NumericCrate::RustDecimal { return Err(Error::NumericCrateFeatureNotEnabled); } Err(Error::NoMappingFound) } fn get_feature_gate($ty_info: &Self::TypeInfo) -> Option<&'static str> { $get_gate } fn fmt_value_debug(value: &Self::Value) -> $crate::type_checking::FmtValue { use $crate::value::Value; let info = value.type_info(); #[cfg(feature = "time")] { $( if <$time_ty as sqlx_core::types::Type<$database>>::compatible(&info) { return $crate::type_checking::FmtValue::debug::<$time_ty>(value); } )* } #[cfg(feature = "chrono")] { $( if <$chrono_ty as sqlx_core::types::Type<$database>>::compatible(&info) { return $crate::type_checking::FmtValue::debug::<$chrono_ty>(value); } )* } #[cfg(feature = "bigdecimal")] { $( if <$bigdecimal_ty as sqlx_core::types::Type<$database>>::compatible(&info) { return $crate::type_checking::FmtValue::debug::<$bigdecimal_ty>(value); } )* } #[cfg(feature = "rust_decimal")] { $( if <$rust_decimal_ty as sqlx_core::types::Type<$database>>::compatible(&info) { return $crate::type_checking::FmtValue::debug::<$rust_decimal_ty>(value); } )* } $( $(#[$meta])? if <$ty as sqlx_core::types::Type<$database>>::compatible(&info) { return $crate::type_checking::FmtValue::debug::<$ty>(value); } )* $crate::type_checking::FmtValue::unknown(value) } } }; } ================================================ FILE: sqlx-core/src/type_info.rs ================================================ use std::fmt::{Debug, Display}; /// Provides information about a SQL type for the database driver. pub trait TypeInfo: Debug + Display + Clone + PartialEq + Send + Sync { fn is_null(&self) -> bool; /// Returns the database system name of the type. Length specifiers should not be included. /// Common type names are `VARCHAR`, `TEXT`, or `INT`. Type names should be uppercase. They /// should be a rough approximation of how they are written in SQL in the given database. fn name(&self) -> &str; /// Return `true` if `self` and `other` represent mutually compatible types. /// /// Defaults to `self == other`. fn type_compatible(&self, other: &Self) -> bool where Self: Sized, { self == other } #[doc(hidden)] fn is_void(&self) -> bool { false } } ================================================ FILE: sqlx-core/src/types/bstr.rs ================================================ /// Conversions between `bstr` types and SQL types. use crate::database::Database; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; #[doc(no_inline)] pub use bstr::{BStr, BString, ByteSlice}; impl Type for BString where DB: Database, [u8]: Type, { fn type_info() -> DB::TypeInfo { <&[u8] as Type>::type_info() } fn compatible(ty: &DB::TypeInfo) -> bool { <&[u8] as Type>::compatible(ty) } } impl<'r, DB> Decode<'r, DB> for BString where DB: Database, Vec: Decode<'r, DB>, { fn decode(value: ::ValueRef<'r>) -> Result { as Decode>::decode(value).map(BString::from) } } impl<'q, DB: Database> Encode<'q, DB> for &'q BStr where DB: Database, &'q [u8]: Encode<'q, DB>, { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { <&[u8] as Encode>::encode(self.as_bytes(), buf) } } impl<'q, DB: Database> Encode<'q, DB> for BString where DB: Database, Vec: Encode<'q, DB>, { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { as Encode>::encode(self.as_bytes().to_vec(), buf) } } ================================================ FILE: sqlx-core/src/types/json.rs ================================================ use std::ops::{Deref, DerefMut}; use serde::{Deserialize, Serialize}; pub use serde_json::value::RawValue as JsonRawValue; pub use serde_json::Value as JsonValue; use crate::database::Database; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; /// Json for json and jsonb fields /// /// Will attempt to cast to type passed in as the generic. /// /// ```toml /// [dependencies] /// serde_json = { version = "1.0", features = ["raw_value"] } /// /// ``` /// /// # Example /// /// ``` /// # use serde::Deserialize; /// #[derive(Deserialize)] /// struct Book { /// name: String /// } /// /// #[derive(sqlx::FromRow)] /// struct Author { /// name: String, /// books: sqlx::types::Json /// } /// ``` /// /// Can also be used to turn the json/jsonb into a hashmap /// ``` /// use std::collections::HashMap; /// use serde::Deserialize; /// /// #[derive(Deserialize)] /// struct Book { /// name: String /// } /// #[derive(sqlx::FromRow)] /// struct Library { /// id: String, /// dewey_decimal: sqlx::types::Json> /// } /// ``` /// /// If the query macros are used, it is necessary to tell the macro to use /// the `Json` adapter by using the type override syntax /// ```rust,ignore /// # async fn example3() -> sqlx::Result<()> { /// # let mut conn: sqlx::PgConnection = unimplemented!(); /// #[derive(sqlx::FromRow)] /// struct Book { /// title: String, /// } /// /// #[derive(sqlx::FromRow)] /// struct Author { /// name: String, /// books: sqlx::types::Json, /// } /// // Note the type override in the query string /// let authors = sqlx::query_as!( /// Author, /// r#" /// SELECT name, books as "books: Json" /// FROM authors /// "# /// ) /// .fetch_all(&mut conn) /// .await?; /// # Ok(()) /// # } /// ``` #[derive( Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize, )] #[serde(transparent)] pub struct Json(pub T); impl Json { /// Extract the inner value. pub fn into_inner(self) -> T { self.0 } } impl From for Json { fn from(value: T) -> Self { Self(value) } } impl Deref for Json { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for Json { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl AsRef for Json { fn as_ref(&self) -> &T { &self.0 } } impl AsMut for Json { fn as_mut(&mut self) -> &mut T { &mut self.0 } } // UNSTABLE: for driver use only! #[doc(hidden)] impl Json { pub fn encode_to_string(&self) -> Result { serde_json::to_string(self) } pub fn encode_to(&self, buf: &mut Vec) -> Result<(), serde_json::Error> { serde_json::to_writer(buf, self) } } // UNSTABLE: for driver use only! #[doc(hidden)] impl<'a, T: 'a> Json where T: Deserialize<'a>, { pub fn decode_from_string(s: &'a str) -> Result { serde_json::from_str(s).map_err(Into::into) } pub fn decode_from_bytes(bytes: &'a [u8]) -> Result { serde_json::from_slice(bytes).map_err(Into::into) } } impl Type for JsonValue where Json: Type, DB: Database, { fn type_info() -> DB::TypeInfo { as Type>::type_info() } fn compatible(ty: &DB::TypeInfo) -> bool { as Type>::compatible(ty) } } impl<'q, DB> Encode<'q, DB> for JsonValue where for<'a> Json<&'a Self>: Encode<'q, DB>, DB: Database, { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { as Encode<'q, DB>>::encode(Json(self), buf) } } impl<'r, DB> Decode<'r, DB> for JsonValue where Json: Decode<'r, DB>, DB: Database, { fn decode(value: ::ValueRef<'r>) -> Result { as Decode>::decode(value).map(|item| item.0) } } impl Type for JsonRawValue where for<'a> Json<&'a Self>: Type, DB: Database, { fn type_info() -> DB::TypeInfo { as Type>::type_info() } fn compatible(ty: &DB::TypeInfo) -> bool { as Type>::compatible(ty) } } impl<'q, DB> Encode<'q, DB> for JsonRawValue where for<'a> Json<&'a Self>: Encode<'q, DB>, DB: Database, { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { as Encode<'q, DB>>::encode(Json(self), buf) } } impl<'q, DB> Encode<'q, DB> for &'q JsonRawValue where for<'a> Json<&'a Self>: Encode<'q, DB>, DB: Database, { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { as Encode<'q, DB>>::encode(Json(self), buf) } } impl<'q, DB> Encode<'q, DB> for Box where for<'a> Json<&'a Self>: Encode<'q, DB>, DB: Database, { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { as Encode<'q, DB>>::encode(Json(self), buf) } } impl<'r, DB> Decode<'r, DB> for &'r JsonRawValue where Json: Decode<'r, DB>, DB: Database, { fn decode(value: ::ValueRef<'r>) -> Result { as Decode>::decode(value).map(|item| item.0) } } impl<'r, DB> Decode<'r, DB> for Box where Json: Decode<'r, DB>, DB: Database, { fn decode(value: ::ValueRef<'r>) -> Result { as Decode>::decode(value).map(|item| item.0) } } ================================================ FILE: sqlx-core/src/types/mod.rs ================================================ //! Conversions between Rust and SQL types. //! //! To see how each SQL type maps to a Rust type, see the corresponding `types` module for each //! database: //! //! * [PostgreSQL](crate::postgres::types) //! * [MySQL](crate::mysql::types) //! * [SQLite](crate::sqlite::types) //! * [MSSQL](crate::mssql::types) //! //! Any external types that have had [`Type`] implemented for, are re-exported in this module //! for convenience as downstream users need to use a compatible version of the external crate //! to take advantage of the implementation. //! //! # Nullable //! //! To represent nullable SQL types, `Option` is supported where `T` implements `Type`. //! An `Option` represents a potentially `NULL` value from SQL. use std::{borrow::Cow, rc::Rc, sync::Arc}; use crate::database::Database; use crate::type_info::TypeInfo; mod non_zero; #[cfg(feature = "bstr")] #[cfg_attr(docsrs, doc(cfg(feature = "bstr")))] pub mod bstr; #[cfg(feature = "json")] #[cfg_attr(docsrs, doc(cfg(feature = "json")))] mod json; mod text; #[cfg(feature = "uuid")] #[cfg_attr(docsrs, doc(cfg(feature = "uuid")))] #[doc(no_inline)] pub use uuid::{self, Uuid}; #[cfg(feature = "chrono")] #[cfg_attr(docsrs, doc(cfg(feature = "chrono")))] pub mod chrono { #[doc(no_inline)] pub use chrono::{ DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc, }; } #[cfg(feature = "bit-vec")] #[cfg_attr(docsrs, doc(cfg(feature = "bit-vec")))] #[doc(no_inline)] pub use bit_vec::BitVec; #[cfg(feature = "time")] #[cfg_attr(docsrs, doc(cfg(feature = "time")))] pub mod time { #[doc(no_inline)] pub use time::{Date, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset}; } #[cfg(feature = "bigdecimal")] #[cfg_attr(docsrs, doc(cfg(feature = "bigdecimal")))] #[doc(no_inline)] pub use bigdecimal::BigDecimal; #[cfg(feature = "rust_decimal")] #[cfg_attr(docsrs, doc(cfg(feature = "rust_decimal")))] #[doc(no_inline)] pub use rust_decimal::Decimal; #[cfg(feature = "ipnet")] #[cfg_attr(docsrs, doc(cfg(feature = "ipnet")))] pub mod ipnet { #[doc(no_inline)] pub use ipnet::{IpNet, Ipv4Net, Ipv6Net}; } #[cfg(feature = "ipnetwork")] #[cfg_attr(docsrs, doc(cfg(feature = "ipnetwork")))] pub mod ipnetwork { #[doc(no_inline)] pub use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network}; } #[cfg(feature = "mac_address")] #[cfg_attr(docsrs, doc(cfg(feature = "mac_address")))] pub mod mac_address { #[doc(no_inline)] pub use mac_address::MacAddress; } #[cfg(feature = "json")] pub use json::{Json, JsonRawValue, JsonValue}; pub use text::Text; #[cfg(feature = "bstr")] pub use bstr::{BStr, BString}; /// Indicates that a SQL type is supported for a database. /// /// ## Compile-time verification /// /// Type definitions are *not* verified against the database at compile-time. /// The [`query!()`](macro.query.html) macros have no implicit knowledge of user-defined types. /// /// When using custom types in query parameters or output columns with `query!()`, /// the use of [type overrides](macro.query.html#type-overrides-bind-parameters-postgres-only) is required. /// /// ```rust,ignore /// struct MyUser { id: UserId, name: String } /// /// // fetch all properties from user and override the type in Rust for `id` /// let user = query_as!(MyUser, r#"SELECT users.*, id as "id: UserId" FROM users"#) /// .fetch_one(&pool).await?; /// ``` /// /// ## Derivable /// /// This trait can be derived by SQLx to support Rust-only wrapper types, enumerations, and (for /// postgres) structured records. Additionally, an implementation of [`Encode`](crate::encode::Encode) and [`Decode`](crate::decode::Decode) is /// generated. /// /// ### Transparent /// /// Rust-only domain wrappers around SQL types. The generated implementations directly delegate /// to the implementation of the inner type. /// /// ```rust,ignore /// #[derive(sqlx::Type)] /// #[sqlx(transparent)] /// struct UserId(i64); /// ``` /// /// ##### Note: `PgHasArrayType` /// If you have the `postgres` feature enabled, this derive also generates a `PgHasArrayType` impl /// so that you may use it with `Vec` and other types that decode from an array in Postgres: /// /// ```rust,ignore /// let user_ids: Vec = sqlx::query_scalar("select '{ 123, 456 }'::int8[]") /// .fetch(&mut pg_connection) /// .await?; /// ``` /// /// However, if you are wrapping a type that does not implement `PgHasArrayType` /// (e.g. `Vec` itself, because we don't currently support multidimensional arrays), /// you may receive an error: /// /// ```rust,ignore /// #[derive(sqlx::Type)] // ERROR: `Vec` does not implement `PgHasArrayType` /// #[sqlx(transparent)] /// struct UserIds(Vec); /// ``` /// /// To remedy this, add `#[sqlx(no_pg_array)]`, which disables the generation /// of the `PgHasArrayType` impl: /// /// ```rust,ignore /// #[derive(sqlx::Type)] /// #[sqlx(transparent, no_pg_array)] /// struct UserIds(Vec); /// ``` /// /// ##### Attributes /// /// * `#[sqlx(type_name = "")]` on struct definition: instead of inferring the SQL /// type name from the inner field (in the above case, `BIGINT`), explicitly set it to /// `` instead. May trigger errors or unexpected behavior if the encoding of the /// given type is different than that of the inferred type (e.g. if you rename the above to /// `VARCHAR`). Affects Postgres only. /// * `#[sqlx(rename_all = "")]` on struct definition: See [`derive docs in FromRow`](crate::from_row::FromRow#rename_all) /// * `#[sqlx(no_pg_array)]`: do not emit a `PgHasArrayType` impl (see above). /// /// ### Enumeration /// /// Enumerations may be defined in Rust and can match SQL by /// integer discriminant or variant name. /// /// With `#[repr(_)]` the integer representation is used when converting from/to SQL and expects /// that SQL type (e.g., `INT`). Without, the names of the variants are used instead and /// expects a textual SQL type (e.g., `VARCHAR`, `TEXT`). /// /// ```rust,ignore /// #[derive(sqlx::Type)] /// #[repr(i32)] /// enum Color { Red = 1, Green = 2, Blue = 3 } /// ``` /// /// ```rust,ignore /// #[derive(sqlx::Type)] /// #[sqlx(type_name = "color")] // only for PostgreSQL to match a type definition /// #[sqlx(rename_all = "lowercase")] /// enum Color { Red, Green, Blue } /// ``` /// /// ### Records /// /// User-defined composite types are supported through deriving a `struct`. /// /// This is only supported for PostgreSQL. /// /// ```rust,ignore /// #[derive(sqlx::Type)] /// #[sqlx(type_name = "interface_type")] /// struct InterfaceType { /// name: String, /// supplier_id: i32, /// price: f64 /// } /// ``` pub trait Type { /// Returns the canonical SQL type for this Rust type. /// /// When binding arguments, this is used to tell the database what is about to be sent; which, /// the database then uses to guide query plans. This can be overridden by `Encode::produces`. /// /// A map of SQL types to Rust types is populated with this and used /// to determine the type that is returned from the anonymous struct type from `query!`. fn type_info() -> DB::TypeInfo; /// Determines if this Rust type is compatible with the given SQL type. /// /// When decoding values from a row, this method is checked to determine if we should continue /// or raise a runtime type mismatch error. /// /// When binding arguments with `query!` or `query_as!`, this method is consulted to determine /// if the Rust type is acceptable. /// /// Defaults to checking [`TypeInfo::type_compatible()`]. fn compatible(ty: &DB::TypeInfo) -> bool { Self::type_info().type_compatible(ty) } } // for references, the underlying SQL type is identical impl, DB: Database> Type for &'_ T { fn type_info() -> DB::TypeInfo { >::type_info() } fn compatible(ty: &DB::TypeInfo) -> bool { >::compatible(ty) } } // for optionals, the underlying SQL type is identical impl, DB: Database> Type for Option { fn type_info() -> DB::TypeInfo { >::type_info() } fn compatible(ty: &DB::TypeInfo) -> bool { ty.is_null() || >::compatible(ty) } } macro_rules! impl_type_for_smartpointer { ($smart_pointer:ty) => { impl Type for $smart_pointer where T: Type + ?Sized, { fn type_info() -> DB::TypeInfo { >::type_info() } fn compatible(ty: &DB::TypeInfo) -> bool { >::compatible(ty) } } }; } impl_type_for_smartpointer!(Arc); impl_type_for_smartpointer!(Box); impl_type_for_smartpointer!(Rc); impl Type for Cow<'_, T> where // `ToOwned` is required here to satisfy `Cow` T: Type + ToOwned + ?Sized, { fn type_info() -> DB::TypeInfo { >::type_info() } fn compatible(ty: &DB::TypeInfo) -> bool { >::compatible(ty) } } ================================================ FILE: sqlx-core/src/types/non_zero.rs ================================================ //! [`Type`], [`Encode`], and [`Decode`] implementations for the various [`NonZero*`][non-zero] //! types from the standard library. //! //! [non-zero]: core::num::NonZero use std::num::{ NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, }; use crate::database::Database; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::types::Type; macro_rules! impl_non_zero { ($($int:ty => $non_zero:ty),* $(,)?) => { $(impl Type for $non_zero where DB: Database, $int: Type, { fn type_info() -> ::TypeInfo { <$int as Type>::type_info() } fn compatible(ty: &::TypeInfo) -> bool { <$int as Type>::compatible(ty) } } impl<'q, DB> Encode<'q, DB> for $non_zero where DB: Database, $int: Encode<'q, DB>, { fn encode_by_ref(&self, buf: &mut ::ArgumentBuffer) -> Result { <$int as Encode<'q, DB>>::encode_by_ref(&self.get(), buf) } fn encode(self, buf: &mut ::ArgumentBuffer) -> Result where Self: Sized, { <$int as Encode<'q, DB>>::encode(self.get(), buf) } fn produces(&self) -> Option<::TypeInfo> { <$int as Encode<'q, DB>>::produces(&self.get()) } } impl<'r, DB> Decode<'r, DB> for $non_zero where DB: Database, $int: Decode<'r, DB>, { fn decode(value: ::ValueRef<'r>) -> Result { let int = <$int as Decode<'r, DB>>::decode(value)?; let non_zero = Self::try_from(int)?; Ok(non_zero) } })* }; } impl_non_zero! { i8 => NonZeroI8, u8 => NonZeroU8, i16 => NonZeroI16, u16 => NonZeroU16, i32 => NonZeroI32, u32 => NonZeroU32, i64 => NonZeroI64, u64 => NonZeroU64, } ================================================ FILE: sqlx-core/src/types/text.rs ================================================ use std::ops::{Deref, DerefMut}; /// Map a SQL text value to/from a Rust type using [`Display`] and [`FromStr`]. /// /// This can be useful for types that do not have a direct SQL equivalent, or are simply not /// supported by SQLx for one reason or another. /// /// For strongly typed databases like Postgres, this will report the value's type as `TEXT`. /// Explicit conversion may be necessary on the SQL side depending on the desired type. /// /// [`Display`]: std::fmt::Display /// [`FromStr`]: std::str::FromStr /// /// ### Panics /// /// You should only use this adapter with `Display` implementations that are infallible, /// otherwise you may encounter panics when attempting to bind a value. /// /// This is because the design of the `Encode` trait assumes encoding is infallible, so there is no /// way to bubble up the error. /// /// Fortunately, most `Display` implementations are infallible by convention anyway /// (the standard `ToString` trait also assumes this), but you may still want to audit /// the source code for any types you intend to use with this adapter, just to be safe. /// /// ### Example: `SocketAddr` /// /// MySQL and SQLite do not have a native SQL equivalent for `SocketAddr`, so if you want to /// store and retrieve instances of it, it makes sense to map it to `TEXT`: /// /// ```rust,no_run /// # use sqlx::types::{time, uuid}; /// /// use std::net::SocketAddr; /// /// use sqlx::Connection; /// use sqlx::mysql::MySqlConnection; /// use sqlx::types::Text; /// /// use uuid::Uuid; /// use time::OffsetDateTime; /// /// #[derive(sqlx::FromRow, Debug)] /// struct Login { /// user_id: Uuid, /// socket_addr: Text, /// login_at: OffsetDateTime /// } /// /// # async fn example() -> Result<(), Box> { /// /// let mut conn: MySqlConnection = MySqlConnection::connect("").await?; /// /// let user_id: Uuid = "e9a72cdc-d907-48d6-a488-c64a91fd063c".parse().unwrap(); /// let socket_addr: SocketAddr = "198.51.100.47:31790".parse().unwrap(); /// /// // CREATE TABLE user_login(user_id VARCHAR(36), socket_addr TEXT, login_at TIMESTAMP); /// sqlx::query("INSERT INTO user_login(user_id, socket_addr, login_at) VALUES (?, ?, NOW())") /// .bind(user_id) /// .bind(Text(socket_addr)) /// .execute(&mut conn) /// .await?; /// /// let logins: Vec = sqlx::query_as("SELECT * FROM user_login") /// .fetch_all(&mut conn) /// .await?; /// /// println!("Logins for user ID {user_id}: {logins:?}"); /// /// # Ok(()) /// # } /// ``` #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Text(pub T); impl Text { /// Extract the inner value. pub fn into_inner(self) -> T { self.0 } } impl Deref for Text { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for Text { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } /* We shouldn't use blanket impls so individual drivers can provide specialized ones. impl Type for Text where String: Type, DB: Database, { fn type_info() -> DB::TypeInfo { String::type_info() } fn compatible(ty: &DB::TypeInfo) -> bool { String::compatible(ty) } } impl<'q, T, DB> Encode<'q, DB> for Text where T: Display, String: Encode<'q, DB>, DB: Database, { fn encode_by_ref(&self, buf: &mut ::ArgumentBuffer) -> Result { self.0.to_string().encode(buf) } } impl<'r, T, DB> Decode<'r, DB> for Text where T: FromStr, BoxDynError: From<::Err>, &'r str: Decode<'r, DB>, DB: Database, { fn decode(value: ::ValueRef<'r>) -> Result { Ok(Text(<&'r str as Decode<'r, DB>>::decode(value)?.parse()?)) } } */ ================================================ FILE: sqlx-core/src/value.rs ================================================ use crate::database::Database; use crate::decode::Decode; use crate::error::{mismatched_types, Error}; use crate::type_info::TypeInfo; use crate::types::Type; use std::borrow::Cow; /// An owned value from the database. pub trait Value { type Database: Database; /// Get this value as a reference. fn as_ref(&self) -> ::ValueRef<'_>; /// Get the type information for this value. fn type_info(&self) -> Cow<'_, ::TypeInfo>; /// Returns `true` if the SQL value is `NULL`. fn is_null(&self) -> bool; /// Decode this single value into the requested type. /// /// # Panics /// /// Panics if the value cannot be decoded into the requested type. /// See [`try_decode`](Self::try_decode) for a non-panicking version. /// #[inline] fn decode<'r, T>(&'r self) -> T where T: Decode<'r, Self::Database> + Type, { self.try_decode::().unwrap() } /// Decode this single value into the requested type. /// /// Unlike [`decode`](Self::decode), this method does not check that the type of this /// value is compatible with the Rust type and blindly tries to decode the value. /// /// # Panics /// /// Panics if the value cannot be decoded into the requested type. /// See [`try_decode_unchecked`](Self::try_decode_unchecked) for a non-panicking version. /// #[inline] fn decode_unchecked<'r, T>(&'r self) -> T where T: Decode<'r, Self::Database>, { self.try_decode_unchecked::().unwrap() } /// Decode this single value into the requested type. /// /// # Errors /// /// * [`Decode`] if the value could not be decoded into the requested type. /// /// [`Decode`]: Error::Decode /// #[inline] fn try_decode<'r, T>(&'r self) -> Result where T: Decode<'r, Self::Database> + Type, { if !self.is_null() { let ty = self.type_info(); if !ty.is_null() && !T::compatible(&ty) { return Err(Error::Decode(mismatched_types::(&ty))); } } self.try_decode_unchecked() } /// Decode this single value into the requested type. /// /// Unlike [`try_decode`](Self::try_decode), this method does not check that the type of this /// value is compatible with the Rust type and blindly tries to decode the value. /// /// # Errors /// /// * [`Decode`] if the value could not be decoded into the requested type. /// /// [`Decode`]: Error::Decode /// #[inline] fn try_decode_unchecked<'r, T>(&'r self) -> Result where T: Decode<'r, Self::Database>, { T::decode(self.as_ref()).map_err(Error::Decode) } } /// A reference to a single value from the database. pub trait ValueRef<'r>: Sized { type Database: Database; /// Creates an owned value from this value reference. /// /// This is just a reference increment in PostgreSQL and MySQL and thus is `O(1)`. In SQLite, /// this is a copy. fn to_owned(&self) -> ::Value; /// Get the type information for this value. fn type_info(&self) -> Cow<'_, ::TypeInfo>; /// Returns `true` if the SQL value is `NULL`. fn is_null(&self) -> bool; } ================================================ FILE: sqlx-macros/Cargo.toml ================================================ [package] name = "sqlx-macros" description = "Macros for SQLx, the rust SQL toolkit. Not intended to be used directly." version.workspace = true license.workspace = true edition.workspace = true authors.workspace = true repository.workspace = true rust-version.workspace = true [lib] proc-macro = true [features] default = [] # for conditional compilation _rt-async-global-executor = ["sqlx-macros-core/_rt-async-global-executor"] _rt-async-std = ["sqlx-macros-core/_rt-async-std"] _rt-smol = ["sqlx-macros-core/_rt-smol"] _rt-tokio = ["sqlx-macros-core/_rt-tokio"] _tls-native-tls = ["sqlx-macros-core/_tls-native-tls"] _tls-rustls-aws-lc-rs = ["sqlx-macros-core/_tls-rustls-aws-lc-rs"] _tls-rustls-ring-webpki = ["sqlx-macros-core/_tls-rustls-ring-webpki"] _tls-rustls-ring-native-roots = ["sqlx-macros-core/_tls-rustls-ring-native-roots"] # SQLx features derive = ["sqlx-macros-core/derive"] macros = ["sqlx-macros-core/macros"] migrate = ["sqlx-macros-core/migrate"] sqlx-toml = ["sqlx-macros-core/sqlx-toml"] # database mysql = ["sqlx-macros-core/mysql"] postgres = ["sqlx-macros-core/postgres"] sqlite = ["sqlx-macros-core/sqlite"] sqlite-unbundled = ["sqlx-macros-core/sqlite-unbundled"] sqlite-load-extension = ["sqlx-macros-core/sqlite-load-extension"] # type bigdecimal = ["sqlx-macros-core/bigdecimal"] bit-vec = ["sqlx-macros-core/bit-vec"] chrono = ["sqlx-macros-core/chrono"] ipnet = ["sqlx-macros-core/ipnet"] ipnetwork = ["sqlx-macros-core/ipnetwork"] mac_address = ["sqlx-macros-core/mac_address"] rust_decimal = ["sqlx-macros-core/rust_decimal"] time = ["sqlx-macros-core/time"] uuid = ["sqlx-macros-core/uuid"] json = ["sqlx-macros-core/json"] [dependencies] sqlx-core = { workspace = true, features = ["any"] } sqlx-macros-core = { workspace = true } proc-macro2 = { version = "1.0.36", default-features = false } syn = { version = "2.0.52", default-features = false, features = ["parsing", "proc-macro"] } quote = { version = "1.0.26", default-features = false } [lints] workspace = true ================================================ FILE: sqlx-macros/src/lib.rs ================================================ use proc_macro::TokenStream; use quote::quote; use sqlx_macros_core::*; #[cfg(feature = "macros")] #[proc_macro] pub fn expand_query(input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as query::QueryMacroInput); match query::expand_input(input, FOSS_DRIVERS) { Ok(ts) => ts.into(), Err(e) => { if let Some(parse_err) = e.downcast_ref::() { parse_err.to_compile_error().into() } else { let msg = e.to_string(); quote!(::std::compile_error!(#msg)).into() } } } } #[cfg(feature = "derive")] #[proc_macro_derive(Encode, attributes(sqlx))] pub fn derive_encode(tokenstream: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(tokenstream as syn::DeriveInput); match derives::expand_derive_encode(&input) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } } #[cfg(feature = "derive")] #[proc_macro_derive(Decode, attributes(sqlx))] pub fn derive_decode(tokenstream: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(tokenstream as syn::DeriveInput); match derives::expand_derive_decode(&input) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } } #[cfg(feature = "derive")] #[proc_macro_derive(Type, attributes(sqlx))] pub fn derive_type(tokenstream: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(tokenstream as syn::DeriveInput); match derives::expand_derive_type_encode_decode(&input) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } } #[cfg(feature = "derive")] #[proc_macro_derive(FromRow, attributes(sqlx))] pub fn derive_from_row(input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as syn::DeriveInput); match derives::expand_derive_from_row(&input) { Ok(ts) => ts.into(), Err(e) => e.to_compile_error().into(), } } #[cfg(feature = "migrate")] #[proc_macro] pub fn migrate(input: TokenStream) -> TokenStream { use syn::LitStr; let input = syn::parse_macro_input!(input as Option); match migrate::expand(input) { Ok(ts) => ts.into(), Err(e) => { if let Some(parse_err) = e.downcast_ref::() { parse_err.to_compile_error().into() } else { let msg = e.to_string(); quote!(::std::compile_error!(#msg)).into() } } } } #[cfg(feature = "macros")] #[proc_macro_attribute] pub fn test(args: TokenStream, input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as syn::ItemFn); match test_attr::expand(args.into(), input) { Ok(ts) => ts.into(), Err(e) => { if let Some(parse_err) = e.downcast_ref::() { parse_err.to_compile_error().into() } else { let msg = e.to_string(); quote!(::std::compile_error!(#msg)).into() } } } } ================================================ FILE: sqlx-macros-core/Cargo.toml ================================================ [package] name = "sqlx-macros-core" description = "Macro support core for SQLx, the Rust SQL toolkit. Not intended to be used directly." version.workspace = true license.workspace = true edition.workspace = true authors.workspace = true repository.workspace = true rust-version.workspace = true [features] default = [] # for conditional compilation _rt-async-global-executor = ["async-global-executor", "sqlx-core/_rt-async-global-executor"] _rt-async-std = ["async-std", "sqlx-core/_rt-async-std"] _rt-smol = ["smol", "sqlx-core/_rt-smol"] _rt-tokio = ["tokio", "sqlx-core/_rt-tokio"] _tls-native-tls = ["sqlx-core/_tls-native-tls"] _tls-rustls-aws-lc-rs = ["sqlx-core/_tls-rustls-aws-lc-rs"] _tls-rustls-ring-webpki = ["sqlx-core/_tls-rustls-ring-webpki"] _tls-rustls-ring-native-roots = ["sqlx-core/_tls-rustls-ring-native-roots"] _sqlite = [] # SQLx features derive = [] macros = ["thiserror"] migrate = ["sqlx-core/migrate"] sqlx-toml = ["sqlx-core/sqlx-toml", "sqlx-sqlite?/sqlx-toml"] # database mysql = ["sqlx-mysql"] postgres = ["sqlx-postgres"] sqlite = ["_sqlite", "sqlx-sqlite/bundled"] sqlite-unbundled = ["_sqlite", "sqlx-sqlite/unbundled"] # Enables `drivers.sqlite.unsafe-load-extensions` in sqlx.toml sqlite-load-extension = ["sqlx-sqlite/load-extension"] # type integrations json = ["sqlx-core/json", "sqlx-mysql?/json", "sqlx-postgres?/json", "sqlx-sqlite?/json"] bigdecimal = ["sqlx-core/bigdecimal", "sqlx-mysql?/bigdecimal", "sqlx-postgres?/bigdecimal"] bit-vec = ["sqlx-core/bit-vec", "sqlx-postgres?/bit-vec"] chrono = ["sqlx-core/chrono", "sqlx-mysql?/chrono", "sqlx-postgres?/chrono", "sqlx-sqlite?/chrono"] ipnet = ["sqlx-core/ipnet", "sqlx-postgres?/ipnet"] ipnetwork = ["sqlx-core/ipnetwork", "sqlx-postgres?/ipnetwork"] mac_address = ["sqlx-core/mac_address", "sqlx-postgres?/mac_address"] rust_decimal = ["sqlx-core/rust_decimal", "sqlx-mysql?/rust_decimal", "sqlx-postgres?/rust_decimal"] time = ["sqlx-core/time", "sqlx-mysql?/time", "sqlx-postgres?/time", "sqlx-sqlite?/time"] uuid = ["sqlx-core/uuid", "sqlx-mysql?/uuid", "sqlx-postgres?/uuid", "sqlx-sqlite?/uuid"] [dependencies] sqlx-core = { workspace = true, features = ["offline"] } sqlx-mysql = { workspace = true, features = ["offline", "migrate"], optional = true } sqlx-postgres = { workspace = true, features = ["offline", "migrate"], optional = true } sqlx-sqlite = { workspace = true, features = ["offline", "migrate"], optional = true } async-global-executor = { workspace = true, optional = true } async-std = { workspace = true, optional = true } smol = { workspace = true, optional = true } tokio = { workspace = true, optional = true } cfg-if = { workspace = true} dotenvy = { workspace = true } thiserror = { workspace = true, optional = true } hex = { version = "0.4.3" } heck = { version = "0.5" } either = "1.6.1" proc-macro2 = { version = "1.0.79", default-features = false } serde = { version = "1.0.132", features = ["derive"] } serde_json = { version = "1.0.73" } sha2 = { version = "0.10.0" } syn = { version = "2.0.52", default-features = false, features = ["full", "derive", "parsing", "printing", "clone-impls"] } quote = { version = "1.0.26", default-features = false } url = { version = "2.2.2" } [lints.rust.unexpected_cfgs] level = "warn" check-cfg = ['cfg(sqlx_macros_unstable)', 'cfg(procmacro2_semver_exempt)'] ================================================ FILE: sqlx-macros-core/clippy.toml ================================================ [[disallowed-methods]] path = "std::env::var" reason = "use `crate::env()` instead, which optionally calls `proc_macro::tracked_env::var()`" ================================================ FILE: sqlx-macros-core/src/common.rs ================================================ use proc_macro2::Span; use std::path::{Path, PathBuf}; pub(crate) fn resolve_path(path: impl AsRef, err_span: Span) -> syn::Result { let path = path.as_ref(); if path.is_absolute() { return Err(syn::Error::new( err_span, "absolute paths will only work on the current machine", )); } // requires `proc_macro::SourceFile::path()` to be stable // https://github.com/rust-lang/rust/issues/54725 if path.is_relative() && path .parent() .is_none_or(|parent| parent.as_os_str().is_empty()) { return Err(syn::Error::new( err_span, "paths relative to the current file's directory are not currently supported", )); } let mut out_path = crate::manifest_dir().map_err(|e| syn::Error::new(err_span, e))?; out_path.push(path); Ok(out_path) } ================================================ FILE: sqlx-macros-core/src/database/impls.rs ================================================ macro_rules! impl_database_ext { ( $database:path, row: $row:path, $(describe-blocking: $describe:path,)? ) => { impl $crate::database::DatabaseExt for $database { const DATABASE_PATH: &'static str = stringify!($database); const ROW_PATH: &'static str = stringify!($row); impl_describe_blocking!($database, $($describe)?); } } } macro_rules! impl_describe_blocking { ($database:path $(,)?) => { fn describe_blocking( query: &str, database_url: &str, driver_config: &sqlx_core::config::drivers::Config, ) -> sqlx_core::Result> { use $crate::database::CachingDescribeBlocking; // This can't be a provided method because the `static` can't reference `Self`. static CACHE: CachingDescribeBlocking<$database> = CachingDescribeBlocking::new(); CACHE.describe(query, database_url, driver_config) } }; ($database:path, $describe:path) => { fn describe_blocking( query: &str, database_url: &str, driver_config: &sqlx_core::config::drivers::Config, ) -> sqlx_core::Result> { $describe(query, database_url, driver_config) } }; } // The paths below will also be emitted from the macros, so they need to match the final facade. mod sqlx { #[cfg(feature = "mysql")] pub use sqlx_mysql as mysql; #[cfg(feature = "postgres")] pub use sqlx_postgres as postgres; #[cfg(feature = "_sqlite")] pub use sqlx_sqlite as sqlite; } // NOTE: type mappings have been moved to `src/type_checking.rs` in their respective driver crates. #[cfg(feature = "mysql")] impl_database_ext! { sqlx::mysql::MySql, row: sqlx::mysql::MySqlRow, } #[cfg(feature = "postgres")] impl_database_ext! { sqlx::postgres::Postgres, row: sqlx::postgres::PgRow, } #[cfg(feature = "_sqlite")] impl_database_ext! { sqlx::sqlite::Sqlite, row: sqlx::sqlite::SqliteRow, // Since proc-macros don't benefit from async, we can make a describe call directly // which also ensures that the database is closed afterwards, regardless of errors. describe-blocking: sqlx_sqlite::describe_blocking, } ================================================ FILE: sqlx-macros-core/src/database/mod.rs ================================================ use sqlx_core::config; use sqlx_core::connection::Connection; use sqlx_core::database::Database; use sqlx_core::describe::Describe; use sqlx_core::executor::Executor; use sqlx_core::sql_str::AssertSqlSafe; use sqlx_core::sql_str::SqlSafeStr; use sqlx_core::type_checking::TypeChecking; use std::collections::hash_map; use std::collections::HashMap; use std::sync::{LazyLock, Mutex}; #[cfg(any(feature = "postgres", feature = "mysql", feature = "_sqlite"))] mod impls; pub trait DatabaseExt: Database + TypeChecking { const DATABASE_PATH: &'static str; const ROW_PATH: &'static str; fn db_path() -> syn::Path { syn::parse_str(Self::DATABASE_PATH).unwrap() } fn row_path() -> syn::Path { syn::parse_str(Self::ROW_PATH).unwrap() } fn describe_blocking( query: &str, database_url: &str, driver_config: &config::drivers::Config, ) -> sqlx_core::Result>; } #[allow(dead_code)] pub struct CachingDescribeBlocking { connections: LazyLock>>, } #[allow(dead_code)] impl CachingDescribeBlocking { #[allow(clippy::new_without_default, reason = "internal API")] pub const fn new() -> Self { CachingDescribeBlocking { connections: LazyLock::new(|| Mutex::new(HashMap::new())), } } pub fn describe( &self, query: &str, database_url: &str, _driver_config: &config::drivers::Config, ) -> sqlx_core::Result> where for<'a> &'a mut DB::Connection: Executor<'a, Database = DB>, { let mut cache = self .connections .lock() .expect("previous panic in describe call"); crate::block_on(async { let conn = match cache.entry(database_url.to_string()) { hash_map::Entry::Occupied(hit) => hit.into_mut(), hash_map::Entry::Vacant(miss) => { let conn = miss.insert(DB::Connection::connect(database_url).await?); #[cfg(feature = "postgres")] if DB::NAME == sqlx_postgres::Postgres::NAME { conn.execute( " DO $$ BEGIN IF EXISTS ( SELECT 1 FROM pg_settings WHERE name = 'plan_cache_mode' ) THEN SET SESSION plan_cache_mode = 'force_generic_plan'; END IF; END $$; ", ) .await?; } conn } }; match conn .describe(AssertSqlSafe(query.to_string()).into_sql_str()) .await { Ok(describe) => Ok(describe), Err(e) => { if matches!(e, sqlx_core::Error::Io(_) | sqlx_core::Error::Protocol(_)) { cache.remove(database_url); } Err(e) } } }) } } ================================================ FILE: sqlx-macros-core/src/derives/attributes.rs ================================================ use proc_macro2::{Ident, Span, TokenStream}; use quote::quote_spanned; use syn::{ parenthesized, punctuated::Punctuated, token::Comma, Attribute, DeriveInput, Field, LitStr, Meta, Token, Type, Variant, }; macro_rules! assert_attribute { ($e:expr, $err:expr, $input:expr) => { if !$e { return Err(syn::Error::new_spanned($input, $err)); } }; } macro_rules! fail { ($t:expr, $m:expr) => { return Err(syn::Error::new_spanned($t, $m)) }; } macro_rules! try_set { ($i:ident, $v:expr, $t:expr) => { match $i { None => $i = Some($v), Some(_) => fail!($t, "duplicate attribute"), } }; } pub struct TypeName { pub val: String, pub span: Span, } impl TypeName { pub fn get(&self) -> TokenStream { let val = &self.val; quote_spanned! { self.span => #val } } } #[derive(Copy, Clone)] #[allow(clippy::enum_variant_names)] pub enum RenameAll { LowerCase, SnakeCase, UpperCase, ScreamingSnakeCase, KebabCase, CamelCase, PascalCase, } pub struct SqlxContainerAttributes { pub transparent: bool, pub type_name: Option, pub rename_all: Option, pub repr: Option, pub no_pg_array: bool, pub default: bool, } pub enum JsonAttribute { NonNullable, Nullable, } pub struct SqlxChildAttributes { pub rename: Option, pub default: bool, pub flatten: bool, pub try_from: Option, pub skip: bool, pub json: Option, } pub fn parse_container_attributes(input: &[Attribute]) -> syn::Result { let mut transparent = None; let mut repr = None; let mut type_name = None; let mut rename_all = None; let mut no_pg_array = None; let mut default = None; for attr in input { if attr.path().is_ident("sqlx") { attr.parse_nested_meta(|meta| { if meta.path.is_ident("transparent") { try_set!(transparent, true, attr); } else if meta.path.is_ident("no_pg_array") { try_set!(no_pg_array, true, attr); } else if meta.path.is_ident("default") { try_set!(default, true, attr); } else if meta.path.is_ident("rename_all") { meta.input.parse::()?; let lit: LitStr = meta.input.parse()?; let val = match lit.value().as_str() { "lowercase" => RenameAll::LowerCase, "snake_case" => RenameAll::SnakeCase, "UPPERCASE" => RenameAll::UpperCase, "SCREAMING_SNAKE_CASE" => RenameAll::ScreamingSnakeCase, "kebab-case" => RenameAll::KebabCase, "camelCase" => RenameAll::CamelCase, "PascalCase" => RenameAll::PascalCase, _ => fail!(lit, "unexpected value for rename_all"), }; try_set!(rename_all, val, lit) } else if meta.path.is_ident("type_name") { meta.input.parse::()?; let lit: LitStr = meta.input.parse()?; let name = TypeName { val: lit.value(), span: lit.span(), }; try_set!(type_name, name, lit) } else { fail!(meta.path, "unexpected attribute") } Ok(()) })?; } else if attr.path().is_ident("repr") { let list: Punctuated = attr.parse_args_with(>::parse_terminated)?; if let Some(path) = list.iter().find_map(|f| f.require_path_only().ok()) { try_set!(repr, path.get_ident().unwrap().clone(), list); } } } Ok(SqlxContainerAttributes { transparent: transparent.unwrap_or(false), repr, type_name, rename_all, no_pg_array: no_pg_array.unwrap_or(false), default: default.unwrap_or(false), }) } pub fn parse_child_attributes(input: &[Attribute]) -> syn::Result { let mut rename = None; let mut default = false; let mut try_from = None; let mut flatten = false; let mut skip: bool = false; let mut json = None; for attr in input.iter().filter(|a| a.path().is_ident("sqlx")) { attr.parse_nested_meta(|meta| { if meta.path.is_ident("rename") { meta.input.parse::()?; let val: LitStr = meta.input.parse()?; try_set!(rename, val.value(), val); } else if meta.path.is_ident("try_from") { meta.input.parse::()?; let val: LitStr = meta.input.parse()?; try_set!(try_from, val.parse()?, val); } else if meta.path.is_ident("default") { default = true; } else if meta.path.is_ident("flatten") { flatten = true; } else if meta.path.is_ident("skip") { skip = true; } else if meta.path.is_ident("json") { if meta.input.peek(syn::token::Paren) { let content; parenthesized!(content in meta.input); let literal: Ident = content.parse()?; assert_eq!(literal.to_string(), "nullable", "Unrecognized `json` attribute. Valid values are `json` or `json(nullable)`"); json = Some(JsonAttribute::Nullable); } else { json = Some(JsonAttribute::NonNullable); } } Ok(()) })?; if json.is_some() && flatten { fail!( attr, "Cannot use `json` and `flatten` together on the same field" ); } } Ok(SqlxChildAttributes { rename, default, flatten, try_from, skip, json, }) } pub fn check_transparent_attributes( input: &DeriveInput, field: &Field, ) -> syn::Result { let attributes = parse_container_attributes(&input.attrs)?; assert_attribute!( attributes.rename_all.is_none(), "unexpected #[sqlx(rename_all = ..)]", field ); let ch_attributes = parse_child_attributes(&field.attrs)?; assert_attribute!( ch_attributes.rename.is_none(), "unexpected #[sqlx(rename = ..)]", field ); Ok(attributes) } pub fn check_enum_attributes(input: &DeriveInput) -> syn::Result { let attributes = parse_container_attributes(&input.attrs)?; assert_attribute!( !attributes.transparent, "unexpected #[sqlx(transparent)]", input ); Ok(attributes) } pub fn check_weak_enum_attributes( input: &DeriveInput, variants: &Punctuated, ) -> syn::Result { let attributes = check_enum_attributes(input)?; assert_attribute!(attributes.repr.is_some(), "expected #[repr(..)]", input); assert_attribute!( attributes.rename_all.is_none(), "unexpected #[sqlx(c = ..)]", input ); for variant in variants { let attributes = parse_child_attributes(&variant.attrs)?; assert_attribute!( attributes.rename.is_none(), "unexpected #[sqlx(rename = ..)]", variant ); } Ok(attributes) } pub fn check_strong_enum_attributes( input: &DeriveInput, _variants: &Punctuated, ) -> syn::Result { let attributes = check_enum_attributes(input)?; assert_attribute!(attributes.repr.is_none(), "unexpected #[repr(..)]", input); Ok(attributes) } pub fn check_struct_attributes( input: &DeriveInput, fields: &Punctuated, ) -> syn::Result { let attributes = parse_container_attributes(&input.attrs)?; assert_attribute!( !attributes.transparent, "#[sqlx(transparent)] is only valid for structs with exactly one field", input ); assert_attribute!( attributes.rename_all.is_none(), "unexpected #[sqlx(rename_all = ..)]", input ); assert_attribute!(attributes.repr.is_none(), "unexpected #[repr(..)]", input); for field in fields { let attributes = parse_child_attributes(&field.attrs)?; assert_attribute!( attributes.rename.is_none(), "unexpected #[sqlx(rename = ..)]", field ); } Ok(attributes) } ================================================ FILE: sqlx-macros-core/src/derives/decode.rs ================================================ use super::attributes::{ check_strong_enum_attributes, check_struct_attributes, check_transparent_attributes, check_weak_enum_attributes, parse_child_attributes, parse_container_attributes, }; use super::rename_all; use proc_macro2::TokenStream; use quote::quote; use syn::punctuated::Punctuated; use syn::token::Comma; use syn::{ parse_quote, Arm, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, FieldsNamed, Stmt, TypeParamBound, Variant, }; pub fn expand_derive_decode(input: &DeriveInput) -> syn::Result { let attrs = parse_container_attributes(&input.attrs)?; match &input.data { Data::Struct(DataStruct { fields, .. }) if fields.len() == 1 && (matches!(fields, Fields::Unnamed(_)) || attrs.transparent) => { expand_derive_decode_transparent(input, fields.iter().next().unwrap()) } Data::Enum(DataEnum { variants, .. }) => match attrs.repr { Some(_) => expand_derive_decode_weak_enum(input, variants), None => expand_derive_decode_strong_enum(input, variants), }, Data::Struct(DataStruct { fields: Fields::Named(FieldsNamed { named, .. }), .. }) => expand_derive_decode_struct(input, named), Data::Union(_) => Err(syn::Error::new_spanned(input, "unions are not supported")), Data::Struct(DataStruct { fields: Fields::Unnamed(..), .. }) => Err(syn::Error::new_spanned( input, "tuple structs may only have a single field", )), Data::Struct(DataStruct { fields: Fields::Unit, .. }) => Err(syn::Error::new_spanned( input, "unit structs are not supported", )), } } fn expand_derive_decode_transparent( input: &DeriveInput, field: &Field, ) -> syn::Result { check_transparent_attributes(input, field)?; let ident = &input.ident; let ty = &field.ty; // extract type generics let generics = &input.generics; let (_, ty_generics, _) = generics.split_for_impl(); // add db type for impl generics & where clause let mut generics = generics.clone(); generics .params .insert(0, parse_quote!(DB: ::sqlx::Database)); generics.params.insert(0, parse_quote!('r)); generics .make_where_clause() .predicates .push(parse_quote!(#ty: ::sqlx::decode::Decode<'r, DB>)); let (impl_generics, _, where_clause) = generics.split_for_impl(); let field_ident = if let Some(ident) = &field.ident { quote! { #ident } } else { quote! { 0 } }; let tts = quote!( #[automatically_derived] impl #impl_generics ::sqlx::decode::Decode<'r, DB> for #ident #ty_generics #where_clause { fn decode( value: ::ValueRef<'r>, ) -> ::std::result::Result< Self, ::std::boxed::Box< dyn ::std::error::Error + 'static + ::std::marker::Send + ::std::marker::Sync, >, > { <#ty as ::sqlx::decode::Decode<'r, DB>>::decode(value) .map(|val| Self { #field_ident: val }) } } ); Ok(tts) } fn expand_derive_decode_weak_enum( input: &DeriveInput, variants: &Punctuated, ) -> syn::Result { let attr = check_weak_enum_attributes(input, variants)?; let repr = attr.repr.unwrap(); let ident = &input.ident; let ident_s = ident.to_string(); let arms = variants .iter() .map(|v| { let id = &v.ident; parse_quote! { _ if (#ident::#id as #repr) == value => ::std::result::Result::Ok(#ident::#id), } }) .collect::>(); Ok(quote!( #[automatically_derived] impl<'r, DB: ::sqlx::Database> ::sqlx::decode::Decode<'r, DB> for #ident where #repr: ::sqlx::decode::Decode<'r, DB>, { fn decode( value: ::ValueRef<'r>, ) -> ::std::result::Result< Self, ::std::boxed::Box< dyn ::std::error::Error + 'static + ::std::marker::Send + ::std::marker::Sync, >, > { let value = <#repr as ::sqlx::decode::Decode<'r, DB>>::decode(value)?; match value { #(#arms)* _ => ::std::result::Result::Err(::std::boxed::Box::new(::sqlx::Error::Decode( ::std::format!("invalid value {:?} for enum {}", value, #ident_s).into(), ))) } } } )) } fn expand_derive_decode_strong_enum( input: &DeriveInput, variants: &Punctuated, ) -> syn::Result { let cattr = check_strong_enum_attributes(input, variants)?; let ident = &input.ident; let ident_s = ident.to_string(); let value_arms = variants.iter().map(|v| -> Arm { let id = &v.ident; let attributes = parse_child_attributes(&v.attrs).unwrap(); if let Some(rename) = attributes.rename { parse_quote!(#rename => ::std::result::Result::Ok(#ident :: #id),) } else if let Some(pattern) = cattr.rename_all { let name = rename_all(&id.to_string(), pattern); parse_quote!(#name => ::std::result::Result::Ok(#ident :: #id),) } else { let name = id.to_string(); parse_quote!(#name => ::std::result::Result::Ok(#ident :: #id),) } }); let values = quote! { match value { #(#value_arms)* _ => Err(format!("invalid value {:?} for enum {}", value, #ident_s).into()) } }; let mut tts = TokenStream::new(); if cfg!(feature = "mysql") { tts.extend(quote!( #[automatically_derived] impl<'r> ::sqlx::decode::Decode<'r, ::sqlx::mysql::MySql> for #ident { fn decode( value: ::sqlx::mysql::MySqlValueRef<'r>, ) -> ::std::result::Result< Self, ::std::boxed::Box< dyn ::std::error::Error + 'static + ::std::marker::Send + ::std::marker::Sync, >, > { let value = <&'r ::std::primitive::str as ::sqlx::decode::Decode< 'r, ::sqlx::mysql::MySql, >>::decode(value)?; #values } } )); } if cfg!(feature = "postgres") { tts.extend(quote!( #[automatically_derived] impl<'r> ::sqlx::decode::Decode<'r, ::sqlx::postgres::Postgres> for #ident { fn decode( value: ::sqlx::postgres::PgValueRef<'r>, ) -> ::std::result::Result< Self, ::std::boxed::Box< dyn ::std::error::Error + 'static + ::std::marker::Send + ::std::marker::Sync, >, > { let value = <&'r ::std::primitive::str as ::sqlx::decode::Decode< 'r, ::sqlx::postgres::Postgres, >>::decode(value)?; #values } } )); } if cfg!(feature = "_sqlite") { tts.extend(quote!( #[automatically_derived] impl<'r> ::sqlx::decode::Decode<'r, ::sqlx::sqlite::Sqlite> for #ident { fn decode( value: ::sqlx::sqlite::SqliteValueRef<'r>, ) -> ::std::result::Result< Self, ::std::boxed::Box< dyn ::std::error::Error + 'static + ::std::marker::Send + ::std::marker::Sync, >, > { let value = <&'r ::std::primitive::str as ::sqlx::decode::Decode< 'r, ::sqlx::sqlite::Sqlite, >>::decode(value)?; #values } } )); } Ok(tts) } fn expand_derive_decode_struct( input: &DeriveInput, fields: &Punctuated, ) -> syn::Result { check_struct_attributes(input, fields)?; let mut tts = TokenStream::new(); if cfg!(feature = "postgres") { let ident = &input.ident; let (_, ty_generics, where_clause) = input.generics.split_for_impl(); let mut generics = input.generics.clone(); // add db type for impl generics & where clause for type_param in &mut generics.type_params_mut() { type_param.bounds.extend::<[TypeParamBound; 2]>([ parse_quote!(for<'decode> ::sqlx::decode::Decode<'decode, ::sqlx::Postgres>), parse_quote!(::sqlx::types::Type<::sqlx::Postgres>), ]); } generics.params.push(parse_quote!('r)); let (impl_generics, _, _) = generics.split_for_impl(); let reads = fields.iter().map(|field| -> Stmt { let id = &field.ident; let ty = &field.ty; parse_quote!( let #id = decoder.try_decode::<#ty>()?; ) }); let names = fields.iter().map(|field| &field.ident); tts.extend(quote!( #[automatically_derived] impl #impl_generics ::sqlx::decode::Decode<'r, ::sqlx::Postgres> for #ident #ty_generics #where_clause { fn decode( value: ::sqlx::postgres::PgValueRef<'r>, ) -> ::std::result::Result< Self, ::std::boxed::Box< dyn ::std::error::Error + 'static + ::std::marker::Send + ::std::marker::Sync, >, > { let mut decoder = ::sqlx::postgres::types::PgRecordDecoder::new(value)?; #(#reads)* ::std::result::Result::Ok(#ident { #(#names),* }) } } )); } Ok(tts) } ================================================ FILE: sqlx-macros-core/src/derives/encode.rs ================================================ use super::attributes::{ check_strong_enum_attributes, check_struct_attributes, check_transparent_attributes, check_weak_enum_attributes, parse_child_attributes, parse_container_attributes, }; use super::rename_all; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::punctuated::Punctuated; use syn::token::Comma; use syn::{ parse_quote, Data, DataEnum, DataStruct, DeriveInput, Expr, Field, Fields, FieldsNamed, Lifetime, LifetimeParam, Stmt, TypeParamBound, Variant, }; pub fn expand_derive_encode(input: &DeriveInput) -> syn::Result { let args = parse_container_attributes(&input.attrs)?; match &input.data { Data::Struct(DataStruct { fields, .. }) if fields.len() == 1 && (matches!(fields, Fields::Unnamed(_)) || args.transparent) => { expand_derive_encode_transparent(input, fields.iter().next().unwrap()) } Data::Enum(DataEnum { variants, .. }) => match args.repr { Some(_) => expand_derive_encode_weak_enum(input, variants), None => expand_derive_encode_strong_enum(input, variants), }, Data::Struct(DataStruct { fields: Fields::Named(FieldsNamed { named, .. }), .. }) => expand_derive_encode_struct(input, named), Data::Union(_) => Err(syn::Error::new_spanned(input, "unions are not supported")), Data::Struct(DataStruct { fields: Fields::Unnamed(..), .. }) => Err(syn::Error::new_spanned( input, "tuple structs may only have a single field", )), Data::Struct(DataStruct { fields: Fields::Unit, .. }) => Err(syn::Error::new_spanned( input, "unit structs are not supported", )), } } fn expand_derive_encode_transparent( input: &DeriveInput, field: &Field, ) -> syn::Result { check_transparent_attributes(input, field)?; let ident = &input.ident; let ty = &field.ty; // extract type generics let generics = &input.generics; let (_, ty_generics, _) = generics.split_for_impl(); // add db type for impl generics & where clause let lifetime = Lifetime::new("'q", Span::call_site()); let mut generics = generics.clone(); generics .params .insert(0, LifetimeParam::new(lifetime.clone()).into()); generics .params .insert(0, parse_quote!(DB: ::sqlx::Database)); generics .make_where_clause() .predicates .push(parse_quote!(#ty: ::sqlx::encode::Encode<#lifetime, DB>)); let (impl_generics, _, where_clause) = generics.split_for_impl(); let field_ident = if let Some(ident) = &field.ident { quote! { #ident } } else { quote! { 0 } }; Ok(quote!( #[automatically_derived] impl #impl_generics ::sqlx::encode::Encode<#lifetime, DB> for #ident #ty_generics #where_clause { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> ::std::result::Result<::sqlx::encode::IsNull, ::sqlx::error::BoxDynError> { <#ty as ::sqlx::encode::Encode<#lifetime, DB>>::encode_by_ref(&self.#field_ident, buf) } fn produces(&self) -> Option { <#ty as ::sqlx::encode::Encode<#lifetime, DB>>::produces(&self.#field_ident) } fn size_hint(&self) -> usize { <#ty as ::sqlx::encode::Encode<#lifetime, DB>>::size_hint(&self.#field_ident) } } )) } fn expand_derive_encode_weak_enum( input: &DeriveInput, variants: &Punctuated, ) -> syn::Result { let attr = check_weak_enum_attributes(input, variants)?; let repr = attr.repr.unwrap(); let ident = &input.ident; let mut values = Vec::new(); for v in variants { let id = &v.ident; values.push(quote!(#ident :: #id => (#ident :: #id as #repr),)); } Ok(quote!( #[automatically_derived] impl<'q, DB: ::sqlx::Database> ::sqlx::encode::Encode<'q, DB> for #ident where #repr: ::sqlx::encode::Encode<'q, DB>, { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> ::std::result::Result<::sqlx::encode::IsNull, ::sqlx::error::BoxDynError> { let value = match self { #(#values)* }; <#repr as ::sqlx::encode::Encode>::encode_by_ref(&value, buf) } fn size_hint(&self) -> usize { <#repr as ::sqlx::encode::Encode>::size_hint(&Default::default()) } } )) } fn expand_derive_encode_strong_enum( input: &DeriveInput, variants: &Punctuated, ) -> syn::Result { let cattr = check_strong_enum_attributes(input, variants)?; let ident = &input.ident; let mut value_arms = Vec::new(); for v in variants { let id = &v.ident; let attributes = parse_child_attributes(&v.attrs)?; if let Some(rename) = attributes.rename { value_arms.push(quote!(#ident :: #id => #rename,)); } else if let Some(pattern) = cattr.rename_all { let name = rename_all(&id.to_string(), pattern); value_arms.push(quote!(#ident :: #id => #name,)); } else { let name = id.to_string(); value_arms.push(quote!(#ident :: #id => #name,)); } } Ok(quote!( #[automatically_derived] impl<'q, DB: ::sqlx::Database> ::sqlx::encode::Encode<'q, DB> for #ident where &'q ::std::primitive::str: ::sqlx::encode::Encode<'q, DB>, { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> ::std::result::Result<::sqlx::encode::IsNull, ::sqlx::error::BoxDynError> { let val = match self { #(#value_arms)* }; <&::std::primitive::str as ::sqlx::encode::Encode<'q, DB>>::encode(val, buf) } fn size_hint(&self) -> ::std::primitive::usize { let val = match self { #(#value_arms)* }; <&::std::primitive::str as ::sqlx::encode::Encode<'q, DB>>::size_hint(&val) } } )) } fn expand_derive_encode_struct( input: &DeriveInput, fields: &Punctuated, ) -> syn::Result { check_struct_attributes(input, fields)?; let mut tts = TokenStream::new(); if cfg!(feature = "postgres") { let ident = &input.ident; let column_count = fields.len(); let (_, ty_generics, where_clause) = input.generics.split_for_impl(); let mut generics = input.generics.clone(); // add db type for impl generics & where clause for type_param in &mut generics.type_params_mut() { type_param.bounds.extend::<[TypeParamBound; 2]>([ parse_quote!(for<'encode> ::sqlx::encode::Encode<'encode, ::sqlx::Postgres>), parse_quote!(::sqlx::types::Type<::sqlx::Postgres>), ]); } generics.params.push(parse_quote!('q)); let (impl_generics, _, _) = generics.split_for_impl(); let writes = fields.iter().map(|field| -> Stmt { let id = &field.ident; parse_quote!( encoder.encode(&self. #id)?; ) }); let sizes = fields.iter().map(|field| -> Expr { let id = &field.ident; let ty = &field.ty; parse_quote!( <#ty as ::sqlx::encode::Encode<::sqlx::Postgres>>::size_hint(&self. #id) ) }); tts.extend(quote!( #[automatically_derived] impl #impl_generics ::sqlx::encode::Encode<'_, ::sqlx::Postgres> for #ident #ty_generics #where_clause { fn encode_by_ref( &self, buf: &mut ::sqlx::postgres::PgArgumentBuffer, ) -> ::std::result::Result<::sqlx::encode::IsNull, ::sqlx::error::BoxDynError> { let mut encoder = ::sqlx::postgres::types::PgRecordEncoder::new(buf); #(#writes)* encoder.finish(); ::std::result::Result::Ok(::sqlx::encode::IsNull::No) } fn size_hint(&self) -> ::std::primitive::usize { #column_count * (4 + 4) // oid (int) and length (int) for each column + #(#sizes)+* // sum of the size hints for each column } } )); } Ok(tts) } ================================================ FILE: sqlx-macros-core/src/derives/mod.rs ================================================ mod attributes; mod decode; mod encode; mod row; mod r#type; pub use decode::expand_derive_decode; pub use encode::expand_derive_encode; pub use r#type::expand_derive_type; pub use row::expand_derive_from_row; use self::attributes::RenameAll; use heck::{ToKebabCase, ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use proc_macro2::TokenStream; use syn::DeriveInput; pub fn expand_derive_type_encode_decode(input: &DeriveInput) -> syn::Result { let encode_tts = expand_derive_encode(input)?; let decode_tts = expand_derive_decode(input)?; let type_tts = expand_derive_type(input)?; let combined = TokenStream::from_iter(encode_tts.into_iter().chain(decode_tts).chain(type_tts)); Ok(combined) } pub(crate) fn rename_all(s: &str, pattern: RenameAll) -> String { match pattern { RenameAll::LowerCase => s.to_lowercase(), RenameAll::SnakeCase => s.to_snake_case(), RenameAll::UpperCase => s.to_uppercase(), RenameAll::ScreamingSnakeCase => s.to_shouty_snake_case(), RenameAll::KebabCase => s.to_kebab_case(), RenameAll::CamelCase => s.to_lower_camel_case(), RenameAll::PascalCase => s.to_upper_camel_case(), } } ================================================ FILE: sqlx-macros-core/src/derives/row.rs ================================================ use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ parse_quote, punctuated::Punctuated, token::Comma, Data, DataStruct, DeriveInput, Expr, Field, Fields, FieldsNamed, FieldsUnnamed, Lifetime, Stmt, }; use super::{ attributes::{parse_child_attributes, parse_container_attributes, JsonAttribute}, rename_all, }; pub fn expand_derive_from_row(input: &DeriveInput) -> syn::Result { match &input.data { Data::Struct(DataStruct { fields: Fields::Named(FieldsNamed { named, .. }), .. }) => expand_derive_from_row_struct(input, named), Data::Struct(DataStruct { fields: Fields::Unnamed(FieldsUnnamed { unnamed, .. }), .. }) => expand_derive_from_row_struct_unnamed(input, unnamed), Data::Struct(DataStruct { fields: Fields::Unit, .. }) => Err(syn::Error::new_spanned( input, "unit structs are not supported", )), Data::Enum(_) => Err(syn::Error::new_spanned(input, "enums are not supported")), Data::Union(_) => Err(syn::Error::new_spanned(input, "unions are not supported")), } } fn expand_derive_from_row_struct( input: &DeriveInput, fields: &Punctuated, ) -> syn::Result { let ident = &input.ident; let generics = &input.generics; let (lifetime, provided) = generics .lifetimes() .next() .map(|def| (def.lifetime.clone(), false)) .unwrap_or_else(|| (Lifetime::new("'a", Span::call_site()), true)); let (_, ty_generics, _) = generics.split_for_impl(); let mut generics = generics.clone(); generics.params.insert(0, parse_quote!(R: ::sqlx::Row)); if provided { generics.params.insert(0, parse_quote!(#lifetime)); } let predicates = &mut generics.make_where_clause().predicates; predicates.push(parse_quote!(&#lifetime ::std::primitive::str: ::sqlx::ColumnIndex)); let container_attributes = parse_container_attributes(&input.attrs)?; let default_instance: Option = if container_attributes.default { predicates.push(parse_quote!(#ident: ::std::default::Default)); Some(parse_quote!( let __default = #ident::default(); )) } else { None }; let reads: Vec = fields .iter() .filter_map(|field| -> Option { let id = &field.ident.as_ref()?; let attributes = parse_child_attributes(&field.attrs).unwrap(); let ty = &field.ty; if attributes.skip { return Some(parse_quote!( let #id: #ty = Default::default(); )); } let id_s = if let Some(s) = attributes.rename { s } else { let s = id.to_string().trim_start_matches("r#").to_owned(); match container_attributes.rename_all { Some(pattern) => rename_all(&s, pattern), None => s } }; let expr: Expr = match (attributes.flatten, attributes.try_from, attributes.json) { // (false, None, None) => { predicates .push(parse_quote!(#ty: ::sqlx::decode::Decode<#lifetime, R::Database>)); predicates.push(parse_quote!(#ty: ::sqlx::types::Type)); parse_quote!(__row.try_get(#id_s)) } // Flatten (true, None, None) => { predicates.push(parse_quote!(#ty: ::sqlx::FromRow<#lifetime, R>)); parse_quote!(<#ty as ::sqlx::FromRow<#lifetime, R>>::from_row(__row)) } // Flatten + Try from (true, Some(try_from), None) => { predicates.push(parse_quote!(#try_from: ::sqlx::FromRow<#lifetime, R>)); parse_quote!( <#try_from as ::sqlx::FromRow<#lifetime, R>>::from_row(__row) .and_then(|v| { <#ty as ::std::convert::TryFrom::<#try_from>>::try_from(v) .map_err(|e| { // Triggers a lint warning if `TryFrom::Err = Infallible` #[allow(unreachable_code)] ::sqlx::Error::ColumnDecode { index: #id_s.to_string(), source: sqlx::__spec_error!(e), } }) }) ) } // Flatten + Json (true, _, Some(_)) => { panic!("Cannot use both flatten and json") } // Try from (false, Some(try_from), None) => { predicates .push(parse_quote!(#try_from: ::sqlx::decode::Decode<#lifetime, R::Database>)); predicates.push(parse_quote!(#try_from: ::sqlx::types::Type)); parse_quote!( __row.try_get(#id_s) .and_then(|v| { <#ty as ::std::convert::TryFrom::<#try_from>>::try_from(v) .map_err(|e| { // Triggers a lint warning if `TryFrom::Err = Infallible` #[allow(unreachable_code)] ::sqlx::Error::ColumnDecode { index: #id_s.to_string(), source: sqlx::__spec_error!(e), } }) }) ) } // Try from + Json mandatory (false, Some(try_from), Some(JsonAttribute::NonNullable)) => { predicates .push(parse_quote!(::sqlx::types::Json<#try_from>: ::sqlx::decode::Decode<#lifetime, R::Database>)); predicates.push(parse_quote!(::sqlx::types::Json<#try_from>: ::sqlx::types::Type)); parse_quote!( __row.try_get::<::sqlx::types::Json<_>, _>(#id_s) .and_then(|v| { <#ty as ::std::convert::TryFrom::<#try_from>>::try_from(v.0) .map_err(|e| { // Triggers a lint warning if `TryFrom::Err = Infallible` #[allow(unreachable_code)] ::sqlx::Error::ColumnDecode { index: #id_s.to_string(), source: sqlx::__spec_error!(e), } }) }) ) }, // Try from + Json nullable (false, Some(_), Some(JsonAttribute::Nullable)) => { panic!("Cannot use both try from and json nullable") }, // Json (false, None, Some(JsonAttribute::NonNullable)) => { predicates .push(parse_quote!(::sqlx::types::Json<#ty>: ::sqlx::decode::Decode<#lifetime, R::Database>)); predicates.push(parse_quote!(::sqlx::types::Json<#ty>: ::sqlx::types::Type)); parse_quote!(__row.try_get::<::sqlx::types::Json<_>, _>(#id_s).map(|x| x.0)) }, (false, None, Some(JsonAttribute::Nullable)) => { predicates .push(parse_quote!(::core::option::Option<::sqlx::types::Json<#ty>>: ::sqlx::decode::Decode<#lifetime, R::Database>)); predicates.push(parse_quote!(::core::option::Option<::sqlx::types::Json<#ty>>: ::sqlx::types::Type)); parse_quote!(__row.try_get::<::core::option::Option<::sqlx::types::Json<_>>, _>(#id_s).map(|x| x.and_then(|y| y.0))) }, }; if attributes.default { Some(parse_quote!( let #id: #ty = #expr.or_else(|e| match e { ::sqlx::Error::ColumnNotFound(_) => { ::std::result::Result::Ok(Default::default()) }, e => ::std::result::Result::Err(e) })?; )) } else if container_attributes.default { Some(parse_quote!( let #id: #ty = #expr.or_else(|e| match e { ::sqlx::Error::ColumnNotFound(_) => { ::std::result::Result::Ok(__default.#id) }, e => ::std::result::Result::Err(e) })?; )) } else { Some(parse_quote!( let #id: #ty = #expr?; )) } }) .collect(); let (impl_generics, _, where_clause) = generics.split_for_impl(); let names = fields.iter().map(|field| &field.ident); Ok(quote!( #[automatically_derived] impl #impl_generics ::sqlx::FromRow<#lifetime, R> for #ident #ty_generics #where_clause { fn from_row(__row: &#lifetime R) -> ::sqlx::Result { #default_instance #(#reads)* ::std::result::Result::Ok(#ident { #(#names),* }) } } )) } fn expand_derive_from_row_struct_unnamed( input: &DeriveInput, fields: &Punctuated, ) -> syn::Result { let ident = &input.ident; let generics = &input.generics; let (lifetime, provided) = generics .lifetimes() .next() .map(|def| (def.lifetime.clone(), false)) .unwrap_or_else(|| (Lifetime::new("'a", Span::call_site()), true)); let (_, ty_generics, _) = generics.split_for_impl(); let mut generics = generics.clone(); generics.params.insert(0, parse_quote!(R: ::sqlx::Row)); if provided { generics.params.insert(0, parse_quote!(#lifetime)); } let predicates = &mut generics.make_where_clause().predicates; predicates.push(parse_quote!( ::std::primitive::usize: ::sqlx::ColumnIndex )); for field in fields { let ty = &field.ty; predicates.push(parse_quote!(#ty: ::sqlx::decode::Decode<#lifetime, R::Database>)); predicates.push(parse_quote!(#ty: ::sqlx::types::Type)); } let (impl_generics, _, where_clause) = generics.split_for_impl(); let gets = fields .iter() .enumerate() .map(|(idx, _)| quote!(row.try_get(#idx)?)); Ok(quote!( #[automatically_derived] impl #impl_generics ::sqlx::FromRow<#lifetime, R> for #ident #ty_generics #where_clause { fn from_row(row: &#lifetime R) -> ::sqlx::Result { ::std::result::Result::Ok(#ident ( #(#gets),* )) } } )) } ================================================ FILE: sqlx-macros-core/src/derives/type.rs ================================================ use super::attributes::{ check_strong_enum_attributes, check_struct_attributes, check_transparent_attributes, check_weak_enum_attributes, parse_container_attributes, TypeName, }; use proc_macro2::{Ident, TokenStream}; use quote::{quote, quote_spanned}; use syn::punctuated::Punctuated; use syn::token::Comma; use syn::{ parse_quote, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, FieldsNamed, Variant, }; pub fn expand_derive_type(input: &DeriveInput) -> syn::Result { let attrs = parse_container_attributes(&input.attrs)?; match &input.data { // Newtype structs: // struct Foo(i32); // struct Foo { field: i32 }; Data::Struct(DataStruct { fields, .. }) if fields.len() == 1 && (matches!(fields, Fields::Unnamed(_)) || attrs.transparent) => { expand_derive_has_sql_type_transparent(input, fields.iter().next().unwrap()) } // Record types // struct Foo { foo: i32, bar: String } Data::Struct(DataStruct { fields: Fields::Named(FieldsNamed { named, .. }), .. }) => expand_derive_has_sql_type_struct(input, named), Data::Struct(DataStruct { fields: Fields::Unnamed(..), .. }) => Err(syn::Error::new_spanned( input, "tuple structs may only have a single field", )), Data::Struct(DataStruct { fields: Fields::Unit, .. }) => Err(syn::Error::new_spanned( input, "unit structs are not supported", )), Data::Enum(DataEnum { variants, .. }) => match attrs.repr { // Enums that encode to/from integers (weak enums) Some(_) => expand_derive_has_sql_type_weak_enum(input, variants), // Enums that decode to/from strings (strong enums) None => expand_derive_has_sql_type_strong_enum(input, variants), }, Data::Union(_) => Err(syn::Error::new_spanned(input, "unions are not supported")), } } fn expand_derive_has_sql_type_transparent( input: &DeriveInput, field: &Field, ) -> syn::Result { let attr = check_transparent_attributes(input, field)?; let ident = &input.ident; let ty = &field.ty; let generics = &input.generics; let (_, ty_generics, _) = generics.split_for_impl(); if attr.transparent { let mut generics = generics.clone(); let mut array_generics = generics.clone(); generics .params .insert(0, parse_quote!(DB: ::sqlx::Database)); generics .make_where_clause() .predicates .push(parse_quote!(#ty: ::sqlx::Type)); let (impl_generics, _, where_clause) = generics.split_for_impl(); array_generics .make_where_clause() .predicates .push(parse_quote!(#ty: ::sqlx::postgres::PgHasArrayType)); let (array_impl_generics, _, array_where_clause) = array_generics.split_for_impl(); let mut tokens = quote!( #[automatically_derived] impl #impl_generics ::sqlx::Type< DB > for #ident #ty_generics #where_clause { fn type_info() -> DB::TypeInfo { <#ty as ::sqlx::Type>::type_info() } fn compatible(ty: &DB::TypeInfo) -> ::std::primitive::bool { <#ty as ::sqlx::Type>::compatible(ty) } } ); if cfg!(feature = "postgres") && !attr.no_pg_array { tokens.extend(quote!( #[automatically_derived] impl #array_impl_generics ::sqlx::postgres::PgHasArrayType for #ident #ty_generics #array_where_clause { fn array_type_info() -> ::sqlx::postgres::PgTypeInfo { <#ty as ::sqlx::postgres::PgHasArrayType>::array_type_info() } } )); } return Ok(tokens); } let mut tts = TokenStream::new(); if cfg!(feature = "postgres") { let ty_name = type_name(ident, attr.type_name.as_ref()); tts.extend(quote!( #[automatically_derived] impl ::sqlx::Type<::sqlx::postgres::Postgres> for #ident #ty_generics { fn type_info() -> ::sqlx::postgres::PgTypeInfo { ::sqlx::postgres::PgTypeInfo::with_name(#ty_name) } } )); if !attr.no_pg_array { tts.extend(quote!( #[automatically_derived] impl ::sqlx::postgres::PgHasArrayType for #ident #ty_generics { fn array_type_info() -> ::sqlx::postgres::PgTypeInfo { ::sqlx::postgres::PgTypeInfo::array_of(#ty_name) } } )); } } Ok(tts) } fn expand_derive_has_sql_type_weak_enum( input: &DeriveInput, variants: &Punctuated, ) -> syn::Result { let attrs = check_weak_enum_attributes(input, variants)?; let repr = attrs.repr.unwrap(); let ident = &input.ident; let mut ts = quote!( #[automatically_derived] impl ::sqlx::Type for #ident where #repr: ::sqlx::Type, { fn type_info() -> DB::TypeInfo { <#repr as ::sqlx::Type>::type_info() } fn compatible(ty: &DB::TypeInfo) -> bool { <#repr as ::sqlx::Type>::compatible(ty) } } ); if cfg!(feature = "postgres") && !attrs.no_pg_array { ts.extend(quote!( #[automatically_derived] impl ::sqlx::postgres::PgHasArrayType for #ident { fn array_type_info() -> ::sqlx::postgres::PgTypeInfo { <#repr as ::sqlx::postgres::PgHasArrayType>::array_type_info() } } )); } Ok(ts) } fn expand_derive_has_sql_type_strong_enum( input: &DeriveInput, variants: &Punctuated, ) -> syn::Result { let attributes = check_strong_enum_attributes(input, variants)?; let ident = &input.ident; let mut tts = TokenStream::new(); if cfg!(feature = "mysql") { tts.extend(quote!( #[automatically_derived] impl ::sqlx::Type<::sqlx::MySql> for #ident { fn type_info() -> ::sqlx::mysql::MySqlTypeInfo { ::sqlx::mysql::MySqlTypeInfo::__enum() } } )); } if cfg!(feature = "postgres") { let ty_name = type_name(ident, attributes.type_name.as_ref()); tts.extend(quote!( #[automatically_derived] impl ::sqlx::Type<::sqlx::Postgres> for #ident { fn type_info() -> ::sqlx::postgres::PgTypeInfo { ::sqlx::postgres::PgTypeInfo::with_name(#ty_name) } } )); if !attributes.no_pg_array { tts.extend(quote!( #[automatically_derived] impl ::sqlx::postgres::PgHasArrayType for #ident { fn array_type_info() -> ::sqlx::postgres::PgTypeInfo { ::sqlx::postgres::PgTypeInfo::array_of(#ty_name) } } )); } } if cfg!(feature = "_sqlite") { tts.extend(quote!( #[automatically_derived] impl sqlx::Type<::sqlx::Sqlite> for #ident { fn type_info() -> ::sqlx::sqlite::SqliteTypeInfo { <::std::primitive::str as ::sqlx::Type>::type_info() } fn compatible(ty: &::sqlx::sqlite::SqliteTypeInfo) -> ::std::primitive::bool { <&::std::primitive::str as ::sqlx::types::Type>::compatible(ty) } } )); } Ok(tts) } fn expand_derive_has_sql_type_struct( input: &DeriveInput, fields: &Punctuated, ) -> syn::Result { let attributes = check_struct_attributes(input, fields)?; let ident = &input.ident; let mut tts = TokenStream::new(); if cfg!(feature = "postgres") { let ty_name = type_name(ident, attributes.type_name.as_ref()); tts.extend(quote!( #[automatically_derived] impl ::sqlx::Type<::sqlx::Postgres> for #ident { fn type_info() -> ::sqlx::postgres::PgTypeInfo { ::sqlx::postgres::PgTypeInfo::with_name(#ty_name) } } )); if !attributes.no_pg_array { tts.extend(quote!( #[automatically_derived] impl ::sqlx::postgres::PgHasArrayType for #ident { fn array_type_info() -> ::sqlx::postgres::PgTypeInfo { ::sqlx::postgres::PgTypeInfo::array_of(#ty_name) } } )); } } Ok(tts) } fn type_name(ident: &Ident, explicit_name: Option<&TypeName>) -> TokenStream { explicit_name.map(|tn| tn.get()).unwrap_or_else(|| { let s = ident.to_string(); quote_spanned!(ident.span()=> #s) }) } ================================================ FILE: sqlx-macros-core/src/lib.rs ================================================ //! Support crate for SQLx's proc macros. //! //! ### Note: Semver Exempt API //! The API of this crate is not meant for general use and does *not* follow Semantic Versioning. //! The only crate that follows Semantic Versioning in the project is the `sqlx` crate itself. //! If you are building a custom SQLx driver, you should pin an exact version of this and //! `sqlx-core` to avoid breakages: //! //! ```toml //! sqlx-core = "=0.6.2" //! sqlx-macros-core = "=0.6.2" //! ``` //! //! And then make releases in lockstep with `sqlx-core`. We recommend all driver crates, in-tree //! or otherwise, use the same version numbers as `sqlx-core` to avoid confusion. #![cfg_attr( any(sqlx_macros_unstable, procmacro2_semver_exempt), feature(track_path) )] use cfg_if::cfg_if; use std::path::PathBuf; #[cfg(feature = "macros")] use crate::query::QueryDriver; pub type Error = Box; pub type Result = std::result::Result; mod common; pub mod database; #[cfg(feature = "derive")] pub mod derives; #[cfg(feature = "macros")] pub mod query; #[cfg(feature = "macros")] // The compiler gives misleading help messages about `#[cfg(test)]` when this is just named `test`. pub mod test_attr; #[cfg(feature = "migrate")] pub mod migrate; #[cfg(feature = "macros")] pub const FOSS_DRIVERS: &[QueryDriver] = &[ #[cfg(feature = "mysql")] QueryDriver::new::(), #[cfg(feature = "postgres")] QueryDriver::new::(), #[cfg(feature = "_sqlite")] QueryDriver::new::(), ]; pub fn block_on(f: F) -> F::Output where F: std::future::Future, { cfg_if! { if #[cfg(feature = "_rt-async-global-executor")] { sqlx_core::rt::test_block_on(f) } else if #[cfg(feature = "_rt-async-std")] { async_std::task::block_on(f) } else if #[cfg(feature = "_rt-smol")] { sqlx_core::rt::test_block_on(f) } else if #[cfg(feature = "_rt-tokio")] { use std::sync::LazyLock; use tokio::runtime::{self, Runtime}; // We need a single, persistent Tokio runtime since we're caching connections, // otherwise we'll get "IO driver has terminated" errors. static TOKIO_RT: LazyLock = LazyLock::new(|| { runtime::Builder::new_current_thread() .enable_all() .build() .expect("failed to start Tokio runtime") }); TOKIO_RT.block_on(f) } else { sqlx_core::rt::missing_rt(f) } } } pub fn env(var: &str) -> Result { env_opt(var)? .ok_or_else(|| format!("env var {var:?} must be set to use the query macros").into()) } #[allow(clippy::disallowed_methods)] pub fn env_opt(var: &str) -> Result> { use std::env::VarError; #[cfg(any(sqlx_macros_unstable, procmacro2_semver_exempt))] let res: Result = proc_macro::tracked_env::var(var); #[cfg(not(any(sqlx_macros_unstable, procmacro2_semver_exempt)))] let res: Result = std::env::var(var); match res { Ok(val) => Ok(Some(val)), Err(VarError::NotPresent) => Ok(None), Err(VarError::NotUnicode(_)) => Err(format!("env var {var:?} is not valid UTF-8").into()), } } pub fn manifest_dir() -> Result { Ok(env("CARGO_MANIFEST_DIR")?.into()) } ================================================ FILE: sqlx-macros-core/src/migrate.rs ================================================ #[cfg(any(sqlx_macros_unstable, procmacro2_semver_exempt))] extern crate proc_macro; use std::path::{Path, PathBuf}; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens, TokenStreamExt}; use sqlx_core::config::Config; use sqlx_core::migrate::{Migration, MigrationType}; use syn::LitStr; pub const DEFAULT_PATH: &str = "./migrations"; pub struct QuoteMigrationType(MigrationType); impl ToTokens for QuoteMigrationType { fn to_tokens(&self, tokens: &mut TokenStream) { let ts = match self.0 { MigrationType::Simple => quote! { ::sqlx::migrate::MigrationType::Simple }, MigrationType::ReversibleUp => quote! { ::sqlx::migrate::MigrationType::ReversibleUp }, MigrationType::ReversibleDown => { quote! { ::sqlx::migrate::MigrationType::ReversibleDown } } }; tokens.append_all(ts); } } struct QuoteMigration { migration: Migration, path: PathBuf, } impl ToTokens for QuoteMigration { fn to_tokens(&self, tokens: &mut TokenStream) { let Migration { version, description, migration_type, checksum, no_tx, .. } = &self.migration; let migration_type = QuoteMigrationType(*migration_type); let sql = self .path .canonicalize() .map_err(|e| { format!( "error canonicalizing migration path {}: {e}", self.path.display() ) }) .and_then(|path| { let path_str = path.to_str().ok_or_else(|| { format!( "migration path cannot be represented as a string: {}", self.path.display() ) })?; // this tells the compiler to watch this path for changes Ok(quote! { include_str!(#path_str) }) }) .unwrap_or_else(|e| quote! { compile_error!(#e) }); let ts = quote! { ::sqlx::migrate::Migration { version: #version, description: ::std::borrow::Cow::Borrowed(#description), migration_type: #migration_type, sql: ::sqlx::SqlStr::from_static(#sql), no_tx: #no_tx, checksum: ::std::borrow::Cow::Borrowed(&[ #(#checksum),* ]), } }; tokens.append_all(ts); } } pub fn default_path(config: &Config) -> &str { config .migrate .migrations_dir .as_deref() .unwrap_or(DEFAULT_PATH) } pub fn expand(path_arg: Option) -> crate::Result { let config = Config::try_from_crate_or_default()?; let path = match path_arg { Some(path_arg) => crate::common::resolve_path(path_arg.value(), path_arg.span())?, None => { crate::common::resolve_path(default_path(&config), Span::call_site()) }?, }; expand_with_path(&config, &path) } pub fn expand_with_path(config: &Config, path: &Path) -> crate::Result { let path = path.canonicalize().map_err(|e| { format!( "error canonicalizing migration directory {}: {e}", path.display() ) })?; let resolve_config = config.migrate.to_resolve_config(); // Use the same code path to resolve migrations at compile time and runtime. let migrations = sqlx_core::migrate::resolve_blocking_with_config(&path, &resolve_config)? .into_iter() .map(|(migration, path)| QuoteMigration { migration, path }); let table_name = config.migrate.table_name(); let create_schemas = config.migrate.create_schemas.iter().map(|schema_name| { quote! { ::std::borrow::Cow::Borrowed(#schema_name) } }); #[cfg(any(sqlx_macros_unstable, procmacro2_semver_exempt))] { let path = path.to_str().ok_or_else(|| { format!( "migration directory path cannot be represented as a string: {:?}", path ) })?; proc_macro::tracked_path::path(path); } Ok(quote! { ::sqlx::migrate::Migrator { migrations: ::std::borrow::Cow::Borrowed(const {&[ #(#migrations),* ]}), create_schemas: ::std::borrow::Cow::Borrowed(&[#(#create_schemas),*]), table_name: ::std::borrow::Cow::Borrowed(#table_name), ..::sqlx::migrate::Migrator::DEFAULT } }) } ================================================ FILE: sqlx-macros-core/src/query/args.rs ================================================ use crate::database::DatabaseExt; use crate::query::{QueryMacroInput, Warnings}; use either::Either; use proc_macro2::TokenStream; use quote::{format_ident, quote, quote_spanned}; use sqlx_core::config::Config; use sqlx_core::describe::Describe; use sqlx_core::type_checking; use sqlx_core::type_info::TypeInfo; use syn::spanned::Spanned; use syn::{Expr, ExprCast, ExprGroup, Type}; /// Returns a tokenstream which typechecks the arguments passed to the macro /// and binds them to `DB::Arguments` with the ident `query_args`. pub fn quote_args( input: &QueryMacroInput, config: &Config, warnings: &mut Warnings, info: &Describe, ) -> crate::Result { let db_path = DB::db_path(); if input.arg_exprs.is_empty() { return Ok(quote! { let query_args = ::core::result::Result::<_, ::sqlx::error::BoxDynError>::Ok(<#db_path as ::sqlx::database::Database>::Arguments::default()); }); } let arg_names = (0..input.arg_exprs.len()) .map(|i| format_ident!("arg{}", i)) .collect::>(); let arg_name = &arg_names; let arg_expr = input.arg_exprs.iter().cloned().map(strip_wildcard); let arg_bindings = quote! { #(let #arg_name = &(#arg_expr);)* }; let args_check = match info.parameters() { None | Some(Either::Right(_)) => { // all we can do is check arity which we did TokenStream::new() } Some(Either::Left(_)) if !input.checked => { // this is an `*_unchecked!()` macro invocation TokenStream::new() } Some(Either::Left(params)) => { params .iter() .zip(arg_names.iter().zip(&input.arg_exprs)) .enumerate() .map(|(i, (param_ty, (name, expr)))| -> crate::Result<_> { if get_type_override(expr).is_some() { // cast will fail to compile if the type does not match // and we strip casts to wildcard return Ok(quote!()); } let param_ty = get_param_type::(param_ty, config, warnings, i)?; Ok(quote_spanned!(expr.span() => // this shouldn't actually run #[allow(clippy::missing_panics_doc, clippy::unreachable)] if false { use ::sqlx::ty_match::{WrapSameExt as _, MatchBorrowExt as _}; // evaluate the expression only once in case it contains moves let expr = ::sqlx::ty_match::dupe_value(#name); // if `expr` is `Option`, get `Option<$ty>`, otherwise `$ty` let ty_check = ::sqlx::ty_match::WrapSame::<#param_ty, _>::new(&expr).wrap_same(); // if `expr` is `&str`, convert `String` to `&str` let (mut _ty_check, match_borrow) = ::sqlx::ty_match::MatchBorrow::new(ty_check, &expr); _ty_check = match_borrow.match_borrow(); // this causes move-analysis to effectively ignore this block ::std::unreachable!(); } )) }) .collect::>()? } }; let args_count = input.arg_exprs.len(); Ok(quote! { #arg_bindings #args_check let mut query_args = <#db_path as ::sqlx::database::Database>::Arguments::default(); query_args.reserve( #args_count, 0 #(+ ::sqlx::encode::Encode::<#db_path>::size_hint(#arg_name))* ); let query_args = ::core::result::Result::<_, ::sqlx::error::BoxDynError>::Ok(query_args) #(.and_then(move |mut query_args| query_args.add(#arg_name).map(move |()| query_args) ))*; }) } fn get_param_type( param_ty: &DB::TypeInfo, config: &Config, warnings: &mut Warnings, i: usize, ) -> crate::Result { if let Some(type_override) = config.macros.type_override(param_ty.name()) { return Ok(type_override.parse()?); } let err = match DB::param_type_for_id(param_ty, &config.macros.preferred_crates) { Ok(t) => return Ok(t.parse()?), Err(e) => e, }; let param_num = i + 1; let message = match err { type_checking::Error::NoMappingFound => { if let Some(feature_gate) = DB::get_feature_gate(param_ty) { format!( "optional sqlx feature `{feature_gate}` required for type {param_ty} of param #{param_num}", ) } else { format!( "no built-in mapping for type {param_ty} of param #{param_num}; \ a type override may be required, see documentation for details" ) } } type_checking::Error::DateTimeCrateFeatureNotEnabled => { let feature_gate = config .macros .preferred_crates .date_time .crate_name() .expect("BUG: got feature-not-enabled error for DateTimeCrate::Inferred"); format!( "SQLx feature `{feature_gate}` required for type {param_ty} of param #{param_num} \ (configured by `macros.preferred-crates.date-time` in sqlx.toml)", ) } type_checking::Error::NumericCrateFeatureNotEnabled => { let feature_gate = config .macros .preferred_crates .numeric .crate_name() .expect("BUG: got feature-not-enabled error for NumericCrate::Inferred"); format!( "SQLx feature `{feature_gate}` required for type {param_ty} of param #{param_num} \ (configured by `macros.preferred-crates.numeric` in sqlx.toml)", ) } type_checking::Error::AmbiguousDateTimeType { fallback } => { warnings.ambiguous_datetime = true; return Ok(fallback.parse()?); } type_checking::Error::AmbiguousNumericType { fallback } => { warnings.ambiguous_numeric = true; return Ok(fallback.parse()?); } }; Err(message.into()) } fn get_type_override(expr: &Expr) -> Option<&Type> { match expr { Expr::Group(group) => get_type_override(&group.expr), Expr::Cast(cast) => Some(&cast.ty), _ => None, } } fn strip_wildcard(expr: Expr) -> Expr { match expr { Expr::Group(ExprGroup { attrs, group_token, expr, }) => Expr::Group(ExprGroup { attrs, group_token, expr: Box::new(strip_wildcard(*expr)), }), // we want to retain casts if they semantically matter Expr::Cast(ExprCast { attrs, expr, as_token, ty, }) => match *ty { // cast to wildcard `_` will produce weird errors; we interpret it as taking the value as-is Type::Infer(_) => *expr, _ => Expr::Cast(ExprCast { attrs, expr, as_token, ty, }), }, _ => expr, } } ================================================ FILE: sqlx-macros-core/src/query/cache.rs ================================================ use std::path::{Path, PathBuf}; use std::sync::Mutex; use std::time::SystemTime; /// A cached value derived from one or more files, which is automatically invalidated /// if the modified-time of any watched file changes. pub struct MtimeCache { inner: Mutex>>, } pub struct MtimeCacheBuilder { file_mtimes: Vec<(PathBuf, Option)>, } struct MtimeCacheInner { builder: MtimeCacheBuilder, cached: T, } impl MtimeCache { pub fn new() -> Self { MtimeCache { inner: Mutex::new(None), } } /// Get the cached value, or (re)initialize it if it does not exist or a file's mtime has changed. pub fn get_or_try_init( &self, init: impl FnOnce(&mut MtimeCacheBuilder) -> Result, ) -> Result { let mut inner = self.inner.lock().unwrap_or_else(|e| { // Reset the cache on-panic. let mut locked = e.into_inner(); *locked = None; locked }); if let Some(inner) = &*inner { if !inner.builder.any_modified() { return Ok(inner.cached.clone()); } } let mut builder = MtimeCacheBuilder::new(); let value = init(&mut builder)?; *inner = Some(MtimeCacheInner { builder, cached: value.clone(), }); Ok(value) } } impl MtimeCacheBuilder { fn new() -> Self { MtimeCacheBuilder { file_mtimes: Vec::new(), } } /// Add a file path to watch. /// /// The cached value will be automatically invalidated if the modified-time of the file changes, /// or if the file does not exist but is created sometime after this call. pub fn add_path(&mut self, path: PathBuf) { let mtime = get_mtime(&path); #[cfg(any(sqlx_macros_unstable, procmacro2_semver_exempt))] { proc_macro::tracked_path::path(&path); } self.file_mtimes.push((path, mtime)); } fn any_modified(&self) -> bool { for (path, expected_mtime) in &self.file_mtimes { let actual_mtime = get_mtime(path); if expected_mtime != &actual_mtime { return true; } } false } } fn get_mtime(path: &Path) -> Option { std::fs::metadata(path) .and_then(|metadata| metadata.modified()) .ok() } ================================================ FILE: sqlx-macros-core/src/query/data.rs ================================================ use std::fmt::{Debug, Display, Formatter}; use std::fs; use std::io::Write as _; use std::marker::PhantomData; use std::path::{Path, PathBuf}; use std::sync::{Arc, LazyLock, Mutex}; use serde::{Serialize, Serializer}; use sqlx_core::database::Database; use sqlx_core::describe::Describe; use sqlx_core::HashMap; use crate::database::DatabaseExt; use crate::query::cache::MtimeCache; #[derive(serde::Serialize)] #[serde(bound(serialize = "Describe: serde::Serialize"))] #[derive(Debug)] pub struct QueryData { db_name: SerializeDbName, #[allow(dead_code)] pub(super) query: String, pub(super) describe: Describe, pub(super) hash: String, } impl QueryData { pub fn from_describe(query: &str, describe: Describe) -> Self { QueryData { db_name: SerializeDbName::default(), query: query.into(), describe, hash: hash_string(query), } } } struct SerializeDbName(PhantomData); impl Default for SerializeDbName { fn default() -> Self { SerializeDbName(PhantomData) } } impl Debug for SerializeDbName { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_tuple("SerializeDbName").field(&DB::NAME).finish() } } impl Display for SerializeDbName { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.pad(DB::NAME) } } impl Serialize for SerializeDbName { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(DB::NAME) } } static OFFLINE_DATA_CACHE: LazyLock>>>> = LazyLock::new(Default::default); /// Offline query data #[derive(Clone, serde::Deserialize)] pub struct DynQueryData { pub db_name: String, pub query: String, pub describe: serde_json::Value, pub hash: String, } impl DynQueryData { /// Loads a query given the path to its "query-.json" file. Subsequent calls for the same /// path are retrieved from an in-memory cache. pub fn from_data_file(path: &Path, query: &str) -> crate::Result { let cache = OFFLINE_DATA_CACHE .lock() // Just reset the cache on error .unwrap_or_else(|poison_err| { let mut guard = poison_err.into_inner(); *guard = Default::default(); guard }) .entry_ref(path) .or_insert_with(|| Arc::new(MtimeCache::new())) .clone(); cache.get_or_try_init(|builder| { builder.add_path(path.into()); let offline_data_contents = fs::read_to_string(path).map_err(|e| { format!("failed to read saved query path {}: {}", path.display(), e) })?; let dyn_data: DynQueryData = serde_json::from_str(&offline_data_contents)?; if query != dyn_data.query { return Err("hash collision for saved query data".into()); } Ok(dyn_data) }) } } impl QueryData where Describe: serde::Serialize + serde::de::DeserializeOwned, { pub fn from_dyn_data(dyn_data: DynQueryData) -> crate::Result { assert!(!dyn_data.db_name.is_empty()); assert!(!dyn_data.hash.is_empty()); if DB::NAME == dyn_data.db_name { let describe: Describe = serde_json::from_value(dyn_data.describe)?; Ok(QueryData { db_name: SerializeDbName::default(), query: dyn_data.query, describe, hash: dyn_data.hash, }) } else { Err(format!( "expected query data for {}, got data for {}", DB::NAME, dyn_data.db_name ) .into()) } } pub(super) fn save_in(&self, dir: &Path) -> crate::Result<()> { use std::io::ErrorKind; let path = dir.join(format!("query-{}.json", self.hash)); if let Err(err) = fs::remove_file(&path) { match err.kind() { ErrorKind::NotFound | ErrorKind::PermissionDenied => (), ErrorKind::NotADirectory => { return Err(format!( "sqlx offline path exists, but is not a directory: {dir:?}" ) .into()); } _ => return Err(format!("failed to delete {path:?}: {err:?}").into()), } } // Prevent tearing from concurrent invocations possibly trying to write the same file // by using the existence of the file itself as a mutex. // // By deleting the file first and then using `.create_new(true)`, // we guarantee that this only succeeds if another invocation hasn't concurrently // re-created the file. let mut file = match fs::OpenOptions::new() .write(true) .create_new(true) .open(&path) { Ok(file) => file, Err(err) => { return match err.kind() { // We overlapped with a concurrent invocation and the other one succeeded. ErrorKind::AlreadyExists => Ok(()), ErrorKind::NotFound => { Err(format!("sqlx offline path does not exist: {dir:?}").into()) } ErrorKind::NotADirectory => Err(format!( "sqlx offline path exists, but is not a directory: {dir:?}" ) .into()), _ => Err(format!("failed to exclusively create {path:?}: {err:?}").into()), }; } }; // From a quick survey of the files generated by `examples/postgres/axum-social-with-tests`, // which are generally in the 1-2 KiB range, this seems like a safe bet to avoid // lots of reallocations without using too much memory. // // As of writing, `serde_json::to_vec_pretty()` only allocates 128 bytes up-front. let mut data = Vec::with_capacity(4096); serde_json::to_writer_pretty(&mut data, self).expect("BUG: failed to serialize query data"); // Ensure there is a newline at the end of the JSON file to avoid // accidental modification by IDE and make github diff tool happier. data.push(b'\n'); // This ideally writes the data in as few syscalls as possible. file.write_all(&data) .map_err(|err| format!("failed to write query data to file {path:?}: {err:?}"))?; // We don't really need to call `.sync_data()` since it's trivial to re-run the macro // in the event a power loss results in incomplete flushing of the data to disk. Ok(()) } } pub(super) fn hash_string(query: &str) -> String { // picked `sha2` because it's already in the dependency tree for both MySQL and Postgres use sha2::{Digest, Sha256}; hex::encode(Sha256::digest(query.as_bytes())) } ================================================ FILE: sqlx-macros-core/src/query/input.rs ================================================ use std::fs; use proc_macro2::{Ident, Span}; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::{Expr, LitBool, LitStr, Token}; use syn::{ExprArray, Type}; /// Macro input shared by `query!()` and `query_file!()` pub struct QueryMacroInput { pub(super) sql: String, pub(super) src_span: Span, pub(super) record_type: RecordType, pub(super) arg_exprs: Vec, pub(super) checked: bool, pub(super) file_path: Option, } enum QuerySrc { String(String), File(String), } pub enum RecordType { Given(Type), Scalar, Generated, } impl Parse for QueryMacroInput { fn parse(input: ParseStream) -> syn::Result { let mut query_src: Option<(QuerySrc, Span)> = None; let mut args: Option> = None; let mut record_type = RecordType::Generated; let mut checked = true; let mut expect_comma = false; while !input.is_empty() { if expect_comma { let _ = input.parse::()?; } let key: Ident = input.parse()?; let _ = input.parse::()?; if key == "source" { let span = input.span(); let query_str = Punctuated::::parse_separated_nonempty(input)? .iter() .map(LitStr::value) .collect(); query_src = Some((QuerySrc::String(query_str), span)); } else if key == "source_file" { let lit_str = input.parse::()?; query_src = Some((QuerySrc::File(lit_str.value()), lit_str.span())); } else if key == "args" { let exprs = input.parse::()?; args = Some(exprs.elems.into_iter().collect()) } else if key == "record" { if !matches!(record_type, RecordType::Generated) { return Err(input.error("colliding `scalar` or `record` key")); } record_type = RecordType::Given(input.parse()?); } else if key == "scalar" { if !matches!(record_type, RecordType::Generated) { return Err(input.error("colliding `scalar` or `record` key")); } // we currently expect only `scalar = _` // a `query_as_scalar!()` variant seems less useful than just overriding the type // of the column in SQL input.parse::()?; record_type = RecordType::Scalar; } else if key == "checked" { let lit_bool = input.parse::()?; checked = lit_bool.value; } else { let message = format!("unexpected input key: {key}"); return Err(syn::Error::new_spanned(key, message)); } expect_comma = true; } let (src, src_span) = query_src.ok_or_else(|| input.error("expected `source` or `source_file` key"))?; let arg_exprs = args.unwrap_or_default(); let file_path = src.file_path(src_span)?; Ok(QueryMacroInput { sql: src.resolve(src_span)?, src_span, record_type, arg_exprs, checked, file_path, }) } } impl QuerySrc { /// If the query source is a file, read it to a string. Otherwise return the query string. fn resolve(self, source_span: Span) -> syn::Result { match self { QuerySrc::String(string) => Ok(string), QuerySrc::File(file) => read_file_src(&file, source_span), } } fn file_path(&self, source_span: Span) -> syn::Result> { if let QuerySrc::File(ref file) = *self { let path = crate::common::resolve_path(file, source_span)? .canonicalize() .map_err(|e| syn::Error::new(source_span, e))?; Ok(Some( path.to_str() .ok_or_else(|| { syn::Error::new( source_span, "query file path cannot be represented as a string", ) })? .to_string(), )) } else { Ok(None) } } } fn read_file_src(source: &str, source_span: Span) -> syn::Result { let file_path = crate::common::resolve_path(source, source_span)?; fs::read_to_string(&file_path).map_err(|e| { syn::Error::new( source_span, format!( "failed to read query file at {}: {}", file_path.display(), e ), ) }) } ================================================ FILE: sqlx-macros-core/src/query/metadata.rs ================================================ use sqlx_core::config::Config; use std::hash::{BuildHasherDefault, DefaultHasher}; use std::io; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use crate::query::cache::{MtimeCache, MtimeCacheBuilder}; use sqlx_core::HashMap; pub struct Metadata { pub manifest_dir: PathBuf, pub config: Config, env: MtimeCache>, workspace_root: Arc>>, } pub struct MacrosEnv { pub database_url: Option, pub offline_dir: Option, pub offline: Option, } impl Metadata { pub fn env(&self) -> crate::Result> { self.env .get_or_try_init(|builder| load_env(&self.manifest_dir, &self.config, builder)) } pub fn workspace_root(&self) -> PathBuf { let mut root = self.workspace_root.lock().unwrap(); if root.is_none() { use serde::Deserialize; use std::process::Command; let cargo = crate::env("CARGO").unwrap(); let output = Command::new(cargo) .args(["metadata", "--format-version=1", "--no-deps"]) .current_dir(&self.manifest_dir) .env_remove("__CARGO_FIX_PLZ") .output() .expect("Could not fetch metadata"); #[derive(Deserialize)] struct CargoMetadata { workspace_root: PathBuf, } let metadata: CargoMetadata = serde_json::from_slice(&output.stdout).expect("Invalid `cargo metadata` output"); *root = Some(metadata.workspace_root); } root.clone().unwrap() } } pub fn try_for_crate() -> crate::Result> { /// The `MtimeCache` in this type covers the config itself, /// any changes to which will indirectly invalidate the loaded env vars as well. #[expect(clippy::type_complexity)] static METADATA: Mutex< HashMap>>, BuildHasherDefault>, > = Mutex::new(HashMap::with_hasher(BuildHasherDefault::new())); let manifest_dir = crate::env("CARGO_MANIFEST_DIR")?; let cache = METADATA .lock() .expect("BUG: we shouldn't panic while holding this lock") .entry_ref(&manifest_dir) .or_insert_with(|| Arc::new(MtimeCache::new())) .clone(); cache.get_or_try_init(|builder| { let manifest_dir = PathBuf::from(manifest_dir); let config_path = manifest_dir.join("sqlx.toml"); builder.add_path(config_path.clone()); let config = Config::try_from_path_or_default(config_path)?; Ok(Arc::new(Metadata { manifest_dir, config, env: MtimeCache::new(), workspace_root: Default::default(), })) }) } fn load_env( manifest_dir: &Path, config: &Config, builder: &mut MtimeCacheBuilder, ) -> crate::Result> { #[derive(thiserror::Error, Debug)] #[error("error reading dotenv file {path:?}")] struct DotenvError { path: PathBuf, #[source] error: dotenvy::Error, } let mut from_dotenv = MacrosEnv { database_url: None, offline_dir: None, offline: None, }; for dir in manifest_dir.ancestors() { let path = dir.join(".env"); let dotenv = match dotenvy::from_path_iter(&path) { Ok(iter) => { builder.add_path(path.clone()); iter } Err(dotenvy::Error::Io(e)) if e.kind() == io::ErrorKind::NotFound => { builder.add_path(dir.to_path_buf()); continue; } Err(e) => { builder.add_path(path.clone()); return Err(DotenvError { path, error: e }.into()); } }; for res in dotenv { let (name, val) = res.map_err(|e| DotenvError { path: path.clone(), error: e, })?; match &*name { "SQLX_OFFLINE_DIR" => from_dotenv.offline_dir = Some(val.into()), "SQLX_OFFLINE" => from_dotenv.offline = Some(is_truthy_bool(&val)), _ if name == config.common.database_url_var() => { from_dotenv.database_url = Some(val) } _ => continue, } } } Ok(Arc::new(MacrosEnv { // Make set variables take precedent database_url: crate::env_opt(config.common.database_url_var())? .or(from_dotenv.database_url), offline_dir: crate::env_opt("SQLX_OFFLINE_DIR")? .map(PathBuf::from) .or(from_dotenv.offline_dir), offline: crate::env_opt("SQLX_OFFLINE")? .map(|val| is_truthy_bool(&val)) .or(from_dotenv.offline), })) } /// Returns `true` if `val` is `"true"`, fn is_truthy_bool(val: &str) -> bool { val.eq_ignore_ascii_case("true") || val == "1" } ================================================ FILE: sqlx-macros-core/src/query/mod.rs ================================================ use std::path::{Path, PathBuf}; use proc_macro2::TokenStream; use syn::Type; pub use input::QueryMacroInput; use quote::{format_ident, quote}; use sqlx_core::database::Database; use sqlx_core::{column::Column, describe::Describe, type_info::TypeInfo}; use crate::database::DatabaseExt; use crate::query::data::{hash_string, DynQueryData, QueryData}; use crate::query::input::RecordType; use crate::query::metadata::MacrosEnv; use either::Either; use metadata::Metadata; use sqlx_core::config::Config; use url::Url; mod args; mod cache; mod data; mod input; mod metadata; mod output; #[derive(Copy, Clone)] pub struct QueryDriver { db_name: &'static str, url_schemes: &'static [&'static str], expand: fn(&Config, QueryMacroInput, QueryDataSource, Option<&Path>) -> crate::Result, } impl QueryDriver { pub const fn new() -> Self where Describe: serde::Serialize + serde::de::DeserializeOwned, { QueryDriver { db_name: DB::NAME, url_schemes: DB::URL_SCHEMES, expand: expand_with::, } } } pub enum QueryDataSource<'a> { Live { database_url: &'a str, database_url_parsed: Url, }, Cached(DynQueryData), } impl<'a> QueryDataSource<'a> { pub fn live(database_url: &'a str) -> crate::Result { Ok(QueryDataSource::Live { database_url, database_url_parsed: database_url.parse()?, }) } pub fn matches_driver(&self, driver: &QueryDriver) -> bool { match self { Self::Live { database_url_parsed, .. } => driver.url_schemes.contains(&database_url_parsed.scheme()), Self::Cached(dyn_data) => dyn_data.db_name == driver.db_name, } } } pub fn expand_input<'a>( input: QueryMacroInput, drivers: impl IntoIterator, ) -> crate::Result { let metadata = metadata::try_for_crate()?; let metadata_env = metadata.env()?; let data_source = match &*metadata_env { MacrosEnv { offline: None | Some(false), database_url: Some(db_url), .. } // Allow `DATABASE_URL=''` if !db_url.is_empty() => QueryDataSource::live(db_url)?, MacrosEnv { offline, offline_dir, .. } => { // Try load the cached query metadata file. let filename = format!("query-{}.json", hash_string(&input.sql)); // Check SQLX_OFFLINE_DIR, then local .sqlx, then workspace .sqlx. let dirs = [ |_: &Metadata, offline_dir: Option<&Path>| offline_dir.map(PathBuf::from), |meta: &Metadata, _: Option<&Path>| Some(meta.manifest_dir.join(".sqlx")), |meta: &Metadata, _: Option<&Path>| Some(meta.workspace_root().join(".sqlx")), ]; let Some(data_file_path) = dirs .iter() .filter_map(|path| path(&metadata, offline_dir.as_deref())) .map(|path| path.join(&filename)) .find(|path| path.exists()) else { return Err( if offline.unwrap_or(false) { "`SQLX_OFFLINE=true` but there is no cached data for this query, run `cargo sqlx prepare` to update the query cache or unset `SQLX_OFFLINE`" } else { "set `DATABASE_URL` to use query macros online, or run `cargo sqlx prepare` to update the query cache" }.into() ); }; QueryDataSource::Cached(DynQueryData::from_data_file(&data_file_path, &input.sql)?) } }; for driver in drivers { if data_source.matches_driver(driver) { return (driver.expand)( &metadata.config, input, data_source, metadata_env.offline_dir.as_deref(), ); } } match data_source { QueryDataSource::Live { database_url_parsed, .. } => Err(format!( "no database driver found matching URL scheme {:?}; the corresponding Cargo feature may need to be enabled", database_url_parsed.scheme() ).into()), QueryDataSource::Cached(data) => { Err(format!( "found cached data for database {:?} but no matching driver; the corresponding Cargo feature may need to be enabled", data.db_name ).into()) } } } fn expand_with( config: &Config, input: QueryMacroInput, data_source: QueryDataSource, offline_dir: Option<&Path>, ) -> crate::Result where Describe: DescribeExt, { let (query_data, save_dir): (QueryData, Option<&Path>) = match data_source { // If the build is offline, the cache is our input so it's pointless to also write data for it. QueryDataSource::Cached(dyn_data) => (QueryData::from_dyn_data(dyn_data)?, None), QueryDataSource::Live { database_url, .. } => { let describe = DB::describe_blocking(&input.sql, database_url, &config.drivers)?; (QueryData::from_describe(&input.sql, describe), offline_dir) } }; expand_with_data(config, input, query_data, save_dir) } // marker trait for `Describe` that lets us conditionally require it to be `Serialize + Deserialize` trait DescribeExt: serde::Serialize + serde::de::DeserializeOwned {} impl DescribeExt for Describe where Describe: serde::Serialize + serde::de::DeserializeOwned { } #[derive(Default)] struct Warnings { ambiguous_datetime: bool, ambiguous_numeric: bool, } fn expand_with_data( config: &Config, input: QueryMacroInput, data: QueryData, save_dir: Option<&Path>, ) -> crate::Result where Describe: DescribeExt, { // validate at the minimum that our args match the query's input parameters let num_parameters = match data.describe.parameters() { Some(Either::Left(params)) => Some(params.len()), Some(Either::Right(num)) => Some(num), None => None, }; if let Some(num) = num_parameters { if num != input.arg_exprs.len() { return Err( format!("expected {} parameters, got {}", num, input.arg_exprs.len()).into(), ); } } let mut warnings = Warnings::default(); let args_tokens = args::quote_args(&input, config, &mut warnings, &data.describe)?; let query_args = format_ident!("query_args"); let output = if data .describe .columns() .iter() .all(|it| it.type_info().is_void()) { let db_path = DB::db_path(); let sql = &input.sql; quote! { ::sqlx::__query_with_result::<#db_path, _>(#sql, #query_args) } } else { match input.record_type { RecordType::Generated => { let columns = output::columns_to_rust::(&data.describe, config, &mut warnings)?; let record_name: Type = syn::parse_str("Record").unwrap(); for rust_col in &columns { if rust_col.type_.is_wildcard() { return Err( "wildcard overrides are only allowed with an explicit record type, \ e.g. `query_as!()` and its variants" .into(), ); } } let record_fields = columns .iter() .map(|output::RustColumn { ident, type_, .. }| quote!(#ident: #type_,)); let mut record_tokens = quote! { #[derive(Debug)] #[allow(non_snake_case)] struct #record_name { #(#record_fields)* } }; record_tokens.extend(output::quote_query_as::( &input, &record_name, &query_args, &columns, )); record_tokens } RecordType::Given(ref out_ty) => { let columns = output::columns_to_rust::(&data.describe, config, &mut warnings)?; output::quote_query_as::(&input, out_ty, &query_args, &columns) } RecordType::Scalar => output::quote_query_scalar::( &input, config, &mut warnings, &query_args, &data.describe, )?, } }; let mut warnings_out = TokenStream::new(); if warnings.ambiguous_datetime { // Warns if the date-time crate is inferred but both `chrono` and `time` are enabled warnings_out.extend(quote! { ::sqlx::warn_on_ambiguous_inferred_date_time_crate(); }); } if warnings.ambiguous_numeric { // Warns if the numeric crate is inferred but both `bigdecimal` and `rust_decimal` are enabled warnings_out.extend(quote! { ::sqlx::warn_on_ambiguous_inferred_numeric_crate(); }); } let ret_tokens = quote! { { #[allow(clippy::all)] { use ::sqlx::Arguments as _; #warnings_out #args_tokens #output } } }; if let Some(save_dir) = save_dir { data.save_in(save_dir)?; } Ok(ret_tokens) } ================================================ FILE: sqlx-macros-core/src/query/output.rs ================================================ use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens, TokenStreamExt}; use syn::Type; use sqlx_core::column::{Column, ColumnOrigin}; use sqlx_core::describe::Describe; use crate::database::DatabaseExt; use crate::query::{QueryMacroInput, Warnings}; use sqlx_core::config::Config; use sqlx_core::type_checking; use sqlx_core::type_checking::TypeChecking; use sqlx_core::type_info::TypeInfo; use std::fmt::{self, Display, Formatter}; use syn::parse::{Parse, ParseStream}; use syn::Token; pub struct RustColumn { pub(super) ident: Ident, pub(super) var_name: Ident, pub(super) type_: ColumnType, } pub(super) enum ColumnType { Exact(TokenStream), Wildcard, OptWildcard, } impl ColumnType { pub(super) fn is_wildcard(&self) -> bool { !matches!(self, ColumnType::Exact(_)) } } impl ToTokens for ColumnType { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.append_all(match &self { ColumnType::Exact(type_) => type_.clone().into_iter(), ColumnType::Wildcard => quote! { _ }.into_iter(), ColumnType::OptWildcard => quote! { ::std::option::Option<_> }.into_iter(), }) } } struct DisplayColumn<'a> { // zero-based index, converted to 1-based number idx: usize, name: &'a str, } struct ColumnDecl { ident: Ident, r#override: ColumnOverride, } struct ColumnOverride { nullability: ColumnNullabilityOverride, type_: ColumnTypeOverride, } #[derive(PartialEq)] enum ColumnNullabilityOverride { NonNull, Nullable, None, } enum ColumnTypeOverride { Exact(Type), Wildcard, None, } impl Display for DisplayColumn<'_> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "column #{} ({:?})", self.idx + 1, self.name) } } pub fn columns_to_rust( describe: &Describe, config: &Config, warnings: &mut Warnings, ) -> crate::Result> { (0..describe.columns().len()) .map(|i| column_to_rust(describe, config, warnings, i)) .collect::>>() } fn column_to_rust( describe: &Describe, config: &Config, warnings: &mut Warnings, i: usize, ) -> crate::Result { let column = &describe.columns()[i]; // add raw prefix to all identifiers let decl = ColumnDecl::parse(column.name()) .map_err(|e| format!("column name {:?} is invalid: {}", column.name(), e))?; let ColumnOverride { nullability, type_ } = decl.r#override; let nullable = match nullability { ColumnNullabilityOverride::NonNull => false, ColumnNullabilityOverride::Nullable => true, ColumnNullabilityOverride::None => describe.nullable(i).unwrap_or(true), }; let type_ = match (type_, nullable) { (ColumnTypeOverride::Exact(type_), false) => ColumnType::Exact(type_.to_token_stream()), (ColumnTypeOverride::Exact(type_), true) => { ColumnType::Exact(quote! { ::std::option::Option<#type_> }) } (ColumnTypeOverride::Wildcard, false) => ColumnType::Wildcard, (ColumnTypeOverride::Wildcard, true) => ColumnType::OptWildcard, (ColumnTypeOverride::None, _) => { let type_ = get_column_type::(config, warnings, i, column); if !nullable { ColumnType::Exact(type_) } else { ColumnType::Exact(quote! { ::std::option::Option<#type_> }) } } }; Ok(RustColumn { // prefix the variable name we use in `quote_query_as!()` so it doesn't conflict // https://github.com/launchbadge/sqlx/issues/1322 var_name: quote::format_ident!("sqlx_query_as_{}", decl.ident), ident: decl.ident, type_, }) } pub fn quote_query_as( input: &QueryMacroInput, out_ty: &Type, bind_args: &Ident, columns: &[RustColumn], ) -> TokenStream { let instantiations = columns.iter().enumerate().map( |( i, RustColumn { var_name, type_, .. }, )| { match (input.checked, type_) { // we guarantee the type is valid so we can skip the runtime check (true, ColumnType::Exact(type_)) => quote! { // binding to a `let` avoids confusing errors about // "try expression alternatives have incompatible types" // it doesn't seem to hurt inference in the other branches #[allow(non_snake_case)] let #var_name = row.try_get_unchecked::<#type_, _>(#i)?.into(); }, // type was overridden to be a wildcard so we fallback to the runtime check (true, ColumnType::Wildcard) => quote! ( #[allow(non_snake_case)] let #var_name = row.try_get(#i)?; ), (true, ColumnType::OptWildcard) => { quote! ( #[allow(non_snake_case)] let #var_name = row.try_get::<::std::option::Option<_>, _>(#i)?; ) } // macro is the `_unchecked!()` variant so this will die in decoding if it's wrong (false, _) => quote!( #[allow(non_snake_case)] let #var_name = row.try_get_unchecked(#i)?; ), } }, ); let ident = columns.iter().map(|col| &col.ident); let var_name = columns.iter().map(|col| &col.var_name); let db_path = DB::db_path(); let row_path = DB::row_path(); // if this query came from a file, use `include_str!()` to tell the compiler where it came from let sql = if let Some(ref path) = &input.file_path { quote::quote_spanned! { input.src_span => include_str!(#path) } } else { let sql = &input.sql; quote! { #sql } }; quote! { ::sqlx::__query_with_result::<#db_path, _>(#sql, #bind_args).try_map(|row: #row_path| { use ::sqlx::Row as _; #(#instantiations)* ::std::result::Result::Ok(#out_ty { #(#ident: #var_name),* }) }) } } pub fn quote_query_scalar( input: &QueryMacroInput, config: &Config, warnings: &mut Warnings, bind_args: &Ident, describe: &Describe, ) -> crate::Result { let columns = describe.columns(); if columns.len() != 1 { return Err(syn::Error::new( input.src_span, format!("expected exactly 1 column, got {}", columns.len()), ) .into()); } // attempt to parse a column override, otherwise fall back to the inferred type of the column let ty = if let Ok(rust_col) = column_to_rust(describe, config, warnings, 0) { rust_col.type_.to_token_stream() } else if input.checked { let ty = get_column_type::(config, warnings, 0, &columns[0]); if describe.nullable(0).unwrap_or(true) { quote! { ::std::option::Option<#ty> } } else { ty } } else { quote! { _ } }; let db = DB::db_path(); let query = &input.sql; Ok(quote! { ::sqlx::__query_scalar_with_result::<#db, #ty, _>(#query, #bind_args) }) } fn get_column_type( config: &Config, warnings: &mut Warnings, i: usize, column: &DB::Column, ) -> TokenStream { if let ColumnOrigin::Table(origin) = column.origin() { if let Some(column_override) = config.macros.column_override(&origin.table, &origin.name) { return column_override.parse().unwrap(); } } let type_info = column.type_info(); if let Some(type_override) = config.macros.type_override(type_info.name()) { return type_override.parse().unwrap(); } let err = match ::return_type_for_id( type_info, &config.macros.preferred_crates, ) { Ok(t) => return t.parse().unwrap(), Err(e) => e, }; let message = match err { type_checking::Error::NoMappingFound => { if let Some(feature_gate) = ::get_feature_gate(type_info) { format!( "SQLx feature `{feat}` required for type {ty} of {col}", ty = &type_info, feat = feature_gate, col = DisplayColumn { idx: i, name: column.name() } ) } else { format!( "no built-in mapping found for type {ty} of {col}; \ a type override may be required, see documentation for details", ty = type_info, col = DisplayColumn { idx: i, name: column.name() } ) } } type_checking::Error::DateTimeCrateFeatureNotEnabled => { let feature_gate = config .macros .preferred_crates .date_time .crate_name() .expect("BUG: got feature-not-enabled error for DateTimeCrate::Inferred"); format!( "SQLx feature `{feat}` required for type {ty} of {col} \ (configured by `macros.preferred-crates.date-time` in sqlx.toml)", ty = &type_info, feat = feature_gate, col = DisplayColumn { idx: i, name: column.name() } ) } type_checking::Error::NumericCrateFeatureNotEnabled => { let feature_gate = config .macros .preferred_crates .numeric .crate_name() .expect("BUG: got feature-not-enabled error for NumericCrate::Inferred"); format!( "SQLx feature `{feat}` required for type {ty} of {col} \ (configured by `macros.preferred-crates.numeric` in sqlx.toml)", ty = &type_info, feat = feature_gate, col = DisplayColumn { idx: i, name: column.name() } ) } type_checking::Error::AmbiguousDateTimeType { fallback } => { warnings.ambiguous_datetime = true; return fallback.parse().unwrap(); } type_checking::Error::AmbiguousNumericType { fallback } => { warnings.ambiguous_numeric = true; return fallback.parse().unwrap(); } }; syn::Error::new(Span::call_site(), message).to_compile_error() } impl ColumnDecl { fn parse(col_name: &str) -> crate::Result { // find the end of the identifier because we want to use our own logic to parse it // if we tried to feed this into `syn::parse_str()` we might get an un-great error // for some kinds of invalid identifiers let (ident, remainder) = if let Some(i) = col_name.find(&[':', '!', '?'][..]) { let (ident, remainder) = col_name.split_at(i); (parse_ident(ident)?, remainder) } else { (parse_ident(col_name)?, "") }; Ok(ColumnDecl { ident, r#override: if !remainder.is_empty() { syn::parse_str(remainder)? } else { ColumnOverride { nullability: ColumnNullabilityOverride::None, type_: ColumnTypeOverride::None, } }, }) } } impl Parse for ColumnOverride { fn parse(input: ParseStream) -> syn::Result { let lookahead = input.lookahead1(); let nullability = if lookahead.peek(Token![!]) { input.parse::()?; ColumnNullabilityOverride::NonNull } else if lookahead.peek(Token![?]) { input.parse::()?; ColumnNullabilityOverride::Nullable } else { ColumnNullabilityOverride::None }; let type_ = if input.lookahead1().peek(Token![:]) { input.parse::()?; let ty = Type::parse(input)?; if let Type::Infer(_) = ty { ColumnTypeOverride::Wildcard } else { ColumnTypeOverride::Exact(ty) } } else { ColumnTypeOverride::None }; Ok(Self { nullability, type_ }) } } fn parse_ident(name: &str) -> crate::Result { // workaround for the following issue (it's semi-fixed but still spits out extra diagnostics) // https://github.com/dtolnay/syn/issues/749#issuecomment-575451318 let is_valid_ident = !name.is_empty() && name.starts_with(|c: char| c.is_alphabetic() || c == '_') && name.chars().all(|c| c.is_alphanumeric() || c == '_'); if is_valid_ident { let ident = String::from("r#") + name; if let Ok(ident) = syn::parse_str(&ident) { return Ok(ident); } } Err(format!("{name:?} is not a valid Rust identifier").into()) } ================================================ FILE: sqlx-macros-core/src/test_attr.rs ================================================ use proc_macro2::TokenStream; use quote::quote; use syn::parse::Parser; #[cfg(feature = "migrate")] struct Args { fixtures: Vec<(FixturesType, Vec)>, migrations: MigrationsOpt, } #[cfg(feature = "migrate")] enum FixturesType { None, RelativePath, CustomRelativePath(syn::LitStr), ExplicitPath, } #[cfg(feature = "migrate")] enum MigrationsOpt { InferredPath, ExplicitPath(syn::LitStr), ExplicitMigrator(syn::Path), Disabled, } type AttributeArgs = syn::punctuated::Punctuated; pub fn expand(args: TokenStream, input: syn::ItemFn) -> crate::Result { let parser = AttributeArgs::parse_terminated; let args = parser.parse2(args)?; if input.sig.inputs.is_empty() { if !args.is_empty() { if cfg!(not(feature = "migrate")) { return Err(syn::Error::new_spanned( args.first().unwrap(), "control attributes are not allowed unless \ the `migrate` feature is enabled and \ automatic test DB management is used; see docs", ) .into()); } return Err(syn::Error::new_spanned( args.first().unwrap(), "control attributes are not allowed unless \ automatic test DB management is used; see docs", ) .into()); } return Ok(expand_simple(input)); } #[cfg(feature = "migrate")] return expand_advanced(args, input); #[cfg(not(feature = "migrate"))] return Err(syn::Error::new_spanned(input, "`migrate` feature required").into()); } fn expand_simple(input: syn::ItemFn) -> TokenStream { let ret = &input.sig.output; let name = &input.sig.ident; let body = &input.block; let attrs = &input.attrs; quote! { #[::core::prelude::v1::test] #(#attrs)* fn #name() #ret { ::sqlx::test_block_on(async { #body }) } } } #[cfg(feature = "migrate")] fn expand_advanced(args: AttributeArgs, input: syn::ItemFn) -> crate::Result { let config = sqlx_core::config::Config::try_from_crate_or_default()?; let ret = &input.sig.output; let name = &input.sig.ident; let inputs = &input.sig.inputs; let body = &input.block; let attrs = &input.attrs; let args = parse_args(args)?; let fn_arg_types = inputs.iter().map(|_| quote! { _ }); let mut fixtures = Vec::new(); for (fixture_type, fixtures_local) in args.fixtures { let mut res = match fixture_type { FixturesType::None => vec![], FixturesType::RelativePath => fixtures_local .into_iter() .map(|fixture| { let mut fixture_str = fixture.value(); add_sql_extension_if_missing(&mut fixture_str); let path = format!("fixtures/{}", fixture_str); quote! { ::sqlx::testing::TestFixture { path: #path, contents: include_str!(#path), } } }) .collect(), FixturesType::CustomRelativePath(path) => fixtures_local .into_iter() .map(|fixture| { let mut fixture_str = fixture.value(); add_sql_extension_if_missing(&mut fixture_str); let path = format!("{}/{}", path.value(), fixture_str); quote! { ::sqlx::testing::TestFixture { path: #path, contents: include_str!(#path), } } }) .collect(), FixturesType::ExplicitPath => fixtures_local .into_iter() .map(|fixture| { let path = fixture.value(); quote! { ::sqlx::testing::TestFixture { path: #path, contents: include_str!(#path), } } }) .collect(), }; fixtures.append(&mut res) } let migrations = match args.migrations { MigrationsOpt::ExplicitPath(path) => { let migrator = crate::migrate::expand(Some(path))?; quote! { args.migrator(&#migrator); } } MigrationsOpt::InferredPath if !inputs.is_empty() => { let path = crate::migrate::default_path(&config); let resolved_path = crate::common::resolve_path(path, proc_macro2::Span::call_site())?; if resolved_path.is_dir() { let migrator = crate::migrate::expand_with_path(&config, &resolved_path)?; quote! { args.migrator(&#migrator); } } else { quote! {} } } MigrationsOpt::ExplicitMigrator(path) => { quote! { args.migrator(&#path); } } _ => quote! {}, }; Ok(quote! { #(#attrs)* #[::core::prelude::v1::test] fn #name() #ret { async fn #name(#inputs) #ret { #body } let mut args = ::sqlx::testing::TestArgs::new(concat!(module_path!(), "::", stringify!(#name))); #migrations args.fixtures(&[#(#fixtures),*]); // We need to give a coercion site or else we get "unimplemented trait" errors. let f: fn(#(#fn_arg_types),*) -> _ = #name; ::sqlx::testing::TestFn::run_test(f, args) } }) } #[cfg(feature = "migrate")] fn parse_args(attr_args: AttributeArgs) -> syn::Result { use syn::{ parenthesized, parse::Parse, punctuated::Punctuated, token::Comma, Expr, Lit, LitStr, Meta, MetaNameValue, Token, }; let mut fixtures = Vec::new(); let mut migrations = MigrationsOpt::InferredPath; for arg in attr_args { let path = arg.path().clone(); match arg { syn::Meta::List(list) if list.path.is_ident("fixtures") => { let mut fixtures_local = vec![]; let mut fixtures_type = FixturesType::None; let parse_nested = list.parse_nested_meta(|meta| { if meta.path.is_ident("path") { // fixtures(path = "", scripts("","")) checking `path` argument meta.input.parse::()?; let val: LitStr = meta.input.parse()?; parse_fixtures_path_args(&mut fixtures_type, val)?; } else if meta.path.is_ident("scripts") { // fixtures(path = "", scripts("","")) checking `scripts` argument let content; parenthesized!(content in meta.input); let list = content.parse_terminated(::parse, Comma)?; parse_fixtures_scripts_args(&mut fixtures_type, list, &mut fixtures_local)?; } else { return Err(syn::Error::new_spanned( meta.path, "unexpected fixture meta", )); } Ok(()) }); if parse_nested.is_err() { // fixtures("","") or fixtures("","") let args = list.parse_args_with(>::parse_terminated)?; for arg in args { parse_fixtures_args(&mut fixtures_type, arg, &mut fixtures_local)?; } } fixtures.push((fixtures_type, fixtures_local)); } syn::Meta::NameValue(value) if value.path.is_ident("migrations") => { if !matches!(migrations, MigrationsOpt::InferredPath) { return Err(syn::Error::new_spanned( value, "cannot have more than one `migrations` or `migrator` arg", )); } fn recurse_lit_lookup(expr: Expr) -> Option { match expr { Expr::Lit(syn::ExprLit { lit, .. }) => Some(lit), Expr::Group(syn::ExprGroup { expr, .. }) => recurse_lit_lookup(*expr), _ => None, } } let Some(lit) = recurse_lit_lookup(value.value) else { return Err(syn::Error::new_spanned(path, "expected string or `false`")); }; migrations = match lit { // migrations = false Lit::Bool(b) if !b.value => MigrationsOpt::Disabled, // migrations = true Lit::Bool(b) => { return Err(syn::Error::new_spanned( b, "`migrations = true` is redundant", )); } // migrations = "path" Lit::Str(s) => MigrationsOpt::ExplicitPath(s), lit => return Err(syn::Error::new_spanned(lit, "expected string or `false`")), }; } // migrator = "" Meta::NameValue(MetaNameValue { value, .. }) if path.is_ident("migrator") => { if !matches!(migrations, MigrationsOpt::InferredPath) { return Err(syn::Error::new_spanned( path, "cannot have more than one `migrations` or `migrator` arg", )); } let Expr::Lit(syn::ExprLit { lit: Lit::Str(lit), .. }) = value else { return Err(syn::Error::new_spanned(path, "expected string")); }; migrations = MigrationsOpt::ExplicitMigrator(lit.parse()?); } arg => { return Err(syn::Error::new_spanned( arg, r#"expected `fixtures("", ...)` or `migrations = "" | false` or `migrator = ""`"#, )) } } } Ok(Args { fixtures, migrations, }) } #[cfg(feature = "migrate")] fn parse_fixtures_args( fixtures_type: &mut FixturesType, litstr: syn::LitStr, fixtures_local: &mut Vec, ) -> syn::Result<()> { // fixtures(path = "", scripts("","")) checking `path` argument let path_str = litstr.value(); let path = std::path::Path::new(&path_str); // This will be `true` if there's at least one path separator (`/` or `\`) // It's also true for all absolute paths, even e.g. `/foo.sql` as the root directory is counted as a component. let is_explicit_path = path.components().count() > 1; match fixtures_type { FixturesType::None => { if is_explicit_path { *fixtures_type = FixturesType::ExplicitPath; } else { *fixtures_type = FixturesType::RelativePath; } } FixturesType::RelativePath => { if is_explicit_path { return Err(syn::Error::new_spanned( litstr, "expected only relative path fixtures", )); } } FixturesType::ExplicitPath => { if !is_explicit_path { return Err(syn::Error::new_spanned( litstr, "expected only explicit path fixtures", )); } } FixturesType::CustomRelativePath(_) => { return Err(syn::Error::new_spanned( litstr, "custom relative path fixtures must be defined in `scripts` argument", )) } } if (matches!(fixtures_type, FixturesType::ExplicitPath) && !is_explicit_path) { return Err(syn::Error::new_spanned( litstr, "expected explicit path fixtures to have `.sql` extension", )); } fixtures_local.push(litstr); Ok(()) } #[cfg(feature = "migrate")] fn parse_fixtures_path_args( fixtures_type: &mut FixturesType, namevalue: syn::LitStr, ) -> syn::Result<()> { if !matches!(fixtures_type, FixturesType::None) { return Err(syn::Error::new_spanned( namevalue, "`path` must be the first argument of `fixtures`", )); } *fixtures_type = FixturesType::CustomRelativePath(namevalue); Ok(()) } #[cfg(feature = "migrate")] fn parse_fixtures_scripts_args( fixtures_type: &mut FixturesType, list: syn::punctuated::Punctuated, fixtures_local: &mut Vec, ) -> syn::Result<()> { // fixtures(path = "", scripts("","")) checking `scripts` argument if !matches!(fixtures_type, FixturesType::CustomRelativePath(_)) { return Err(syn::Error::new_spanned( list, "`scripts` must be the second argument of `fixtures` and used together with `path`", )); } fixtures_local.extend(list); Ok(()) } #[cfg(feature = "migrate")] fn add_sql_extension_if_missing(fixture: &mut String) { let has_extension = std::path::Path::new(&fixture).extension().is_some(); if !has_extension { fixture.push_str(".sql") } } ================================================ FILE: sqlx-mysql/Cargo.toml ================================================ [package] name = "sqlx-mysql" documentation = "https://docs.rs/sqlx" description = "MySQL driver implementation for SQLx. Not for direct use; see the `sqlx` crate for details." version.workspace = true license.workspace = true edition.workspace = true authors.workspace = true repository.workspace = true rust-version.workspace = true [features] json = ["sqlx-core/json", "serde"] any = ["sqlx-core/any"] offline = ["sqlx-core/offline", "serde/derive", "bitflags/serde"] migrate = ["sqlx-core/migrate"] # Type Integration features bigdecimal = ["dep:bigdecimal", "sqlx-core/bigdecimal"] chrono = ["dep:chrono", "sqlx-core/chrono"] rust_decimal = ["dep:rust_decimal", "rust_decimal/maths", "sqlx-core/rust_decimal"] time = ["dep:time", "sqlx-core/time"] uuid = ["dep:uuid", "sqlx-core/uuid"] [dependencies] sqlx-core = { workspace = true } # Futures crates futures-channel = { version = "0.3.19", default-features = false, features = ["sink", "alloc", "std"] } futures-core = { version = "0.3.19", default-features = false } futures-io = "0.3.24" futures-util = { version = "0.3.19", default-features = false, features = ["alloc", "sink", "io"] } # Cryptographic Primitives crc = "3.0.0" digest = { version = "0.10.0", default-features = false, features = ["std"] } hkdf = "0.12.0" hmac = { version = "0.12.0", default-features = false } md-5 = { version = "0.10.0", default-features = false } rand = { version = "0.8.4", default-features = false, features = ["std", "std_rng"] } rsa = "0.9" sha1 = { version = "0.10.1", default-features = false } sha2 = { version = "0.10.0", default-features = false } # Type Integrations (versions inherited from `[workspace.dependencies]`) bigdecimal = { workspace = true, optional = true } chrono = { workspace = true, optional = true } rust_decimal = { workspace = true, optional = true } time = { workspace = true, optional = true } uuid = { workspace = true, optional = true } # Misc atoi = "2.0" base64 = { version = "0.22.0", default-features = false, features = ["std"] } bitflags = { version = "2", default-features = false } byteorder = { version = "1.4.3", default-features = false, features = ["std"] } bytes = "1.1.0" either = "1.6.1" generic-array = { version = "0.14.4", default-features = false } hex = "0.4.3" itoa = "1.0.1" log = "0.4.18" memchr = { version = "2.4.1", default-features = false } percent-encoding = "2.1.0" smallvec = "1.7.0" stringprep = "0.1.2" tracing = { version = "0.1.37", features = ["log"] } dotenvy.workspace = true thiserror.workspace = true serde = { version = "1.0.144", optional = true } [dev-dependencies] # FIXME: https://github.com/rust-lang/cargo/issues/15622 sqlx = { path = "..", default-features = false, features = ["mysql"] } [lints] workspace = true ================================================ FILE: sqlx-mysql/src/any.rs ================================================ use crate::protocol::text::ColumnType; use crate::{ MySql, MySqlColumn, MySqlConnectOptions, MySqlConnection, MySqlQueryResult, MySqlRow, MySqlTransactionManager, MySqlTypeInfo, }; use either::Either; use futures_core::future::BoxFuture; use futures_core::stream::BoxStream; use futures_util::{stream, FutureExt, StreamExt, TryFutureExt, TryStreamExt}; use sqlx_core::any::{ AnyArguments, AnyColumn, AnyConnectOptions, AnyConnectionBackend, AnyQueryResult, AnyRow, AnyStatement, AnyTypeInfo, AnyTypeInfoKind, }; use sqlx_core::connection::Connection; use sqlx_core::database::Database; use sqlx_core::executor::Executor; use sqlx_core::sql_str::SqlStr; use sqlx_core::transaction::TransactionManager; use std::{future, pin::pin}; sqlx_core::declare_driver_with_optional_migrate!(DRIVER = MySql); impl AnyConnectionBackend for MySqlConnection { fn name(&self) -> &str { ::NAME } fn close(self: Box) -> BoxFuture<'static, sqlx_core::Result<()>> { Connection::close(*self).boxed() } fn close_hard(self: Box) -> BoxFuture<'static, sqlx_core::Result<()>> { Connection::close_hard(*self).boxed() } fn ping(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { Connection::ping(self).boxed() } fn begin(&mut self, statement: Option) -> BoxFuture<'_, sqlx_core::Result<()>> { MySqlTransactionManager::begin(self, statement).boxed() } fn commit(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { MySqlTransactionManager::commit(self).boxed() } fn rollback(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { MySqlTransactionManager::rollback(self).boxed() } fn start_rollback(&mut self) { MySqlTransactionManager::start_rollback(self) } fn get_transaction_depth(&self) -> usize { MySqlTransactionManager::get_transaction_depth(self) } fn shrink_buffers(&mut self) { Connection::shrink_buffers(self); } fn flush(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { Connection::flush(self).boxed() } fn should_flush(&self) -> bool { Connection::should_flush(self) } #[cfg(feature = "migrate")] fn as_migrate( &mut self, ) -> sqlx_core::Result<&mut (dyn sqlx_core::migrate::Migrate + Send + 'static)> { Ok(self) } fn fetch_many( &mut self, query: SqlStr, persistent: bool, arguments: Option, ) -> BoxStream<'_, sqlx_core::Result>> { let persistent = persistent && arguments.is_some(); let arguments = match arguments.map(AnyArguments::convert_into).transpose() { Ok(arguments) => arguments, Err(error) => { return stream::once(future::ready(Err(sqlx_core::Error::Encode(error)))).boxed() } }; Box::pin( self.run(query, arguments, persistent) .try_flatten_stream() .map(|res| { Ok(match res? { Either::Left(result) => Either::Left(map_result(result)), Either::Right(row) => Either::Right(AnyRow::try_from(&row)?), }) }), ) } fn fetch_optional( &mut self, query: SqlStr, persistent: bool, arguments: Option, ) -> BoxFuture<'_, sqlx_core::Result>> { let persistent = persistent && arguments.is_some(); let arguments = arguments .map(AnyArguments::convert_into) .transpose() .map_err(sqlx_core::Error::Encode); Box::pin(async move { let arguments = arguments?; let mut stream = pin!(self.run(query, arguments, persistent).await?); while let Some(result) = stream.try_next().await? { if let Either::Right(row) = result { return Ok(Some(AnyRow::try_from(&row)?)); } } Ok(None) }) } fn prepare_with<'c, 'q: 'c>( &'c mut self, sql: SqlStr, _parameters: &[AnyTypeInfo], ) -> BoxFuture<'c, sqlx_core::Result> { Box::pin(async move { let statement = Executor::prepare_with(self, sql, &[]).await?; let column_names = statement.metadata.column_names.clone(); AnyStatement::try_from_statement(statement, column_names) }) } #[cfg(feature = "offline")] fn describe( &mut self, sql: SqlStr, ) -> BoxFuture<'_, sqlx_core::Result>> { Box::pin(async move { let describe = Executor::describe(self, sql).await?; describe.try_into_any() }) } } impl<'a> TryFrom<&'a MySqlTypeInfo> for AnyTypeInfo { type Error = sqlx_core::Error; fn try_from(type_info: &'a MySqlTypeInfo) -> Result { Ok(AnyTypeInfo { kind: match &type_info.r#type { ColumnType::Null => AnyTypeInfoKind::Null, ColumnType::Short => AnyTypeInfoKind::SmallInt, ColumnType::Long => AnyTypeInfoKind::Integer, ColumnType::LongLong => AnyTypeInfoKind::BigInt, ColumnType::Float => AnyTypeInfoKind::Real, ColumnType::Double => AnyTypeInfoKind::Double, ColumnType::Blob | ColumnType::TinyBlob | ColumnType::MediumBlob | ColumnType::LongBlob => AnyTypeInfoKind::Blob, ColumnType::String | ColumnType::VarString | ColumnType::VarChar => { AnyTypeInfoKind::Text } _ => { return Err(sqlx_core::Error::AnyDriverError( format!("Any driver does not support MySql type {type_info:?}").into(), )) } }, }) } } impl<'a> TryFrom<&'a MySqlColumn> for AnyColumn { type Error = sqlx_core::Error; fn try_from(column: &'a MySqlColumn) -> Result { let type_info = AnyTypeInfo::try_from(&column.type_info)?; Ok(AnyColumn { ordinal: column.ordinal, name: column.name.clone(), type_info, }) } } impl<'a> TryFrom<&'a MySqlRow> for AnyRow { type Error = sqlx_core::Error; fn try_from(row: &'a MySqlRow) -> Result { AnyRow::map_from(row, row.column_names.clone()) } } impl<'a> TryFrom<&'a AnyConnectOptions> for MySqlConnectOptions { type Error = sqlx_core::Error; fn try_from(any_opts: &'a AnyConnectOptions) -> Result { let mut opts = Self::parse_from_url(&any_opts.database_url)?; opts.log_settings = any_opts.log_settings.clone(); Ok(opts) } } fn map_result(result: MySqlQueryResult) -> AnyQueryResult { AnyQueryResult { rows_affected: result.rows_affected, // Don't expect this to be a problem #[allow(clippy::cast_possible_wrap)] last_insert_id: Some(result.last_insert_id as i64), } } ================================================ FILE: sqlx-mysql/src/arguments.rs ================================================ use crate::encode::{Encode, IsNull}; use crate::types::Type; use crate::{MySql, MySqlTypeInfo}; pub(crate) use sqlx_core::arguments::*; use sqlx_core::error::BoxDynError; use std::ops::Deref; /// Implementation of [`Arguments`] for MySQL. #[derive(Debug, Default, Clone)] pub struct MySqlArguments { pub(crate) values: Vec, pub(crate) types: Vec, pub(crate) null_bitmap: NullBitMap, } impl MySqlArguments { pub(crate) fn add<'q, T>(&mut self, value: T) -> Result<(), BoxDynError> where T: Encode<'q, MySql> + Type, { let ty = value.produces().unwrap_or_else(T::type_info); let value_length_before_encoding = self.values.len(); let is_null = match value.encode(&mut self.values) { Ok(is_null) => is_null, Err(error) => { // reset the value buffer to its previous value if encoding failed so we don't leave a half-encoded value behind self.values.truncate(value_length_before_encoding); return Err(error); } }; self.types.push(ty); self.null_bitmap.push(is_null); Ok(()) } } impl Arguments for MySqlArguments { type Database = MySql; fn reserve(&mut self, len: usize, size: usize) { self.types.reserve(len); self.values.reserve(size); } fn add<'t, T>(&mut self, value: T) -> Result<(), BoxDynError> where T: Encode<'t, Self::Database> + Type, { self.add(value) } fn len(&self) -> usize { self.types.len() } } #[derive(Debug, Default, Clone)] pub(crate) struct NullBitMap { bytes: Vec, length: usize, } impl NullBitMap { fn push(&mut self, is_null: IsNull) { let byte_index = self.length / (u8::BITS as usize); let bit_offset = self.length % (u8::BITS as usize); if bit_offset == 0 { self.bytes.push(0); } self.bytes[byte_index] |= u8::from(is_null.is_null()) << bit_offset; self.length += 1; } } impl Deref for NullBitMap { type Target = [u8]; fn deref(&self) -> &Self::Target { &self.bytes } } #[cfg(test)] mod test { use super::*; #[test] fn null_bit_map_should_push_is_null() { let mut bit_map = NullBitMap::default(); bit_map.push(IsNull::Yes); bit_map.push(IsNull::No); bit_map.push(IsNull::Yes); bit_map.push(IsNull::No); bit_map.push(IsNull::Yes); bit_map.push(IsNull::No); bit_map.push(IsNull::Yes); bit_map.push(IsNull::No); bit_map.push(IsNull::Yes); assert_eq!([0b01010101, 0b1].as_slice(), bit_map.deref()); } } ================================================ FILE: sqlx-mysql/src/collation.rs ================================================ // One of several questionable design decisions in MySQL is the choice to conflate // *how stored data is sorted* with the character encoding used over the wire. // // The documentation for `Protocol::HandshakeResponse41` implies that // the lower 8 bits of the collation ID may be used to uniquely identify the character set: // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_response.html // // However, this isn't at _all_ true in practice. Collation IDs are assigned without any apparent // rhyme or reason, mostly just sequential with unexplained gaps. Masking the collation ID with 0xFF // doesn't actually tell you anything meaningful, except obviously for collation IDs under 256 // which just gives you the same collation ID again. // // Hanlon's razor would suggest they just forgot that they told clients they could do this. // Occam's razor suggests no one ever bothers to set the connection charset/collation this way, // and they all just default to `latin1_swedish_ci` (8), `utf8mb4_general_ci` (45), // or `utf8mb4_0900_ai_ci` (255). // // This would seem to mean that if we want to be *sure* of the character encoding of a given column, // we have to reference the _full_ catalog of collations. Because new ones are added occasionally, // we can't just assume a collation we don't recognize is UTF-8 as that's not always the case. // // This is especially true when we include MariaDB because they've started creating // their *own* collations, and even character sets, separately from MySQL. // // Awesome, right? // // However, as long as `character_set_client` and `character_set_results` are set correctly, // we can assume that any non-binary collation is a valid string, because the server will transcode. // As it turns out, the collation specified in the `Protocol::ColumnDefinition` // is *purely* informational. It has no bearing on what's sent over the wire except for `binary` (63), // which is never transcoded. // // So at the end of the day, none of this matters anyway! To know if a column is a string or not, // we merely need to check if it's not `binary` (63). If the protocol was just a *bit* // better documented, it would have saved me literally six hours spent figuring this out. // // Thanks, MySQL. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "offline", derive(serde::Deserialize, serde::Serialize))] pub struct Collation(pub u16); impl Collation { /// Collation used for all non-string data. pub const BINARY: Self = Collation(63); /// Most broadly supported UTF-8 collation. pub const UTF8MB4_GENERAL_CI: Self = Collation(45); } ================================================ FILE: sqlx-mysql/src/column.rs ================================================ use crate::ext::ustr::UStr; use crate::protocol::text::ColumnFlags; use crate::{MySql, MySqlTypeInfo}; pub(crate) use sqlx_core::column::*; #[derive(Debug, Clone)] #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] pub struct MySqlColumn { pub(crate) ordinal: usize, pub(crate) name: UStr, pub(crate) type_info: MySqlTypeInfo, #[cfg_attr(feature = "offline", serde(default))] pub(crate) origin: ColumnOrigin, #[allow(unused)] #[cfg_attr(feature = "offline", serde(skip))] pub(crate) flags: Option, } impl Column for MySqlColumn { type Database = MySql; fn ordinal(&self) -> usize { self.ordinal } fn name(&self) -> &str { &self.name } fn type_info(&self) -> &MySqlTypeInfo { &self.type_info } fn origin(&self) -> ColumnOrigin { self.origin.clone() } } ================================================ FILE: sqlx-mysql/src/connection/auth.rs ================================================ use bytes::buf::Chain; use bytes::Bytes; use digest::{Digest, OutputSizeUser}; use generic_array::GenericArray; use rand::thread_rng; use rsa::{pkcs8::DecodePublicKey, Oaep, RsaPublicKey}; use sha1::Sha1; use sha2::Sha256; use crate::connection::stream::MySqlStream; use crate::error::Error; use crate::protocol::auth::AuthPlugin; use crate::protocol::Packet; impl AuthPlugin { pub(super) async fn scramble( self, stream: &mut MySqlStream, password: &str, nonce: &Chain, ) -> Result, Error> { match self { // https://mariadb.com/kb/en/caching_sha2_password-authentication-plugin/ AuthPlugin::CachingSha2Password => Ok(scramble_sha256(password, nonce).to_vec()), AuthPlugin::MySqlNativePassword => Ok(scramble_sha1(password, nonce).to_vec()), // https://mariadb.com/kb/en/sha256_password-plugin/ AuthPlugin::Sha256Password => encrypt_rsa(stream, 0x01, password, nonce).await, AuthPlugin::MySqlClearPassword => { let mut pw_bytes = password.as_bytes().to_owned(); pw_bytes.push(0); // null terminate Ok(pw_bytes) } } } pub(super) async fn handle( self, stream: &mut MySqlStream, packet: Packet, password: &str, nonce: &Chain, ) -> Result { match self { AuthPlugin::CachingSha2Password if packet[0] == 0x01 => { match packet[1] { // AUTH_OK 0x03 => Ok(true), // AUTH_CONTINUE 0x04 => { let payload = encrypt_rsa(stream, 0x02, password, nonce).await?; stream.write_packet(&*payload)?; stream.flush().await?; Ok(false) } v => { Err(err_protocol!("unexpected result from fast authentication 0x{:x} when expecting 0x03 (AUTH_OK) or 0x04 (AUTH_CONTINUE)", v)) } } } _ => Err(err_protocol!( "unexpected packet 0x{:02x} for auth plugin '{}' during authentication", packet[0], self.name() )), } } } fn scramble_sha1( password: &str, nonce: &Chain, ) -> GenericArray::OutputSize> { // SHA1( password ) ^ SHA1( seed + SHA1( SHA1( password ) ) ) // https://mariadb.com/kb/en/connection/#mysql_native_password-plugin let mut ctx = Sha1::new(); ctx.update(password); let mut pw_hash = ctx.finalize_reset(); ctx.update(pw_hash); let pw_hash_hash = ctx.finalize_reset(); ctx.update(nonce.first_ref()); ctx.update(nonce.last_ref()); ctx.update(pw_hash_hash); let pw_seed_hash_hash = ctx.finalize(); xor_eq(&mut pw_hash, &pw_seed_hash_hash); pw_hash } fn scramble_sha256( password: &str, nonce: &Chain, ) -> GenericArray::OutputSize> { // XOR(SHA256(password), SHA256(seed, SHA256(SHA256(password)))) // https://mariadb.com/kb/en/caching_sha2_password-authentication-plugin/#sha-2-encrypted-password let mut ctx = Sha256::new(); ctx.update(password); let mut pw_hash = ctx.finalize_reset(); ctx.update(pw_hash); let pw_hash_hash = ctx.finalize_reset(); ctx.update(nonce.first_ref()); ctx.update(nonce.last_ref()); ctx.update(pw_hash_hash); let pw_seed_hash_hash = ctx.finalize(); xor_eq(&mut pw_hash, &pw_seed_hash_hash); pw_hash } async fn encrypt_rsa<'s>( stream: &'s mut MySqlStream, public_key_request_id: u8, password: &'s str, nonce: &'s Chain, ) -> Result, Error> { // https://mariadb.com/kb/en/caching_sha2_password-authentication-plugin/ if stream.is_tls { // If in a TLS stream, send the password directly in clear text return Ok(to_asciz(password)); } // client sends a public key request stream.write_packet(&[public_key_request_id][..])?; stream.flush().await?; // server sends a public key response let packet = stream.recv_packet().await?; let rsa_pub_key = &packet[1..]; // xor the password with the given nonce let mut pass = to_asciz(password); let (a, b) = (nonce.first_ref(), nonce.last_ref()); let mut nonce = Vec::with_capacity(a.len() + b.len()); nonce.extend_from_slice(a); nonce.extend_from_slice(b); xor_eq(&mut pass, &nonce); // client sends an RSA encrypted password let pkey = parse_rsa_pub_key(rsa_pub_key)?; let padding = Oaep::new::(); pkey.encrypt(&mut thread_rng(), padding, &pass[..]) .map_err(Error::protocol) } // XOR(x, y) // If len(y) < len(x), wrap around inside y fn xor_eq(x: &mut [u8], y: &[u8]) { let y_len = y.len(); for i in 0..x.len() { x[i] ^= y[i % y_len]; } } fn to_asciz(s: &str) -> Vec { let mut z = String::with_capacity(s.len() + 1); z.push_str(s); z.push('\0'); z.into_bytes() } // https://docs.rs/rsa/0.3.0/rsa/struct.RSAPublicKey.html?search=#example-1 fn parse_rsa_pub_key(key: &[u8]) -> Result { let pem = std::str::from_utf8(key).map_err(Error::protocol)?; // This takes advantage of the knowledge that we know // we are receiving a PKCS#8 RSA Public Key at all // times from MySQL RsaPublicKey::from_public_key_pem(pem).map_err(Error::protocol) } ================================================ FILE: sqlx-mysql/src/connection/establish.rs ================================================ use bytes::buf::Buf; use bytes::Bytes; use crate::common::StatementCache; use crate::connection::{tls, MySqlConnectionInner, MySqlStream, MAX_PACKET_SIZE}; use crate::error::Error; use crate::net::{Socket, WithSocket}; use crate::protocol::connect::{ AuthSwitchRequest, AuthSwitchResponse, Handshake, HandshakeResponse, }; use crate::protocol::Capabilities; use crate::{MySqlConnectOptions, MySqlConnection, MySqlSslMode}; impl MySqlConnection { pub(crate) async fn establish(options: &MySqlConnectOptions) -> Result { let do_handshake = DoHandshake::new(options)?; let handshake = match &options.socket { Some(path) => crate::net::connect_uds(path, do_handshake).await?, None => crate::net::connect_tcp(&options.host, options.port, do_handshake).await?, }; let stream = handshake?; Ok(Self { inner: Box::new(MySqlConnectionInner { stream, transaction_depth: 0, status_flags: Default::default(), cache_statement: StatementCache::new(options.statement_cache_capacity), log_settings: options.log_settings.clone(), }), }) } } struct DoHandshake<'a> { options: &'a MySqlConnectOptions, } impl<'a> DoHandshake<'a> { fn new(options: &'a MySqlConnectOptions) -> Result { if options.enable_cleartext_plugin && matches!( options.ssl_mode, MySqlSslMode::Disabled | MySqlSslMode::Preferred ) { log::warn!("Security warning: sending cleartext passwords without requiring SSL"); } Ok(Self { options }) } async fn do_handshake(self, socket: S) -> Result { let DoHandshake { options } = self; let mut stream = MySqlStream::with_socket(options, socket); // https://dev.mysql.com/doc/internals/en/connection-phase.html // https://mariadb.com/kb/en/connection/ let handshake: Handshake = stream.recv_packet().await?.decode()?; let mut plugin = handshake.auth_plugin; let nonce = handshake.auth_plugin_data; // FIXME: server version parse is a bit ugly // expecting MAJOR.MINOR.PATCH let mut server_version = handshake.server_version.split('.'); let server_version_major: u16 = server_version .next() .unwrap_or_default() .parse() .unwrap_or(0); let server_version_minor: u16 = server_version .next() .unwrap_or_default() .parse() .unwrap_or(0); let server_version_patch: u16 = server_version .next() .unwrap_or_default() .parse() .unwrap_or(0); stream.server_version = ( server_version_major, server_version_minor, server_version_patch, ); stream.capabilities &= handshake.server_capabilities; stream.capabilities |= Capabilities::PROTOCOL_41; let mut stream = tls::maybe_upgrade(stream, self.options).await?; let auth_response = if let (Some(plugin), Some(password)) = (plugin, &options.password) { Some(plugin.scramble(&mut stream, password, &nonce).await?) } else { None }; stream.write_packet(HandshakeResponse { charset: super::INITIAL_CHARSET, max_packet_size: MAX_PACKET_SIZE, username: &options.username, database: options.database.as_deref(), auth_plugin: plugin, auth_response: auth_response.as_deref(), })?; stream.flush().await?; loop { let packet = stream.recv_packet().await?; match packet[0] { 0x00 => { let _ok = packet.ok()?; break; } 0xfe => { let switch: AuthSwitchRequest = packet.decode_with(self.options.enable_cleartext_plugin)?; plugin = Some(switch.plugin); let nonce = switch.data.chain(Bytes::new()); let response = switch .plugin .scramble( &mut stream, options.password.as_deref().unwrap_or_default(), &nonce, ) .await?; stream.write_packet(AuthSwitchResponse(response))?; stream.flush().await?; } id => { if let (Some(plugin), Some(password)) = (plugin, &options.password) { if plugin.handle(&mut stream, packet, password, &nonce).await? { // plugin signaled authentication is ok break; } // plugin signaled to continue authentication } else { return Err(err_protocol!( "unexpected packet 0x{:02x} during authentication", id )); } } } } Ok(stream) } } impl WithSocket for DoHandshake<'_> { type Output = Result; async fn with_socket(self, socket: S) -> Self::Output { self.do_handshake(socket).await } } ================================================ FILE: sqlx-mysql/src/connection/executor.rs ================================================ use super::MySqlStream; use crate::connection::stream::Waiting; use crate::error::Error; use crate::executor::{Execute, Executor}; use crate::ext::ustr::UStr; use crate::io::MySqlBufExt; use crate::logger::QueryLogger; use crate::protocol::response::Status; use crate::protocol::statement::{ BinaryRow, Execute as StatementExecute, Prepare, PrepareOk, StmtClose, }; use crate::protocol::text::{ColumnDefinition, Query, TextRow}; use crate::statement::{MySqlStatement, MySqlStatementMetadata}; use crate::HashMap; use crate::{ MySql, MySqlArguments, MySqlColumn, MySqlConnection, MySqlQueryResult, MySqlRow, MySqlTypeInfo, MySqlValueFormat, }; use either::Either; use futures_core::future::BoxFuture; use futures_core::stream::BoxStream; use futures_core::Stream; use futures_util::TryStreamExt; use sqlx_core::column::{ColumnOrigin, TableColumn}; use sqlx_core::sql_str::SqlStr; use std::{pin::pin, sync::Arc}; impl MySqlConnection { async fn prepare_statement( &mut self, sql: &str, ) -> Result<(u32, MySqlStatementMetadata), Error> { // https://dev.mysql.com/doc/internals/en/com-stmt-prepare.html // https://dev.mysql.com/doc/internals/en/com-stmt-prepare-response.html#packet-COM_STMT_PREPARE_OK self.inner .stream .send_packet(Prepare { query: sql }) .await?; let ok: PrepareOk = self.inner.stream.recv().await?; // the parameter definitions are very unreliable so we skip over them // as we have little use if ok.params > 0 { for _ in 0..ok.params { let _def: ColumnDefinition = self.inner.stream.recv().await?; } self.inner.stream.maybe_recv_eof().await?; } // the column definitions are berefit the type information from the // to-be-bound parameters; we will receive the output column definitions // once more on execute so we wait for that let mut columns = Vec::new(); let column_names = if ok.columns > 0 { recv_result_metadata(&mut self.inner.stream, ok.columns as usize, &mut columns).await? } else { Default::default() }; let id = ok.statement_id; let metadata = MySqlStatementMetadata { parameters: ok.params as usize, columns: Arc::new(columns), column_names: Arc::new(column_names), }; Ok((id, metadata)) } async fn get_or_prepare_statement( &mut self, sql: &str, ) -> Result<(u32, MySqlStatementMetadata), Error> { if let Some(statement) = self.inner.cache_statement.get_mut(sql) { // is internally reference-counted return Ok((*statement).clone()); } let (id, metadata) = self.prepare_statement(sql).await?; // in case of the cache being full, close the least recently used statement if let Some((id, _)) = self .inner .cache_statement .insert(sql, (id, metadata.clone())) { self.inner .stream .send_packet(StmtClose { statement: id }) .await?; } Ok((id, metadata)) } #[allow(clippy::needless_lifetimes)] pub(crate) async fn run<'e, 'c: 'e, 'q: 'e>( &'c mut self, sql: SqlStr, arguments: Option, persistent: bool, ) -> Result, Error>> + 'e, Error> { let mut logger = QueryLogger::new(sql, self.inner.log_settings.clone()); self.inner.stream.wait_until_ready().await?; self.inner.stream.waiting.push_back(Waiting::Result); Ok(try_stream! { let sql = logger.sql().as_str(); // make a slot for the shared column data // as long as a reference to a row is not held past one iteration, this enables us // to re-use this memory freely between result sets let mut columns = Arc::new(Vec::new()); let (mut column_names, format, mut needs_metadata) = if let Some(arguments) = arguments { if persistent && self.inner.cache_statement.is_enabled() { let (id, metadata) = self .get_or_prepare_statement(sql) .await?; if arguments.types.len() != metadata.parameters { return Err( err_protocol!( "prepared statement expected {} parameters but {} parameters were provided", metadata.parameters, arguments.types.len() ) ); } // https://dev.mysql.com/doc/internals/en/com-stmt-execute.html self.inner.stream .send_packet(StatementExecute { statement: id, arguments: &arguments, }) .await?; (metadata.column_names, MySqlValueFormat::Binary, false) } else { let (id, metadata) = self .prepare_statement(sql) .await?; if arguments.types.len() != metadata.parameters { return Err( err_protocol!( "prepared statement expected {} parameters but {} parameters were provided", metadata.parameters, arguments.types.len() ) ); } // https://dev.mysql.com/doc/internals/en/com-stmt-execute.html self.inner.stream .send_packet(StatementExecute { statement: id, arguments: &arguments, }) .await?; self.inner.stream.send_packet(StmtClose { statement: id }).await?; (metadata.column_names, MySqlValueFormat::Binary, false) } } else { // https://dev.mysql.com/doc/internals/en/com-query.html self.inner.stream.send_packet(Query(sql)).await?; (Arc::default(), MySqlValueFormat::Text, true) }; loop { // query response is a meta-packet which may be one of: // Ok, Err, ResultSet, or (unhandled) LocalInfileRequest let mut packet = self.inner.stream.recv_packet().await?; if packet[0] == 0x00 || packet[0] == 0xff { // first packet in a query response is OK or ERR // this indicates either a successful query with no rows at all or a failed query let ok = packet.ok()?; self.inner.status_flags = ok.status; let rows_affected = ok.affected_rows; logger.increase_rows_affected(rows_affected); let done = MySqlQueryResult { rows_affected, last_insert_id: ok.last_insert_id, }; r#yield!(Either::Left(done)); if ok.status.contains(Status::SERVER_MORE_RESULTS_EXISTS) { // more result sets exist, continue to the next one continue; } self.inner.stream.waiting.pop_front(); return Ok(()); } // otherwise, this first packet is the start of the result-set metadata, *self.inner.stream.waiting.front_mut().unwrap() = Waiting::Row; let num_columns = packet.get_uint_lenenc()?; // column count let num_columns = usize::try_from(num_columns) .map_err(|_| err_protocol!("column count overflows usize: {num_columns}"))?; if needs_metadata { column_names = Arc::new(recv_result_metadata(&mut self.inner.stream, num_columns, Arc::make_mut(&mut columns)).await?); } else { // next time we hit here, it'll be a new result set and we'll need the // full metadata needs_metadata = true; recv_result_columns(&mut self.inner.stream, num_columns, Arc::make_mut(&mut columns)).await?; } // finally, there will be none or many result-rows loop { let packet = self.inner.stream.recv_packet().await?; if packet[0] == 0xfe { let (rows_affected, last_insert_id, status) = if packet.len() < 9 { // EOF packet let eof = packet.eof(self.inner.stream.capabilities)?; (0, 0, eof.status) } else { // OK packet let ok = packet.ok()?; (ok.affected_rows, ok.last_insert_id, ok.status) }; self.inner.status_flags = status; r#yield!(Either::Left(MySqlQueryResult { rows_affected, last_insert_id, })); if status.contains(Status::SERVER_MORE_RESULTS_EXISTS) { *self.inner.stream.waiting.front_mut().unwrap() = Waiting::Result; break; } self.inner.stream.waiting.pop_front(); return Ok(()); } let row = match format { MySqlValueFormat::Binary => packet.decode_with::(&columns)?.0, MySqlValueFormat::Text => packet.decode_with::(&columns)?.0, }; let v = Either::Right(MySqlRow { row, format, columns: Arc::clone(&columns), column_names: Arc::clone(&column_names), }); logger.increment_rows_returned(); r#yield!(v); } } }) } } impl<'c> Executor<'c> for &'c mut MySqlConnection { type Database = MySql; fn fetch_many<'e, 'q, E>( self, mut query: E, ) -> BoxStream<'e, Result, Error>> where 'c: 'e, E: Execute<'q, Self::Database>, 'q: 'e, E: 'q, { let arguments = query.take_arguments().map_err(Error::Encode); let persistent = query.persistent(); Box::pin(try_stream! { let sql = query.sql(); let arguments = arguments?; let mut s = pin!(self.run(sql, arguments, persistent).await?); while let Some(v) = s.try_next().await? { r#yield!(v); } Ok(()) }) } fn fetch_optional<'e, 'q, E>(self, query: E) -> BoxFuture<'e, Result, Error>> where 'c: 'e, E: Execute<'q, Self::Database>, 'q: 'e, E: 'q, { let mut s = self.fetch_many(query); Box::pin(async move { while let Some(v) = s.try_next().await? { if let Either::Right(r) = v { return Ok(Some(r)); } } Ok(None) }) } fn prepare_with<'e>( self, sql: SqlStr, _parameters: &'e [MySqlTypeInfo], ) -> BoxFuture<'e, Result> where 'c: 'e, { Box::pin(async move { self.inner.stream.wait_until_ready().await?; let metadata = if self.inner.cache_statement.is_enabled() { self.get_or_prepare_statement(sql.as_str()).await?.1 } else { let (id, metadata) = self.prepare_statement(sql.as_str()).await?; self.inner .stream .send_packet(StmtClose { statement: id }) .await?; metadata }; Ok(MySqlStatement { sql, // metadata has internal Arcs for expensive data structures metadata: metadata.clone(), }) }) } #[doc(hidden)] #[cfg(feature = "offline")] fn describe<'e>( self, sql: SqlStr, ) -> BoxFuture<'e, Result, Error>> where 'c: 'e, { Box::pin(async move { self.inner.stream.wait_until_ready().await?; let (id, metadata) = self.prepare_statement(sql.as_str()).await?; self.inner .stream .send_packet(StmtClose { statement: id }) .await?; let columns = (*metadata.columns).clone(); let nullable = columns .iter() .map(|col| { col.flags .map(|flags| !flags.contains(crate::protocol::text::ColumnFlags::NOT_NULL)) }) .collect(); Ok(crate::describe::Describe { parameters: Some(Either::Right(metadata.parameters)), columns, nullable, }) }) } } async fn recv_result_columns( stream: &mut MySqlStream, num_columns: usize, columns: &mut Vec, ) -> Result<(), Error> { columns.clear(); columns.reserve(num_columns); for ordinal in 0..num_columns { columns.push(recv_next_result_column(&stream.recv().await?, ordinal)?); } if num_columns > 0 { stream.maybe_recv_eof().await?; } Ok(()) } fn recv_next_result_column(def: &ColumnDefinition, ordinal: usize) -> Result { // if the alias is empty, use the alias // only then use the name let column_name = def.name()?; let name = match (def.name()?, def.alias()?) { (_, alias) if !alias.is_empty() => UStr::new(alias), (name, _) => UStr::new(name), }; let table = def.table()?; let origin = if table.is_empty() { ColumnOrigin::Expression } else { let schema = def.schema()?; ColumnOrigin::Table(TableColumn { table: if !schema.is_empty() { format!("{schema}.{table}").into() } else { table.into() }, name: column_name.into(), }) }; let type_info = MySqlTypeInfo::from_column(def); Ok(MySqlColumn { name, type_info, ordinal, flags: Some(def.flags), origin, }) } async fn recv_result_metadata( stream: &mut MySqlStream, num_columns: usize, columns: &mut Vec, ) -> Result, Error> { // the result-set metadata is primarily a listing of each output // column in the result-set let mut column_names = HashMap::with_capacity(num_columns); columns.clear(); columns.reserve(num_columns); for ordinal in 0..num_columns { let def: ColumnDefinition = stream.recv().await?; let column = recv_next_result_column(&def, ordinal)?; column_names.insert(column.name.clone(), ordinal); columns.push(column); } stream.maybe_recv_eof().await?; Ok(column_names) } ================================================ FILE: sqlx-mysql/src/connection/mod.rs ================================================ use std::fmt::{self, Debug, Formatter}; use std::future::Future; pub(crate) use sqlx_core::connection::*; use sqlx_core::sql_str::SqlSafeStr; pub(crate) use stream::{MySqlStream, Waiting}; use crate::collation::Collation; use crate::common::StatementCache; use crate::error::Error; use crate::protocol::response::Status; use crate::protocol::statement::StmtClose; use crate::protocol::text::{Ping, Quit}; use crate::statement::MySqlStatementMetadata; use crate::transaction::Transaction; use crate::{MySql, MySqlConnectOptions}; mod auth; mod establish; mod executor; mod stream; mod tls; const MAX_PACKET_SIZE: u32 = 1024; /// The charset parameter sent in the `Protocol::HandshakeResponse41` packet. /// /// This becomes the default if `set_names = false`, /// and also ensures that any error messages returned before `SET NAMES` are encoded correctly. #[allow(clippy::cast_possible_truncation)] const INITIAL_CHARSET: u8 = Collation::UTF8MB4_GENERAL_CI.0 as u8; /// A connection to a MySQL database. pub struct MySqlConnection { pub(crate) inner: Box, } pub(crate) struct MySqlConnectionInner { // underlying TCP stream, // wrapped in a potentially TLS stream, // wrapped in a buffered stream pub(crate) stream: MySqlStream, // transaction status pub(crate) transaction_depth: usize, status_flags: Status, // cache by query string to the statement id and metadata cache_statement: StatementCache<(u32, MySqlStatementMetadata)>, log_settings: LogSettings, } impl MySqlConnection { pub(crate) fn in_transaction(&self) -> bool { self.inner .status_flags .intersects(Status::SERVER_STATUS_IN_TRANS) } } impl Debug for MySqlConnection { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("MySqlConnection").finish() } } impl Connection for MySqlConnection { type Database = MySql; type Options = MySqlConnectOptions; async fn close(mut self) -> Result<(), Error> { self.inner.stream.send_packet(Quit).await?; self.inner.stream.shutdown().await?; Ok(()) } async fn close_hard(mut self) -> Result<(), Error> { self.inner.stream.shutdown().await?; Ok(()) } async fn ping(&mut self) -> Result<(), Error> { self.inner.stream.wait_until_ready().await?; self.inner.stream.send_packet(Ping).await?; self.inner.stream.recv_ok().await?; Ok(()) } #[doc(hidden)] fn flush(&mut self) -> impl Future> + Send + '_ { self.inner.stream.wait_until_ready() } fn cached_statements_size(&self) -> usize { self.inner.cache_statement.len() } async fn clear_cached_statements(&mut self) -> Result<(), Error> { while let Some((statement_id, _)) = self.inner.cache_statement.remove_lru() { self.inner .stream .send_packet(StmtClose { statement: statement_id, }) .await?; } Ok(()) } #[doc(hidden)] fn should_flush(&self) -> bool { !self.inner.stream.write_buffer().is_empty() } fn begin( &mut self, ) -> impl Future, Error>> + Send + '_ { Transaction::begin(self, None) } fn begin_with( &mut self, statement: impl SqlSafeStr, ) -> impl Future, Error>> + Send + '_ where Self: Sized, { Transaction::begin(self, Some(statement.into_sql_str())) } fn shrink_buffers(&mut self) { self.inner.stream.shrink_buffers(); } } ================================================ FILE: sqlx-mysql/src/connection/stream.rs ================================================ use std::collections::VecDeque; use std::ops::{Deref, DerefMut}; use bytes::{Buf, Bytes, BytesMut}; use crate::error::Error; use crate::io::MySqlBufExt; use crate::io::{ProtocolDecode, ProtocolEncode}; use crate::net::{BufferedSocket, Socket}; use crate::protocol::response::{EofPacket, ErrPacket, OkPacket, Status}; use crate::protocol::{Capabilities, Packet}; use crate::{MySqlConnectOptions, MySqlDatabaseError}; pub struct MySqlStream> { // Wrapping the socket in `Box` allows us to unsize in-place. pub(crate) socket: BufferedSocket, pub(crate) server_version: (u16, u16, u16), pub(super) capabilities: Capabilities, pub(crate) sequence_id: u8, pub(crate) waiting: VecDeque, pub(crate) is_tls: bool, } #[derive(Debug, PartialEq, Eq)] pub(crate) enum Waiting { // waiting for a result set Result, // waiting for a row within a result set Row, } impl MySqlStream { pub(crate) fn with_socket(options: &MySqlConnectOptions, socket: S) -> Self { let mut capabilities = Capabilities::PROTOCOL_41 | Capabilities::IGNORE_SPACE | Capabilities::DEPRECATE_EOF | Capabilities::FOUND_ROWS | Capabilities::TRANSACTIONS | Capabilities::SECURE_CONNECTION | Capabilities::PLUGIN_AUTH_LENENC_DATA | Capabilities::MULTI_STATEMENTS | Capabilities::MULTI_RESULTS | Capabilities::PLUGIN_AUTH | Capabilities::PS_MULTI_RESULTS | Capabilities::SSL; if options.database.is_some() { capabilities |= Capabilities::CONNECT_WITH_DB; } Self { waiting: VecDeque::new(), capabilities, server_version: (0, 0, 0), sequence_id: 0, socket: BufferedSocket::new(socket), is_tls: false, } } pub(crate) async fn wait_until_ready(&mut self) -> Result<(), Error> { if !self.socket.write_buffer().is_empty() { self.socket.flush().await?; } while !self.waiting.is_empty() { while self.waiting.front() == Some(&Waiting::Row) { let packet = self.recv_packet().await?; if !packet.is_empty() && packet[0] == 0xfe && packet.len() < 9 { let eof = packet.eof(self.capabilities)?; if eof.status.contains(Status::SERVER_MORE_RESULTS_EXISTS) { *self.waiting.front_mut().unwrap() = Waiting::Result; } else { self.waiting.pop_front(); }; } } while self.waiting.front() == Some(&Waiting::Result) { let packet = self.recv_packet().await?; if !packet.is_empty() && (packet[0] == 0x00 || packet[0] == 0xff) { let ok = packet.ok()?; if !ok.status.contains(Status::SERVER_MORE_RESULTS_EXISTS) { self.waiting.pop_front(); } } else { *self.waiting.front_mut().unwrap() = Waiting::Row; self.skip_result_metadata(packet).await?; } } } Ok(()) } pub(crate) async fn send_packet<'en, T>(&mut self, payload: T) -> Result<(), Error> where T: ProtocolEncode<'en, Capabilities>, { self.sequence_id = 0; self.write_packet(payload)?; self.flush().await?; Ok(()) } pub(crate) fn write_packet<'en, T>(&mut self, payload: T) -> Result<(), Error> where T: ProtocolEncode<'en, Capabilities>, { self.socket .write_with(Packet(payload), (self.capabilities, &mut self.sequence_id)) } async fn recv_packet_part(&mut self) -> Result { // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_basic_packets.html // https://mariadb.com/kb/en/library/0-packet/#standard-packet let mut header: Bytes = self.socket.read(4).await?; // cannot overflow #[allow(clippy::cast_possible_truncation)] let packet_size = header.get_uint_le(3) as usize; let sequence_id = header.get_u8(); self.sequence_id = sequence_id.wrapping_add(1); let payload: Bytes = self.socket.read(packet_size).await?; // TODO: packet compression Ok(payload) } // receive the next packet from the database server // may block (async) on more data from the server pub(crate) async fn recv_packet(&mut self) -> Result, Error> { let payload = self.recv_packet_part().await?; let payload = if payload.len() < 0xFF_FF_FF { payload } else { let mut final_payload = BytesMut::with_capacity(0xFF_FF_FF * 2); final_payload.extend_from_slice(&payload); drop(payload); // we don't need the allocation anymore let mut last_read = 0xFF_FF_FF; while last_read == 0xFF_FF_FF { let part = self.recv_packet_part().await?; last_read = part.len(); final_payload.extend_from_slice(&part); } final_payload.into() }; if payload .first() .ok_or(err_protocol!("Packet empty"))? .eq(&0xff) { self.waiting.pop_front(); // instead of letting this packet be looked at everywhere, we check here // and emit a proper Error return Err( MySqlDatabaseError(ErrPacket::decode_with(payload, self.capabilities)?).into(), ); } Ok(Packet(payload)) } pub(crate) async fn recv<'de, T>(&mut self) -> Result where T: ProtocolDecode<'de, Capabilities>, { self.recv_packet().await?.decode_with(self.capabilities) } pub(crate) async fn recv_ok(&mut self) -> Result { self.recv_packet().await?.ok() } pub(crate) async fn maybe_recv_eof(&mut self) -> Result, Error> { if self.capabilities.contains(Capabilities::DEPRECATE_EOF) { Ok(None) } else { self.recv().await.map(Some) } } async fn skip_result_metadata(&mut self, mut packet: Packet) -> Result<(), Error> { let num_columns: u64 = packet.get_uint_lenenc()?; // column count for _ in 0..num_columns { let _ = self.recv_packet().await?; } self.maybe_recv_eof().await?; Ok(()) } pub fn boxed_socket(self) -> MySqlStream { MySqlStream { socket: self.socket.boxed(), server_version: self.server_version, capabilities: self.capabilities, sequence_id: self.sequence_id, waiting: self.waiting, is_tls: self.is_tls, } } } impl Deref for MySqlStream { type Target = BufferedSocket; fn deref(&self) -> &Self::Target { &self.socket } } impl DerefMut for MySqlStream { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.socket } } ================================================ FILE: sqlx-mysql/src/connection/tls.rs ================================================ use crate::connection::{MySqlStream, Waiting}; use crate::error::Error; use crate::net::tls::TlsConfig; use crate::net::{tls, BufferedSocket, Socket, WithSocket}; use crate::protocol::connect::SslRequest; use crate::protocol::Capabilities; use crate::{MySqlConnectOptions, MySqlSslMode}; use std::collections::VecDeque; struct MapStream { server_version: (u16, u16, u16), capabilities: Capabilities, sequence_id: u8, waiting: VecDeque, } pub(super) async fn maybe_upgrade( mut stream: MySqlStream, options: &MySqlConnectOptions, ) -> Result { let server_supports_tls = stream.capabilities.contains(Capabilities::SSL); if matches!(options.ssl_mode, MySqlSslMode::Disabled) || !tls::available() { // remove the SSL capability if SSL has been explicitly disabled stream.capabilities.remove(Capabilities::SSL); } // https://www.postgresql.org/docs/12/libpq-ssl.html#LIBPQ-SSL-SSLMODE-STATEMENTS match options.ssl_mode { MySqlSslMode::Disabled => return Ok(stream.boxed_socket()), MySqlSslMode::Preferred => { if !tls::available() { // Client doesn't support TLS tracing::debug!("not performing TLS upgrade: TLS support not compiled in"); return Ok(stream.boxed_socket()); } if !server_supports_tls { // Server doesn't support TLS tracing::debug!("not performing TLS upgrade: unsupported by server"); return Ok(stream.boxed_socket()); } } MySqlSslMode::Required | MySqlSslMode::VerifyIdentity | MySqlSslMode::VerifyCa => { tls::error_if_unavailable()?; if !server_supports_tls { // upgrade failed, die return Err(Error::Tls("server does not support TLS".into())); } } } let tls_config = TlsConfig { accept_invalid_certs: !matches!( options.ssl_mode, MySqlSslMode::VerifyCa | MySqlSslMode::VerifyIdentity ), accept_invalid_hostnames: !matches!(options.ssl_mode, MySqlSslMode::VerifyIdentity), hostname: &options.host, root_cert_path: options.ssl_ca.as_ref(), client_cert_path: options.ssl_client_cert.as_ref(), client_key_path: options.ssl_client_key.as_ref(), }; // Request TLS upgrade stream.write_packet(SslRequest { max_packet_size: super::MAX_PACKET_SIZE, charset: super::INITIAL_CHARSET, })?; stream.flush().await?; tls::handshake( stream.socket.into_inner(), tls_config, MapStream { server_version: stream.server_version, capabilities: stream.capabilities, sequence_id: stream.sequence_id, waiting: stream.waiting, }, ) .await } impl WithSocket for MapStream { type Output = MySqlStream; async fn with_socket(self, socket: S) -> Self::Output { MySqlStream { socket: BufferedSocket::new(Box::new(socket)), server_version: self.server_version, capabilities: self.capabilities, sequence_id: self.sequence_id, waiting: self.waiting, is_tls: true, } } } ================================================ FILE: sqlx-mysql/src/database.rs ================================================ use crate::value::{MySqlValue, MySqlValueRef}; use crate::{ MySqlArguments, MySqlColumn, MySqlConnection, MySqlQueryResult, MySqlRow, MySqlStatement, MySqlTransactionManager, MySqlTypeInfo, }; pub(crate) use sqlx_core::database::{Database, HasStatementCache}; /// MySQL database driver. #[derive(Debug)] pub struct MySql; impl Database for MySql { type Connection = MySqlConnection; type TransactionManager = MySqlTransactionManager; type Row = MySqlRow; type QueryResult = MySqlQueryResult; type Column = MySqlColumn; type TypeInfo = MySqlTypeInfo; type Value = MySqlValue; type ValueRef<'r> = MySqlValueRef<'r>; type Arguments = MySqlArguments; type ArgumentBuffer = Vec; type Statement = MySqlStatement; const NAME: &'static str = "MySQL"; const URL_SCHEMES: &'static [&'static str] = &["mysql", "mariadb"]; } impl HasStatementCache for MySql {} ================================================ FILE: sqlx-mysql/src/error.rs ================================================ use std::error::Error as StdError; use std::fmt::{self, Debug, Display, Formatter}; use crate::protocol::response::ErrPacket; use std::borrow::Cow; pub(crate) use sqlx_core::error::*; /// An error returned from the MySQL database. pub struct MySqlDatabaseError(pub(super) ErrPacket); impl MySqlDatabaseError { /// The [SQLSTATE](https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html) code for this error. pub fn code(&self) -> Option<&str> { self.0.sql_state.as_deref() } /// The [number](https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html) /// for this error. /// /// MySQL tends to use SQLSTATE as a general error category, and the error number as a more /// granular indication of the error. pub fn number(&self) -> u16 { self.0.error_code } /// The human-readable error message. pub fn message(&self) -> &str { &self.0.error_message } } impl Debug for MySqlDatabaseError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("MySqlDatabaseError") .field("code", &self.code()) .field("number", &self.number()) .field("message", &self.message()) .finish() } } impl Display for MySqlDatabaseError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if let Some(code) = &self.code() { write!(f, "{} ({}): {}", self.number(), code, self.message()) } else { write!(f, "{}: {}", self.number(), self.message()) } } } impl StdError for MySqlDatabaseError {} impl DatabaseError for MySqlDatabaseError { #[inline] fn message(&self) -> &str { self.message() } #[inline] fn code(&self) -> Option> { self.code().map(Cow::Borrowed) } #[doc(hidden)] fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static) { self } #[doc(hidden)] fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) { self } #[doc(hidden)] fn into_error(self: Box) -> Box { self } fn kind(&self) -> ErrorKind { match self.number() { error_codes::ER_DUP_KEY | error_codes::ER_DUP_ENTRY | error_codes::ER_DUP_UNIQUE | error_codes::ER_DUP_ENTRY_WITH_KEY_NAME | error_codes::ER_DUP_UNKNOWN_IN_INDEX => ErrorKind::UniqueViolation, error_codes::ER_NO_REFERENCED_ROW | error_codes::ER_NO_REFERENCED_ROW_2 | error_codes::ER_ROW_IS_REFERENCED | error_codes::ER_ROW_IS_REFERENCED_2 | error_codes::ER_FK_COLUMN_NOT_NULL | error_codes::ER_FK_CANNOT_DELETE_PARENT => ErrorKind::ForeignKeyViolation, error_codes::ER_BAD_NULL_ERROR | error_codes::ER_NO_DEFAULT_FOR_FIELD => { ErrorKind::NotNullViolation } error_codes::ER_CHECK_CONSTRAINT_VIOLATED => ErrorKind::CheckViolation, // https://mariadb.com/kb/en/e4025/ error_codes::mariadb::ER_CONSTRAINT_FAILED // MySQL uses this code for a completely different error, // but we can differentiate by SQLSTATE: // { ErrorKind::CheckViolation } _ => ErrorKind::Other, } } } /// The MySQL server uses SQLSTATEs as a generic error category, /// and returns a `error_code` instead within the error packet. /// /// For reference: . pub(crate) mod error_codes { /// Caused when a DDL operation creates duplicated keys. pub const ER_DUP_KEY: u16 = 1022; /// Caused when a DML operation tries create a duplicated entry for a key, /// be it a unique or primary one. pub const ER_DUP_ENTRY: u16 = 1062; /// Similar to `ER_DUP_ENTRY`, but only present in NDB clusters. /// /// See: . pub const ER_DUP_UNIQUE: u16 = 1169; /// Similar to `ER_DUP_ENTRY`, but with a formatted string message. /// /// See: . pub const ER_DUP_ENTRY_WITH_KEY_NAME: u16 = 1586; /// Caused when a DDL operation to add a unique index fails, /// because duplicate items were created by concurrent DML operations. /// When this happens, the key is unknown, so the server can't use `ER_DUP_KEY`. /// /// For example: an `INSERT` operation creates duplicate `name` fields when `ALTER`ing a table and making `name` unique. pub const ER_DUP_UNKNOWN_IN_INDEX: u16 = 1859; /// Caused when inserting an entry with a column with a value that does not reference a foreign row. pub const ER_NO_REFERENCED_ROW: u16 = 1216; /// Caused when deleting a row that is referenced in other tables. pub const ER_ROW_IS_REFERENCED: u16 = 1217; /// Caused when deleting a row that is referenced in other tables. /// This differs from `ER_ROW_IS_REFERENCED` in that the error message contains the affected constraint. pub const ER_ROW_IS_REFERENCED_2: u16 = 1451; /// Caused when inserting an entry with a column with a value that does not reference a foreign row. /// This differs from `ER_NO_REFERENCED_ROW` in that the error message contains the affected constraint. pub const ER_NO_REFERENCED_ROW_2: u16 = 1452; /// Caused when creating a FK with `ON DELETE SET NULL` or `ON UPDATE SET NULL` to a column that is `NOT NULL`, or vice-versa. pub const ER_FK_COLUMN_NOT_NULL: u16 = 1830; /// Removed in 5.7.3. pub const ER_FK_CANNOT_DELETE_PARENT: u16 = 1834; /// Caused when inserting a NULL value to a column marked as NOT NULL. pub const ER_BAD_NULL_ERROR: u16 = 1048; /// Caused when inserting a DEFAULT value to a column marked as NOT NULL, which also doesn't have a default value set. pub const ER_NO_DEFAULT_FOR_FIELD: u16 = 1364; /// Caused when a check constraint is violated. /// /// Only available after 8.0.16. pub const ER_CHECK_CONSTRAINT_VIOLATED: u16 = 3819; pub(crate) mod mariadb { /// Error code emitted by MariaDB for constraint errors: /// /// MySQL emits this code for a completely different error: /// /// /// You also check that SQLSTATE is `23000`. pub const ER_CONSTRAINT_FAILED: u16 = 4025; } } ================================================ FILE: sqlx-mysql/src/io/buf.rs ================================================ use bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::BufExt; pub trait MySqlBufExt: Buf { // Read a length-encoded integer. // NOTE: 0xfb or NULL is only returned for binary value encoding to indicate NULL. // NOTE: 0xff is only returned during a result set to indicate ERR. // fn get_uint_lenenc(&mut self) -> Result; // Read a length-encoded string. #[allow(dead_code)] fn get_str_lenenc(&mut self) -> Result; // Read a length-encoded byte sequence. fn get_bytes_lenenc(&mut self) -> Result; } impl MySqlBufExt for Bytes { fn get_uint_lenenc(&mut self) -> Result { if self.remaining() < 1 { return Err(err_protocol!("lenenc int: no bytes remaining")); } match self.get_u8() { 0xfc => { if self.remaining() < 2 { return Err(err_protocol!( "lenenc int: need 2 more bytes, have {}", self.remaining() )); } Ok(u64::from(self.get_u16_le())) } 0xfd => { if self.remaining() < 3 { return Err(err_protocol!( "lenenc int: need 3 more bytes, have {}", self.remaining() )); } Ok(self.get_uint_le(3)) } 0xfe => { if self.remaining() < 8 { return Err(err_protocol!( "lenenc int: need 8 more bytes, have {}", self.remaining() )); } Ok(self.get_u64_le()) } v => Ok(u64::from(v)), } } fn get_str_lenenc(&mut self) -> Result { let size = self.get_uint_lenenc()?; let size = usize::try_from(size) .map_err(|_| err_protocol!("string length overflows usize: {size}"))?; self.get_str(size) } fn get_bytes_lenenc(&mut self) -> Result { let size = self.get_uint_lenenc()?; let size = usize::try_from(size) .map_err(|_| err_protocol!("string length overflows usize: {size}"))?; Ok(self.split_to(size)) } } ================================================ FILE: sqlx-mysql/src/io/buf_mut.rs ================================================ use bytes::BufMut; pub trait MySqlBufMutExt: BufMut { fn put_uint_lenenc(&mut self, v: u64); fn put_str_lenenc(&mut self, v: &str); fn put_bytes_lenenc(&mut self, v: &[u8]); } impl MySqlBufMutExt for Vec { fn put_uint_lenenc(&mut self, v: u64) { // https://dev.mysql.com/doc/internals/en/integer.html // https://mariadb.com/kb/en/library/protocol-data-types/#length-encoded-integers let encoded_le = v.to_le_bytes(); match v { 0..=250 => self.push(encoded_le[0]), 251..=0xFF_FF => { self.push(0xfc); self.extend_from_slice(&encoded_le[..2]); } 0x1_00_00..=0xFF_FF_FF => { self.push(0xfd); self.extend_from_slice(&encoded_le[..3]); } _ => { self.push(0xfe); self.extend_from_slice(&encoded_le); } } } fn put_str_lenenc(&mut self, v: &str) { self.put_bytes_lenenc(v.as_bytes()); } fn put_bytes_lenenc(&mut self, v: &[u8]) { self.put_uint_lenenc(v.len() as u64); self.extend(v); } } #[test] fn test_encodes_int_lenenc_u8() { let mut buf = Vec::with_capacity(1024); buf.put_uint_lenenc(0xFA_u64); assert_eq!(&buf[..], b"\xFA"); } #[test] fn test_encodes_int_lenenc_u16() { let mut buf = Vec::with_capacity(1024); buf.put_uint_lenenc(u16::MAX as u64); assert_eq!(&buf[..], b"\xFC\xFF\xFF"); } #[test] fn test_encodes_int_lenenc_u24() { let mut buf = Vec::with_capacity(1024); buf.put_uint_lenenc(0xFF_FF_FF_u64); assert_eq!(&buf[..], b"\xFD\xFF\xFF\xFF"); } #[test] fn test_encodes_int_lenenc_u64() { let mut buf = Vec::with_capacity(1024); buf.put_uint_lenenc(u64::MAX); assert_eq!(&buf[..], b"\xFE\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"); } #[test] fn test_encodes_int_lenenc_fb() { let mut buf = Vec::with_capacity(1024); buf.put_uint_lenenc(0xFB_u64); assert_eq!(&buf[..], b"\xFC\xFB\x00"); } #[test] fn test_encodes_int_lenenc_fc() { let mut buf = Vec::with_capacity(1024); buf.put_uint_lenenc(0xFC_u64); assert_eq!(&buf[..], b"\xFC\xFC\x00"); } #[test] fn test_encodes_int_lenenc_fd() { let mut buf = Vec::with_capacity(1024); buf.put_uint_lenenc(0xFD_u64); assert_eq!(&buf[..], b"\xFC\xFD\x00"); } #[test] fn test_encodes_int_lenenc_fe() { let mut buf = Vec::with_capacity(1024); buf.put_uint_lenenc(0xFE_u64); assert_eq!(&buf[..], b"\xFC\xFE\x00"); } #[test] fn test_encodes_int_lenenc_ff() { let mut buf = Vec::with_capacity(1024); buf.put_uint_lenenc(0xFF_u64); assert_eq!(&buf[..], b"\xFC\xFF\x00"); } #[test] fn test_encodes_string_lenenc() { let mut buf = Vec::with_capacity(1024); buf.put_str_lenenc("random_string"); assert_eq!(&buf[..], b"\x0Drandom_string"); } #[test] fn test_encodes_byte_lenenc() { let mut buf = Vec::with_capacity(1024); buf.put_bytes_lenenc(b"random_string"); assert_eq!(&buf[..], b"\x0Drandom_string"); } ================================================ FILE: sqlx-mysql/src/io/mod.rs ================================================ mod buf; mod buf_mut; pub use buf::MySqlBufExt; pub use buf_mut::MySqlBufMutExt; pub(crate) use sqlx_core::io::*; ================================================ FILE: sqlx-mysql/src/lib.rs ================================================ //! **MySQL** database driver. #![deny(clippy::cast_possible_truncation)] #![deny(clippy::cast_possible_wrap)] #![deny(clippy::cast_sign_loss)] #[macro_use] extern crate sqlx_core; use crate::executor::Executor; pub(crate) use sqlx_core::driver_prelude::*; #[cfg(feature = "any")] pub mod any; mod arguments; mod collation; mod column; mod connection; mod database; mod error; mod io; mod options; mod protocol; mod query_result; mod row; mod statement; mod transaction; mod type_checking; mod type_info; pub mod types; mod value; #[cfg(feature = "migrate")] mod migrate; #[cfg(feature = "migrate")] mod testing; pub use arguments::MySqlArguments; pub use column::MySqlColumn; pub use connection::MySqlConnection; pub use database::MySql; pub use error::MySqlDatabaseError; pub use options::{MySqlConnectOptions, MySqlSslMode}; pub use query_result::MySqlQueryResult; pub use row::MySqlRow; pub use statement::MySqlStatement; pub use transaction::MySqlTransactionManager; pub use type_info::MySqlTypeInfo; pub use value::{MySqlValue, MySqlValueFormat, MySqlValueRef}; /// An alias for [`Pool`][crate::pool::Pool], specialized for MySQL. pub type MySqlPool = crate::pool::Pool; /// An alias for [`PoolOptions`][crate::pool::PoolOptions], specialized for MySQL. pub type MySqlPoolOptions = crate::pool::PoolOptions; /// An alias for [`Executor<'_, Database = MySql>`][Executor]. pub trait MySqlExecutor<'c>: Executor<'c, Database = MySql> {} impl<'c, T: Executor<'c, Database = MySql>> MySqlExecutor<'c> for T {} /// An alias for [`Transaction`][crate::transaction::Transaction], specialized for MySQL. pub type MySqlTransaction<'c> = crate::transaction::Transaction<'c, MySql>; // NOTE: required due to the lack of lazy normalization impl_into_arguments_for_arguments!(MySqlArguments); impl_acquire!(MySql, MySqlConnection); impl_column_index_for_row!(MySqlRow); impl_column_index_for_statement!(MySqlStatement); // required because some databases have a different handling of NULL impl_encode_for_option!(MySql); ================================================ FILE: sqlx-mysql/src/migrate.rs ================================================ use std::str::FromStr; use std::time::Duration; use std::time::Instant; use futures_core::future::BoxFuture; pub(crate) use sqlx_core::migrate::*; use sqlx_core::sql_str::AssertSqlSafe; use crate::connection::{ConnectOptions, Connection}; use crate::error::Error; use crate::executor::Executor; use crate::query::query; use crate::query_as::query_as; use crate::query_scalar::query_scalar; use crate::{MySql, MySqlConnectOptions, MySqlConnection}; fn parse_for_maintenance(url: &str) -> Result<(MySqlConnectOptions, String), Error> { let mut options = MySqlConnectOptions::from_str(url)?; let database = if let Some(database) = &options.database { database.to_owned() } else { return Err(Error::Configuration( "DATABASE_URL does not specify a database".into(), )); }; // switch us to database for create/drop commands options.database = None; Ok((options, database)) } impl MigrateDatabase for MySql { async fn create_database(url: &str) -> Result<(), Error> { let (options, database) = parse_for_maintenance(url)?; let mut conn = options.connect().await?; let _ = conn .execute(AssertSqlSafe(format!("CREATE DATABASE `{database}`"))) .await?; Ok(()) } async fn database_exists(url: &str) -> Result { let (options, database) = parse_for_maintenance(url)?; let mut conn = options.connect().await?; let exists: bool = query_scalar( "select exists(SELECT 1 from INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?)", ) .bind(database) .fetch_one(&mut conn) .await?; Ok(exists) } async fn drop_database(url: &str) -> Result<(), Error> { let (options, database) = parse_for_maintenance(url)?; let mut conn = options.connect().await?; let _ = conn .execute(AssertSqlSafe(format!( "DROP DATABASE IF EXISTS `{database}`" ))) .await?; Ok(()) } } impl Migrate for MySqlConnection { fn create_schema_if_not_exists<'e>( &'e mut self, schema_name: &'e str, ) -> BoxFuture<'e, Result<(), MigrateError>> { Box::pin(async move { // language=SQL self.execute(AssertSqlSafe(format!( r#"CREATE SCHEMA IF NOT EXISTS {schema_name};"# ))) .await?; Ok(()) }) } fn ensure_migrations_table<'e>( &'e mut self, table_name: &'e str, ) -> BoxFuture<'e, Result<(), MigrateError>> { Box::pin(async move { // language=MySQL self.execute(AssertSqlSafe(format!( r#" CREATE TABLE IF NOT EXISTS {table_name} ( version BIGINT PRIMARY KEY, description TEXT NOT NULL, installed_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, success BOOLEAN NOT NULL, checksum BLOB NOT NULL, execution_time BIGINT NOT NULL ); "# ))) .await?; Ok(()) }) } fn dirty_version<'e>( &'e mut self, table_name: &'e str, ) -> BoxFuture<'e, Result, MigrateError>> { Box::pin(async move { // language=SQL let row: Option<(i64,)> = query_as(AssertSqlSafe(format!( "SELECT version FROM {table_name} WHERE success = false ORDER BY version LIMIT 1" ))) .fetch_optional(self) .await?; Ok(row.map(|r| r.0)) }) } fn list_applied_migrations<'e>( &'e mut self, table_name: &'e str, ) -> BoxFuture<'e, Result, MigrateError>> { Box::pin(async move { // language=SQL let rows: Vec<(i64, Vec)> = query_as(AssertSqlSafe(format!( "SELECT version, checksum FROM {table_name} ORDER BY version" ))) .fetch_all(self) .await?; let migrations = rows .into_iter() .map(|(version, checksum)| AppliedMigration { version, checksum: checksum.into(), }) .collect(); Ok(migrations) }) } fn lock(&mut self) -> BoxFuture<'_, Result<(), MigrateError>> { Box::pin(async move { let database_name = current_database(self).await?; let lock_id = generate_lock_id(&database_name); // create an application lock over the database // this function will not return until the lock is acquired // https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS // https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS-TABLE // language=MySQL let _ = query("SELECT GET_LOCK(?, -1)") .bind(lock_id) .execute(self) .await?; Ok(()) }) } fn unlock(&mut self) -> BoxFuture<'_, Result<(), MigrateError>> { Box::pin(async move { let database_name = current_database(self).await?; let lock_id = generate_lock_id(&database_name); // language=MySQL let _ = query("SELECT RELEASE_LOCK(?)") .bind(lock_id) .execute(self) .await?; Ok(()) }) } fn apply<'e>( &'e mut self, table_name: &'e str, migration: &'e Migration, ) -> BoxFuture<'e, Result> { Box::pin(async move { // Use a single transaction for the actual migration script and the essential bookkeeping so we never // execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966. // The `execution_time` however can only be measured for the whole transaction. This value _only_ exists for // data lineage and debugging reasons, so it is not super important if it is lost. So we initialize it to -1 // and update it once the actual transaction completed. let mut tx = self.begin().await?; let start = Instant::now(); // For MySQL we cannot really isolate migrations due to implicit commits caused by table modification, see // https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html // // To somewhat try to detect this, we first insert the migration into the migration table with // `success=FALSE` and later modify the flag. // // language=MySQL let _ = query(AssertSqlSafe(format!( r#" INSERT INTO {table_name} ( version, description, success, checksum, execution_time ) VALUES ( ?, ?, FALSE, ?, -1 ) "# ))) .bind(migration.version) .bind(&*migration.description) .bind(&*migration.checksum) .execute(&mut *tx) .await?; let _ = tx .execute(migration.sql.clone()) .await .map_err(|e| MigrateError::ExecuteMigration(e, migration.version))?; // language=MySQL let _ = query(AssertSqlSafe(format!( r#" UPDATE {table_name} SET success = TRUE WHERE version = ? "# ))) .bind(migration.version) .execute(&mut *tx) .await?; tx.commit().await?; // Update `elapsed_time`. // NOTE: The process may disconnect/die at this point, so the elapsed time value might be lost. We accept // this small risk since this value is not super important. let elapsed = start.elapsed(); #[allow(clippy::cast_possible_truncation)] let _ = query(AssertSqlSafe(format!( r#" UPDATE {table_name} SET execution_time = ? WHERE version = ? "# ))) .bind(elapsed.as_nanos() as i64) .bind(migration.version) .execute(self) .await?; Ok(elapsed) }) } fn revert<'e>( &'e mut self, table_name: &'e str, migration: &'e Migration, ) -> BoxFuture<'e, Result> { Box::pin(async move { // Use a single transaction for the actual migration script and the essential bookkeeping so we never // execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966. let mut tx = self.begin().await?; let start = Instant::now(); // For MySQL we cannot really isolate migrations due to implicit commits caused by table modification, see // https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html // // To somewhat try to detect this, we first insert the migration into the migration table with // `success=FALSE` and later remove the migration altogether. // // language=MySQL let _ = query(AssertSqlSafe(format!( r#" UPDATE {table_name} SET success = FALSE WHERE version = ? "# ))) .bind(migration.version) .execute(&mut *tx) .await?; tx.execute(migration.sql.clone()).await?; // language=SQL let _ = query(AssertSqlSafe(format!( r#"DELETE FROM {table_name} WHERE version = ?"# ))) .bind(migration.version) .execute(&mut *tx) .await?; tx.commit().await?; let elapsed = start.elapsed(); Ok(elapsed) }) } } async fn current_database(conn: &mut MySqlConnection) -> Result { // language=MySQL Ok(query_scalar("SELECT DATABASE()").fetch_one(conn).await?) } // inspired from rails: https://github.com/rails/rails/blob/6e49cc77ab3d16c06e12f93158eaf3e507d4120e/activerecord/lib/active_record/migration.rb#L1308 fn generate_lock_id(database_name: &str) -> String { const CRC_IEEE: crc::Crc = crc::Crc::::new(&crc::CRC_32_ISO_HDLC); // 0x3d32ad9e chosen by fair dice roll format!( "{:x}", 0x3d32ad9e * (CRC_IEEE.checksum(database_name.as_bytes()) as i64) ) } ================================================ FILE: sqlx-mysql/src/options/connect.rs ================================================ use crate::connection::ConnectOptions; use crate::error::Error; use crate::executor::Executor; use crate::{MySqlConnectOptions, MySqlConnection}; use log::LevelFilter; use sqlx_core::sql_str::AssertSqlSafe; use sqlx_core::Url; use std::time::Duration; impl ConnectOptions for MySqlConnectOptions { type Connection = MySqlConnection; fn from_url(url: &Url) -> Result { Self::parse_from_url(url) } fn to_url_lossy(&self) -> Url { self.build_url() } async fn connect(&self) -> Result where Self::Connection: Sized, { let mut conn = MySqlConnection::establish(self).await?; // After the connection is established, we initialize by configuring a few // connection parameters // https://mariadb.com/kb/en/sql-mode/ // PIPES_AS_CONCAT - Allows using the pipe character (ASCII 124) as string concatenation operator. // This means that "A" || "B" can be used in place of CONCAT("A", "B"). // NO_ENGINE_SUBSTITUTION - If not set, if the available storage engine specified by a CREATE TABLE is // not available, a warning is given and the default storage // engine is used instead. // NO_ZERO_DATE - Don't allow '0000-00-00'. This is invalid in Rust. // NO_ZERO_IN_DATE - Don't allow 'YYYY-00-00'. This is invalid in Rust. // -- // Setting the time zone allows us to assume that the output // from a TIMESTAMP field is UTC // -- // https://mathiasbynens.be/notes/mysql-utf8mb4 let mut sql_mode = Vec::new(); if self.pipes_as_concat { sql_mode.push(r#"PIPES_AS_CONCAT"#); } if self.no_engine_substitution { sql_mode.push(r#"NO_ENGINE_SUBSTITUTION"#); } let mut options = Vec::new(); if !sql_mode.is_empty() { options.push(format!( r#"sql_mode=(SELECT CONCAT(@@sql_mode, ',{}'))"#, sql_mode.join(",") )); } if let Some(timezone) = &self.timezone { options.push(format!(r#"time_zone='{}'"#, timezone)); } if self.set_names { // As it turns out, we don't _have_ to set a collation if we don't want to. // We can let the server choose the default collation for the charset. let set_names = if let Some(collation) = &self.collation { format!(r#"NAMES {} COLLATE {collation}"#, self.charset,) } else { // Leaves the default collation up to the server, // but ensures statements and results are encoded using the proper charset. format!("NAMES {}", self.charset) }; options.push(set_names); } if !options.is_empty() { conn.execute(AssertSqlSafe(format!(r#"SET {};"#, options.join(",")))) .await?; } Ok(conn) } fn log_statements(mut self, level: LevelFilter) -> Self { self.log_settings.log_statements(level); self } fn log_slow_statements(mut self, level: LevelFilter, duration: Duration) -> Self { self.log_settings.log_slow_statements(level, duration); self } } ================================================ FILE: sqlx-mysql/src/options/mod.rs ================================================ use std::path::{Path, PathBuf}; mod connect; mod parse; mod ssl_mode; use crate::{connection::LogSettings, net::tls::CertificateInput}; pub use ssl_mode::MySqlSslMode; /// Options and flags which can be used to configure a MySQL connection. /// /// A value of `MySqlConnectOptions` can be parsed from a connection URL, /// as described by [MySQL](https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-jdbc-url-format.html). /// /// The generic format of the connection URL: /// /// ```text /// mysql://[host][/database][?properties] /// ``` /// /// This type also implements [`FromStr`][std::str::FromStr] so you can parse it from a string /// containing a connection URL and then further adjust options if necessary (see example below). /// /// ## Properties /// /// |Parameter|Default|Description| /// |---------|-------|-----------| /// | `ssl-mode` | `PREFERRED` | Determines whether or with what priority a secure SSL TCP/IP connection will be negotiated. See [`MySqlSslMode`]. | /// | `ssl-ca` | `None` | Sets the name of a file containing a list of trusted SSL Certificate Authorities. | /// | `statement-cache-capacity` | `100` | The maximum number of prepared statements stored in the cache. Set to `0` to disable. | /// | `socket` | `None` | Path to the unix domain socket, which will be used instead of TCP if set. | /// /// # Example /// /// ```rust,no_run /// # async fn example() -> sqlx::Result<()> { /// use sqlx::{Connection, ConnectOptions}; /// use sqlx::mysql::{MySqlConnectOptions, MySqlConnection, MySqlPool, MySqlSslMode}; /// /// // URL connection string /// let conn = MySqlConnection::connect("mysql://root:password@localhost/db").await?; /// /// // Manually-constructed options /// let conn = MySqlConnectOptions::new() /// .host("localhost") /// .username("root") /// .password("password") /// .database("db") /// .connect().await?; /// /// // Modifying options parsed from a string /// let mut opts: MySqlConnectOptions = "mysql://root:password@localhost/db".parse()?; /// /// // Change the log verbosity level for queries. /// // Information about SQL queries is logged at `DEBUG` level by default. /// opts = opts.log_statements(log::LevelFilter::Trace); /// /// let pool = MySqlPool::connect_with(opts).await?; /// # Ok(()) /// # } /// ``` #[derive(Debug, Clone)] pub struct MySqlConnectOptions { pub(crate) host: String, pub(crate) port: u16, pub(crate) socket: Option, pub(crate) username: String, pub(crate) password: Option, pub(crate) database: Option, pub(crate) ssl_mode: MySqlSslMode, pub(crate) ssl_ca: Option, pub(crate) ssl_client_cert: Option, pub(crate) ssl_client_key: Option, pub(crate) statement_cache_capacity: usize, pub(crate) charset: String, pub(crate) collation: Option, pub(crate) log_settings: LogSettings, pub(crate) pipes_as_concat: bool, pub(crate) enable_cleartext_plugin: bool, pub(crate) no_engine_substitution: bool, pub(crate) timezone: Option, pub(crate) set_names: bool, } impl Default for MySqlConnectOptions { fn default() -> Self { Self::new() } } impl MySqlConnectOptions { /// Creates a new, default set of options ready for configuration pub fn new() -> Self { Self { port: 3306, host: String::from("localhost"), socket: None, username: String::from("root"), password: None, database: None, charset: String::from("utf8mb4"), collation: None, ssl_mode: MySqlSslMode::Preferred, ssl_ca: None, ssl_client_cert: None, ssl_client_key: None, statement_cache_capacity: 100, log_settings: Default::default(), pipes_as_concat: true, enable_cleartext_plugin: false, no_engine_substitution: true, timezone: Some(String::from("+00:00")), set_names: true, } } /// Sets the name of the host to connect to. /// /// The default behavior when the host is not specified, /// is to connect to localhost. pub fn host(mut self, host: &str) -> Self { host.clone_into(&mut self.host); self } /// Sets the port to connect to at the server host. /// /// The default port for MySQL is `3306`. pub fn port(mut self, port: u16) -> Self { self.port = port; self } /// Pass a path to a Unix socket. This changes the connection stream from /// TCP to UDS. /// /// By default set to `None`. pub fn socket(mut self, path: impl AsRef) -> Self { self.socket = Some(path.as_ref().to_path_buf()); self } /// Sets the username to connect as. pub fn username(mut self, username: &str) -> Self { username.clone_into(&mut self.username); self } /// Sets the password to connect with. pub fn password(mut self, password: &str) -> Self { self.password = Some(password.to_owned()); self } /// Sets the database name. pub fn database(mut self, database: &str) -> Self { self.database = Some(database.to_owned()); self } /// Sets whether or with what priority a secure SSL TCP/IP connection will be negotiated /// with the server. /// /// By default, the SSL mode is [`Preferred`](MySqlSslMode::Preferred), and the client will /// first attempt an SSL connection but fallback to a non-SSL connection on failure. /// /// # Example /// /// ```rust /// # use sqlx_mysql::{MySqlSslMode, MySqlConnectOptions}; /// let options = MySqlConnectOptions::new() /// .ssl_mode(MySqlSslMode::Required); /// ``` pub fn ssl_mode(mut self, mode: MySqlSslMode) -> Self { self.ssl_mode = mode; self } /// Sets the name of a file containing a list of trusted SSL Certificate Authorities. /// /// # Example /// /// ```rust /// # use sqlx_mysql::{MySqlSslMode, MySqlConnectOptions}; /// let options = MySqlConnectOptions::new() /// .ssl_mode(MySqlSslMode::VerifyCa) /// .ssl_ca("path/to/ca.crt"); /// ``` pub fn ssl_ca(mut self, file_name: impl AsRef) -> Self { self.ssl_ca = Some(CertificateInput::File(file_name.as_ref().to_owned())); self } /// Sets PEM encoded list of trusted SSL Certificate Authorities. /// /// # Example /// /// ```rust /// # use sqlx_mysql::{MySqlSslMode, MySqlConnectOptions}; /// let options = MySqlConnectOptions::new() /// .ssl_mode(MySqlSslMode::VerifyCa) /// .ssl_ca_from_pem(vec![]); /// ``` pub fn ssl_ca_from_pem(mut self, pem_certificate: Vec) -> Self { self.ssl_ca = Some(CertificateInput::Inline(pem_certificate)); self } /// Sets the name of a file containing SSL client certificate. /// /// # Example /// /// ```rust /// # use sqlx_mysql::{MySqlSslMode, MySqlConnectOptions}; /// let options = MySqlConnectOptions::new() /// .ssl_mode(MySqlSslMode::VerifyCa) /// .ssl_client_cert("path/to/client.crt"); /// ``` pub fn ssl_client_cert(mut self, cert: impl AsRef) -> Self { self.ssl_client_cert = Some(CertificateInput::File(cert.as_ref().to_path_buf())); self } /// Sets the SSL client certificate as a PEM-encoded byte slice. /// /// This should be an ASCII-encoded blob that starts with `-----BEGIN CERTIFICATE-----`. /// /// # Example /// Note: embedding SSL certificates and keys in the binary is not advised. /// This is for illustration purposes only. /// /// ```rust /// # use sqlx_mysql::{MySqlSslMode, MySqlConnectOptions}; /// /// const CERT: &[u8] = b"\ /// -----BEGIN CERTIFICATE----- /// /// -----END CERTIFICATE-----"; /// /// let options = MySqlConnectOptions::new() /// .ssl_mode(MySqlSslMode::VerifyCa) /// .ssl_client_cert_from_pem(CERT); /// ``` pub fn ssl_client_cert_from_pem(mut self, cert: impl AsRef<[u8]>) -> Self { self.ssl_client_cert = Some(CertificateInput::Inline(cert.as_ref().to_vec())); self } /// Sets the name of a file containing SSL client key. /// /// # Example /// /// ```rust /// # use sqlx_mysql::{MySqlSslMode, MySqlConnectOptions}; /// let options = MySqlConnectOptions::new() /// .ssl_mode(MySqlSslMode::VerifyCa) /// .ssl_client_key("path/to/client.key"); /// ``` pub fn ssl_client_key(mut self, key: impl AsRef) -> Self { self.ssl_client_key = Some(CertificateInput::File(key.as_ref().to_path_buf())); self } /// Sets the SSL client key as a PEM-encoded byte slice. /// /// This should be an ASCII-encoded blob that starts with `-----BEGIN PRIVATE KEY-----`. /// /// # Example /// Note: embedding SSL certificates and keys in the binary is not advised. /// This is for illustration purposes only. /// /// ```rust /// # use sqlx_mysql::{MySqlSslMode, MySqlConnectOptions}; /// /// const KEY: &[u8] = b"\ /// -----BEGIN PRIVATE KEY----- /// /// -----END PRIVATE KEY-----"; /// /// let options = MySqlConnectOptions::new() /// .ssl_mode(MySqlSslMode::VerifyCa) /// .ssl_client_key_from_pem(KEY); /// ``` pub fn ssl_client_key_from_pem(mut self, key: impl AsRef<[u8]>) -> Self { self.ssl_client_key = Some(CertificateInput::Inline(key.as_ref().to_vec())); self } /// Sets the capacity of the connection's statement cache in a number of stored /// distinct statements. Caching is handled using LRU, meaning when the /// amount of queries hits the defined limit, the oldest statement will get /// dropped. /// /// The default cache capacity is 100 statements. pub fn statement_cache_capacity(mut self, capacity: usize) -> Self { self.statement_cache_capacity = capacity; self } /// Sets the character set for the connection. /// /// The default character set is `utf8mb4`. This is supported from MySQL 5.5.3. /// If you need to connect to an older version, we recommend you to change this to `utf8`. /// /// Implies [`.set_names(true)`][Self::set_names()]. pub fn charset(mut self, charset: &str) -> Self { self.set_names = true; charset.clone_into(&mut self.charset); self } /// Sets the collation for the connection. /// /// The default collation is derived on the server from the `charset`, if set. /// Normally, you should only have to set the `charset`. /// /// If setting this, it is recommended to also set [`charset`][Self::charset()]. /// /// Implies [`.set_names(true)`][Self::set_names()]. pub fn collation(mut self, collation: &str) -> Self { self.set_names = true; self.collation = Some(collation.to_owned()); self } /// Sets the flag that enables or disables the `PIPES_AS_CONCAT` connection setting /// /// The default value is set to true, but some MySql databases such as PlanetScale /// error out with this connection setting so it needs to be set false in such /// cases. pub fn pipes_as_concat(mut self, flag_val: bool) -> Self { self.pipes_as_concat = flag_val; self } /// Enables mysql_clear_password plugin support. /// /// Security Note: /// Sending passwords as cleartext may be a security problem in some /// configurations. Without additional defensive configuration like /// ssl-mode=VERIFY_IDENTITY, an attacker can compromise a router /// and trick the application into divulging its credentials. /// /// It is strongly recommended to set `.ssl_mode` to `Required`, /// `VerifyCa`, or `VerifyIdentity` when enabling cleartext plugin. pub fn enable_cleartext_plugin(mut self, flag_val: bool) -> Self { self.enable_cleartext_plugin = flag_val; self } #[deprecated = "renamed to .no_engine_substitution()"] pub fn no_engine_subsitution(self, flag_val: bool) -> Self { self.no_engine_substitution(flag_val) } /// Flag that enables or disables the `NO_ENGINE_SUBSTITUTION` sql_mode setting after /// connection. /// /// If not set, if the available storage engine specified by a `CREATE TABLE` is not available, /// a warning is given and the default storage engine is used instead. /// /// By default, this is `true` (`NO_ENGINE_SUBSTITUTION` is passed, forbidding engine /// substitution). /// /// pub fn no_engine_substitution(mut self, flag_val: bool) -> Self { self.no_engine_substitution = flag_val; self } /// If `Some`, sets the `time_zone` option to the given string after connecting to the database. /// /// If `None`, no `time_zone` parameter is sent; the server timezone will be used instead. /// /// Defaults to `Some(String::from("+00:00"))` to ensure all timestamps are in UTC. /// /// ### Warning /// Changing this setting from its default will apply an unexpected skew to any /// `time::OffsetDateTime` or `chrono::DateTime` value, whether passed as a parameter or /// decoded as a result. `TIMESTAMP` values are not encoded with their UTC offset in the MySQL /// protocol, so encoding and decoding of these types assumes the server timezone is *always* /// UTC. /// /// If you are changing this option, ensure your application only uses /// `time::PrimitiveDateTime` or `chrono::NaiveDateTime` and that it does not assume these /// timestamps can be placed on a real timeline without applying the proper offset. pub fn timezone(mut self, value: impl Into>) -> Self { self.timezone = value.into(); self } /// If enabled, [`.charset()`] and [`.collation()`] are set with the appropriate command. /// /// If only `.charset()` /// /// This ensures the connection uses the specified character set and collation. /// /// Enabled by default. /// /// ### Warning /// If this is disabled and the default charset is not binary-compatible with UTF-8, query /// strings, column names and string values will likely not decode (or encode) correctly, which /// may result in unexpected errors or garbage outputs at runtime. /// /// For proper functioning, you *must* ensure the server is using a binary-compatible charset, /// such as ASCII or Latin-1 (ISO 8859-1), and that you do not pass any strings containing /// codepoints not supported by said charset. /// /// Instead of disabling this, you may also consider setting [`.charset()`] to a charset that /// is supported by your MySQL or MariaDB server version and compatible with UTF-8. /// /// [`.charset`]: Self::charset() pub fn set_names(mut self, flag_val: bool) -> Self { self.set_names = flag_val; self } } impl MySqlConnectOptions { /// Get the current host. /// /// # Example /// /// ```rust /// # use sqlx_mysql::MySqlConnectOptions; /// let options = MySqlConnectOptions::new() /// .host("127.0.0.1"); /// assert_eq!(options.get_host(), "127.0.0.1"); /// ``` pub fn get_host(&self) -> &str { &self.host } /// Get the server's port. /// /// # Example /// /// ```rust /// # use sqlx_mysql::MySqlConnectOptions; /// let options = MySqlConnectOptions::new() /// .port(6543); /// assert_eq!(options.get_port(), 6543); /// ``` pub fn get_port(&self) -> u16 { self.port } /// Get the socket path. /// /// # Example /// /// ```rust /// # use sqlx_mysql::MySqlConnectOptions; /// let options = MySqlConnectOptions::new() /// .socket("/tmp"); /// assert!(options.get_socket().is_some()); /// ``` pub fn get_socket(&self) -> Option<&PathBuf> { self.socket.as_ref() } /// Get the current username. /// /// # Example /// /// ```rust /// # use sqlx_mysql::MySqlConnectOptions; /// let options = MySqlConnectOptions::new() /// .username("foo"); /// assert_eq!(options.get_username(), "foo"); /// ``` pub fn get_username(&self) -> &str { &self.username } /// Get the current database name. /// /// # Example /// /// ```rust /// # use sqlx_mysql::MySqlConnectOptions; /// let options = MySqlConnectOptions::new() /// .database("postgres"); /// assert!(options.get_database().is_some()); /// ``` pub fn get_database(&self) -> Option<&str> { self.database.as_deref() } /// Get the SSL mode. /// /// # Example /// /// ```rust /// # use sqlx_mysql::{MySqlConnectOptions, MySqlSslMode}; /// let options = MySqlConnectOptions::new(); /// assert!(matches!(options.get_ssl_mode(), MySqlSslMode::Preferred)); /// ``` pub fn get_ssl_mode(&self) -> MySqlSslMode { self.ssl_mode } /// Get the server charset. /// /// # Example /// /// ```rust /// # use sqlx_mysql::MySqlConnectOptions; /// let options = MySqlConnectOptions::new(); /// assert_eq!(options.get_charset(), "utf8mb4"); /// ``` pub fn get_charset(&self) -> &str { &self.charset } /// Get the server collation. /// /// # Example /// /// ```rust /// # use sqlx_mysql::MySqlConnectOptions; /// let options = MySqlConnectOptions::new() /// .collation("collation"); /// assert!(options.get_collation().is_some()); /// ``` pub fn get_collation(&self) -> Option<&str> { self.collation.as_deref() } } ================================================ FILE: sqlx-mysql/src/options/parse.rs ================================================ use std::str::FromStr; use percent_encoding::{percent_decode_str, utf8_percent_encode, NON_ALPHANUMERIC}; use sqlx_core::Url; use crate::{error::Error, MySqlSslMode}; use super::MySqlConnectOptions; impl MySqlConnectOptions { pub(crate) fn parse_from_url(url: &Url) -> Result { let mut options = Self::new(); if let Some(host) = url.host_str() { options = options.host(host); } if let Some(port) = url.port() { options = options.port(port); } let username = url.username(); if !username.is_empty() { options = options.username( &percent_decode_str(username) .decode_utf8() .map_err(Error::config)?, ); } if let Some(password) = url.password() { options = options.password( &percent_decode_str(password) .decode_utf8() .map_err(Error::config)?, ); } let path = url.path().trim_start_matches('/'); if !path.is_empty() { options = options.database( &percent_decode_str(path) .decode_utf8() .map_err(Error::config)?, ); } for (key, value) in url.query_pairs().into_iter() { match &*key { "sslmode" | "ssl-mode" => { options = options.ssl_mode(value.parse().map_err(Error::config)?); } "sslca" | "ssl-ca" => { options = options.ssl_ca(&*value); } "charset" => { options = options.charset(&value); } "collation" => { options = options.collation(&value); } "sslcert" | "ssl-cert" => options = options.ssl_client_cert(&*value), "sslkey" | "ssl-key" => options = options.ssl_client_key(&*value), "statement-cache-capacity" => { options = options.statement_cache_capacity(value.parse().map_err(Error::config)?); } "socket" => { options = options.socket(&*value); } "timezone" | "time-zone" => { options = options.timezone(Some(value.to_string())); } _ => {} } } Ok(options) } pub(crate) fn build_url(&self) -> Url { let mut url = Url::parse(&format!( "mysql://{}@{}:{}", self.username, self.host, self.port )) .expect("BUG: generated un-parseable URL"); if let Some(password) = &self.password { let password = utf8_percent_encode(password, NON_ALPHANUMERIC).to_string(); let _ = url.set_password(Some(&password)); } if let Some(database) = &self.database { url.set_path(database); } let ssl_mode = match self.ssl_mode { MySqlSslMode::Disabled => "DISABLED", MySqlSslMode::Preferred => "PREFERRED", MySqlSslMode::Required => "REQUIRED", MySqlSslMode::VerifyCa => "VERIFY_CA", MySqlSslMode::VerifyIdentity => "VERIFY_IDENTITY", }; url.query_pairs_mut().append_pair("ssl-mode", ssl_mode); if let Some(ssl_ca) = &self.ssl_ca { url.query_pairs_mut() .append_pair("ssl-ca", &ssl_ca.to_string()); } url.query_pairs_mut().append_pair("charset", &self.charset); if let Some(collation) = &self.collation { url.query_pairs_mut().append_pair("charset", collation); } if let Some(ssl_client_cert) = &self.ssl_client_cert { url.query_pairs_mut() .append_pair("ssl-cert", &ssl_client_cert.to_string()); } if let Some(ssl_client_key) = &self.ssl_client_key { url.query_pairs_mut() .append_pair("ssl-key", &ssl_client_key.to_string()); } url.query_pairs_mut().append_pair( "statement-cache-capacity", &self.statement_cache_capacity.to_string(), ); if let Some(socket) = &self.socket { url.query_pairs_mut() .append_pair("socket", &socket.to_string_lossy()); } url } } impl FromStr for MySqlConnectOptions { type Err = Error; fn from_str(s: &str) -> Result { let url: Url = s.parse().map_err(Error::config)?; Self::parse_from_url(&url) } } #[test] fn it_parses_username_with_at_sign_correctly() { let url = "mysql://user@hostname:password@hostname:5432/database"; let opts = MySqlConnectOptions::from_str(url).unwrap(); assert_eq!("user@hostname", &opts.username); } #[test] fn it_parses_password_with_non_ascii_chars_correctly() { let url = "mysql://username:p@ssw0rd@hostname:5432/database"; let opts = MySqlConnectOptions::from_str(url).unwrap(); assert_eq!(Some("p@ssw0rd".into()), opts.password); } #[test] fn it_returns_the_parsed_url() { let url = "mysql://username:p@ssw0rd@hostname:3306/database"; let opts = MySqlConnectOptions::from_str(url).unwrap(); let mut expected_url = Url::parse(url).unwrap(); // MySqlConnectOptions defaults let query_string = "ssl-mode=PREFERRED&charset=utf8mb4&statement-cache-capacity=100"; expected_url.set_query(Some(query_string)); assert_eq!(expected_url, opts.build_url()); } #[test] fn it_parses_timezone() { let opts: MySqlConnectOptions = "mysql://user:password@hostname/database?timezone=%2B08:00" .parse() .unwrap(); assert_eq!(opts.timezone.as_deref(), Some("+08:00")); let opts: MySqlConnectOptions = "mysql://user:password@hostname/database?time-zone=%2B08:00" .parse() .unwrap(); assert_eq!(opts.timezone.as_deref(), Some("+08:00")); } ================================================ FILE: sqlx-mysql/src/options/ssl_mode.rs ================================================ use crate::error::Error; use std::str::FromStr; /// Options for controlling the desired security state of the connection to the MySQL server. /// /// It is used by the [`ssl_mode`](super::MySqlConnectOptions::ssl_mode) method. #[derive(Debug, Clone, Copy, Default)] pub enum MySqlSslMode { /// Establish an unencrypted connection. Disabled, /// Establish an encrypted connection if the server supports encrypted connections, falling /// back to an unencrypted connection if an encrypted connection cannot be established. /// /// This is the default if `ssl_mode` is not specified. #[default] Preferred, /// Establish an encrypted connection if the server supports encrypted connections. /// The connection attempt fails if an encrypted connection cannot be established. Required, /// Like `Required`, but additionally verify the server Certificate Authority (CA) /// certificate against the configured CA certificates. The connection attempt fails /// if no valid matching CA certificates are found. VerifyCa, /// Like `VerifyCa`, but additionally perform host name identity verification by /// checking the host name the client uses for connecting to the server against the /// identity in the certificate that the server sends to the client. VerifyIdentity, } impl FromStr for MySqlSslMode { type Err = Error; fn from_str(s: &str) -> Result { Ok(match &*s.to_ascii_lowercase() { "disabled" => MySqlSslMode::Disabled, "preferred" => MySqlSslMode::Preferred, "required" => MySqlSslMode::Required, "verify_ca" => MySqlSslMode::VerifyCa, "verify_identity" => MySqlSslMode::VerifyIdentity, _ => { return Err(Error::Configuration( format!("unknown value {s:?} for `ssl_mode`").into(), )); } }) } } ================================================ FILE: sqlx-mysql/src/protocol/auth.rs ================================================ use std::str::FromStr; use crate::error::Error; #[derive(Debug, Copy, Clone)] // These have all the same suffix but they match the auth plugin names. #[allow(clippy::enum_variant_names)] pub enum AuthPlugin { MySqlNativePassword, CachingSha2Password, Sha256Password, MySqlClearPassword, } impl AuthPlugin { pub(crate) fn name(self) -> &'static str { match self { AuthPlugin::MySqlNativePassword => "mysql_native_password", AuthPlugin::CachingSha2Password => "caching_sha2_password", AuthPlugin::Sha256Password => "sha256_password", AuthPlugin::MySqlClearPassword => "mysql_clear_password", } } } impl FromStr for AuthPlugin { type Err = Error; fn from_str(s: &str) -> Result { match s { "mysql_native_password" => Ok(AuthPlugin::MySqlNativePassword), "caching_sha2_password" => Ok(AuthPlugin::CachingSha2Password), "sha256_password" => Ok(AuthPlugin::Sha256Password), "mysql_clear_password" => Ok(AuthPlugin::MySqlClearPassword), _ => Err(err_protocol!("unknown authentication plugin: {}", s)), } } } ================================================ FILE: sqlx-mysql/src/protocol/capabilities.rs ================================================ // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/group__group__cs__capabilities__flags.html // https://mariadb.com/kb/en/library/connection/#capabilities // // MySQL defines the capabilities flags as fitting in an `int<4>` but MariaDB // extends this with more bits sent in a separate field. // For simplicity, we've chosen to combine these into one type. bitflags::bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Capabilities: u64 { // [MariaDB] MySQL compatibility const MYSQL = 1; // [*] Send found rows instead of affected rows in EOF_Packet. const FOUND_ROWS = 2; // Get all column flags. const LONG_FLAG = 4; // [*] Database (schema) name can be specified on connect in Handshake Response Packet. const CONNECT_WITH_DB = 8; // Don't allow database.table.column const NO_SCHEMA = 16; // [*] Compression protocol supported const COMPRESS = 32; // Special handling of ODBC behavior. const ODBC = 64; // Can use LOAD DATA LOCAL const LOCAL_FILES = 128; // [*] Ignore spaces before '(' const IGNORE_SPACE = 256; // [*] New 4.1+ protocol const PROTOCOL_41 = 512; // This is an interactive client const INTERACTIVE = 1024; // Use SSL encryption for this session const SSL = 2048; // Client knows about transactions const TRANSACTIONS = 8192; // 4.1+ authentication const SECURE_CONNECTION = 1 << 15; // Enable/disable multi-statement support for COM_QUERY *and* COM_STMT_PREPARE const MULTI_STATEMENTS = 1 << 16; // Enable/disable multi-results for COM_QUERY const MULTI_RESULTS = 1 << 17; // Enable/disable multi-results for COM_STMT_PREPARE const PS_MULTI_RESULTS = 1 << 18; // Client supports plugin authentication const PLUGIN_AUTH = 1 << 19; // Client supports connection attributes const CONNECT_ATTRS = 1 << 20; // Enable authentication response packet to be larger than 255 bytes. const PLUGIN_AUTH_LENENC_DATA = 1 << 21; // Don't close the connection for a user account with expired password. const CAN_HANDLE_EXPIRED_PASSWORDS = 1 << 22; // Capable of handling server state change information. const SESSION_TRACK = 1 << 23; // Client no longer needs EOF_Packet and will use OK_Packet instead. const DEPRECATE_EOF = 1 << 24; // Support ZSTD protocol compression const ZSTD_COMPRESSION_ALGORITHM = 1 << 26; // Verify server certificate const SSL_VERIFY_SERVER_CERT = 1 << 30; // The client can handle optional metadata information in the resultset const OPTIONAL_RESULTSET_METADATA = 1 << 25; // Don't reset the options after an unsuccessful connect const REMEMBER_OPTIONS = 1 << 31; // Extended capabilities (MariaDB only, as of writing) // Client support progress indicator (since 10.2) const MARIADB_CLIENT_PROGRESS = 1 << 32; // Permit COM_MULTI protocol const MARIADB_CLIENT_MULTI = 1 << 33; // Permit bulk insert const MARIADB_CLIENT_STMT_BULK_OPERATIONS = 1 << 34; // Add extended metadata information const MARIADB_CLIENT_EXTENDED_TYPE_INFO = 1 << 35; // Permit skipping metadata const MARIADB_CLIENT_CACHE_METADATA = 1 << 36; // when enabled, indicate that Bulk command can use STMT_BULK_FLAG_SEND_UNIT_RESULTS flag // that permit to return a result-set of all affected rows and auto-increment values const MARIADB_CLIENT_BULK_UNIT_RESULTS = 1 << 37; } } ================================================ FILE: sqlx-mysql/src/protocol/connect/auth_switch.rs ================================================ use bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::ProtocolEncode; use crate::io::{BufExt, ProtocolDecode}; use crate::protocol::auth::AuthPlugin; use crate::protocol::Capabilities; // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_connection_phase_packets_protocol_auth_switch_request.html #[derive(Debug)] pub struct AuthSwitchRequest { pub plugin: AuthPlugin, pub data: Bytes, } impl ProtocolDecode<'_, bool> for AuthSwitchRequest { fn decode_with(mut buf: Bytes, enable_cleartext_plugin: bool) -> Result { let header = buf.get_u8(); if header != 0xfe { return Err(err_protocol!( "expected 0xfe (AUTH_SWITCH) but found 0x{:x}", header )); } let plugin = buf.get_str_nul()?.parse()?; if matches!(plugin, AuthPlugin::MySqlClearPassword) && !enable_cleartext_plugin { return Err(err_protocol!("mysql_cleartext_plugin disabled")); } if matches!(plugin, AuthPlugin::MySqlClearPassword) && buf.is_empty() { // Contrary to the MySQL protocol, AWS Aurora with IAM sends // no data. That is fine because the mysql_clear_password says to // ignore any data sent. // See: https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_authentication_methods_clear_text_password.html return Ok(Self { plugin, data: Bytes::new(), }); } // See: https://github.com/mysql/mysql-server/blob/ea7d2e2d16ac03afdd9cb72a972a95981107bf51/sql/auth/sha2_password.cc#L942 if buf.len() != 21 { return Err(err_protocol!( "expected 21 bytes but found {} bytes", buf.len() )); } let data = buf.get_bytes(20); buf.advance(1); // NUL-terminator Ok(Self { plugin, data }) } } #[derive(Debug)] pub struct AuthSwitchResponse(pub Vec); impl ProtocolEncode<'_, Capabilities> for AuthSwitchResponse { fn encode_with(&self, buf: &mut Vec, _: Capabilities) -> Result<(), Error> { buf.extend_from_slice(&self.0); Ok(()) } } #[test] fn test_decode_auth_switch_packet_data() { const AUTH_SWITCH_NO_DATA: &[u8] = b"\xfecaching_sha2_password\x00abcdefghijabcdefghij\x00"; let p = AuthSwitchRequest::decode_with(AUTH_SWITCH_NO_DATA.into(), true).unwrap(); assert!(matches!(p.plugin, AuthPlugin::CachingSha2Password)); assert_eq!(p.data, &b"abcdefghijabcdefghij"[..]); } #[test] fn test_decode_auth_switch_cleartext_disabled() { const AUTH_SWITCH_CLEARTEXT: &[u8] = b"\xfemysql_clear_password\x00abcdefghijabcdefghij\x00"; let e = AuthSwitchRequest::decode_with(AUTH_SWITCH_CLEARTEXT.into(), false).unwrap_err(); let e_str = e.to_string(); let expected = "encountered unexpected or invalid data: mysql_cleartext_plugin disabled"; assert!( // Don't want to assert the full string since it contains the module path now. e_str.starts_with(expected), "expected error string to start with {expected:?}, got {e_str:?}" ); } #[test] fn test_decode_auth_switch_packet_no_data() { const AUTH_SWITCH_NO_DATA: &[u8] = b"\xfemysql_clear_password\x00"; let p = AuthSwitchRequest::decode_with(AUTH_SWITCH_NO_DATA.into(), true).unwrap(); assert!(matches!(p.plugin, AuthPlugin::MySqlClearPassword)); assert_eq!(p.data, Bytes::new()); } ================================================ FILE: sqlx-mysql/src/protocol/connect/handshake.rs ================================================ use bytes::buf::Chain; use bytes::{Buf, Bytes}; use std::cmp; use crate::error::Error; use crate::io::{BufExt, ProtocolDecode}; use crate::protocol::auth::AuthPlugin; use crate::protocol::response::Status; use crate::protocol::Capabilities; // https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake // https://mariadb.com/kb/en/connection/#initial-handshake-packet #[derive(Debug)] pub(crate) struct Handshake { #[allow(unused)] pub(crate) protocol_version: u8, pub(crate) server_version: String, #[allow(unused)] pub(crate) connection_id: u32, pub(crate) server_capabilities: Capabilities, #[allow(unused)] pub(crate) server_default_collation: u8, #[allow(unused)] pub(crate) status: Status, pub(crate) auth_plugin: Option, pub(crate) auth_plugin_data: Chain, } impl ProtocolDecode<'_> for Handshake { fn decode_with(mut buf: Bytes, _: ()) -> Result { let protocol_version = buf.get_u8(); // int<1> let server_version = buf.get_str_nul()?; // string let connection_id = buf.get_u32_le(); // int<4> let auth_plugin_data_1 = buf.get_bytes(8); // string<8> buf.advance(1); // reserved: string<1> let capabilities_1 = buf.get_u16_le(); // int<2> let mut capabilities = Capabilities::from_bits_truncate(capabilities_1.into()); let collation = buf.get_u8(); // int<1> let status = Status::from_bits_truncate(buf.get_u16_le()); let capabilities_2 = buf.get_u16_le(); // int<2> capabilities |= Capabilities::from_bits_truncate(((capabilities_2 as u32) << 16).into()); let auth_plugin_data_len = if capabilities.contains(Capabilities::PLUGIN_AUTH) { buf.get_u8() } else { buf.advance(1); // int<1> 0 }; buf.advance(6); // reserved: string<6> if capabilities.contains(Capabilities::MYSQL) { buf.advance(4); // reserved: string<4> } else { let capabilities_3 = buf.get_u32_le(); // int<4> capabilities |= Capabilities::from_bits_truncate((capabilities_3 as u64) << 32); } let auth_plugin_data_2 = if capabilities.contains(Capabilities::SECURE_CONNECTION) { let len = cmp::max(auth_plugin_data_len.saturating_sub(9), 12); let v = buf.get_bytes(len as usize); buf.advance(1); // NUL-terminator v } else { Bytes::new() }; let auth_plugin = if capabilities.contains(Capabilities::PLUGIN_AUTH) { Some(buf.get_str_nul()?.parse()?) } else { None }; Ok(Self { protocol_version, server_version, connection_id, server_default_collation: collation, status, server_capabilities: capabilities, auth_plugin, auth_plugin_data: auth_plugin_data_1.chain(auth_plugin_data_2), }) } } #[test] fn test_decode_handshake_mysql_8_0_18() { const HANDSHAKE_MYSQL_8_0_18: &[u8] = b"\n8.0.18\x00\x19\x00\x00\x00\x114aB0c\x06g\x00\xff\xff\xff\x02\x00\xff\xc7\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00tL\x03s\x0f[4\rl4. \x00caching_sha2_password\x00"; let p = Handshake::decode(HANDSHAKE_MYSQL_8_0_18.into()).unwrap(); assert_eq!(p.protocol_version, 10); assert_eq!( p.server_capabilities, Capabilities::MYSQL | Capabilities::FOUND_ROWS | Capabilities::LONG_FLAG | Capabilities::CONNECT_WITH_DB | Capabilities::NO_SCHEMA | Capabilities::COMPRESS | Capabilities::ODBC | Capabilities::LOCAL_FILES | Capabilities::IGNORE_SPACE | Capabilities::PROTOCOL_41 | Capabilities::INTERACTIVE | Capabilities::SSL | Capabilities::TRANSACTIONS | Capabilities::SECURE_CONNECTION | Capabilities::MULTI_STATEMENTS | Capabilities::MULTI_RESULTS | Capabilities::PS_MULTI_RESULTS | Capabilities::PLUGIN_AUTH | Capabilities::CONNECT_ATTRS | Capabilities::PLUGIN_AUTH_LENENC_DATA | Capabilities::CAN_HANDLE_EXPIRED_PASSWORDS | Capabilities::SESSION_TRACK | Capabilities::DEPRECATE_EOF | Capabilities::ZSTD_COMPRESSION_ALGORITHM | Capabilities::SSL_VERIFY_SERVER_CERT | Capabilities::OPTIONAL_RESULTSET_METADATA | Capabilities::REMEMBER_OPTIONS, ); assert_eq!(p.server_default_collation, 255); assert!(p.status.contains(Status::SERVER_STATUS_AUTOCOMMIT)); assert!(matches!( p.auth_plugin, Some(AuthPlugin::CachingSha2Password) )); assert_eq!( &*p.auth_plugin_data.into_iter().collect::>(), &[17, 52, 97, 66, 48, 99, 6, 103, 116, 76, 3, 115, 15, 91, 52, 13, 108, 52, 46, 32,] ); } #[test] fn test_decode_handshake_mariadb_10_4_7() { const HANDSHAKE_MARIA_DB_10_4_7: &[u8] = b"\n5.5.5-10.4.7-MariaDB-1:10.4.7+maria~bionic\x00\x0b\x00\x00\x00t6L\\j\"dS\x00\xfe\xf7\x08\x02\x00\xff\x81\x15\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00U14Oph9\">(), &[116, 54, 76, 92, 106, 34, 100, 83, 85, 49, 52, 79, 112, 104, 57, 34, 60, 72, 53, 110,] ); } ================================================ FILE: sqlx-mysql/src/protocol/connect/handshake_response.rs ================================================ use crate::io::MySqlBufMutExt; use crate::io::{BufMutExt, ProtocolEncode}; use crate::protocol::auth::AuthPlugin; use crate::protocol::connect::ssl_request::SslRequest; use crate::protocol::Capabilities; // https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse // https://mariadb.com/kb/en/connection/#client-handshake-response #[derive(Debug)] pub struct HandshakeResponse<'a> { pub database: Option<&'a str>, /// Max size of a command packet that the client wants to send to the server pub max_packet_size: u32, /// Default charset (collation ID < 256) for the connection pub charset: u8, /// Name of the SQL account which client wants to log in pub username: &'a str, /// Authentication method used by the client pub auth_plugin: Option, /// Opaque authentication response pub auth_response: Option<&'a [u8]>, } impl ProtocolEncode<'_, Capabilities> for HandshakeResponse<'_> { fn encode_with( &self, buf: &mut Vec, mut context: Capabilities, ) -> Result<(), crate::Error> { if self.auth_plugin.is_none() { // ensure PLUGIN_AUTH is set *only* if we have a defined plugin context.remove(Capabilities::PLUGIN_AUTH); } // NOTE: Half of this packet is identical to the SSL Request packet SslRequest { max_packet_size: self.max_packet_size, charset: self.charset, } .encode_with(buf, context)?; buf.put_str_nul(self.username); if context.contains(Capabilities::PLUGIN_AUTH_LENENC_DATA) { buf.put_bytes_lenenc(self.auth_response.unwrap_or_default()); } else if context.contains(Capabilities::SECURE_CONNECTION) { let response = self.auth_response.unwrap_or_default(); let response_len = u8::try_from(response.len()) .map_err(|_| err_protocol!("auth_response.len() too long: {}", response.len()))?; buf.push(response_len); buf.extend(response); } else { buf.push(0); } if context.contains(Capabilities::CONNECT_WITH_DB) { if let Some(database) = &self.database { buf.put_str_nul(database); } else { buf.push(0); } } if context.contains(Capabilities::PLUGIN_AUTH) { if let Some(plugin) = &self.auth_plugin { buf.put_str_nul(plugin.name()); } else { buf.push(0); } } Ok(()) } } ================================================ FILE: sqlx-mysql/src/protocol/connect/mod.rs ================================================ //! Connection Phase //! //! mod auth_switch; mod handshake; mod handshake_response; mod ssl_request; pub(crate) use auth_switch::{AuthSwitchRequest, AuthSwitchResponse}; pub(crate) use handshake::Handshake; pub(crate) use handshake_response::HandshakeResponse; pub(crate) use ssl_request::SslRequest; ================================================ FILE: sqlx-mysql/src/protocol/connect/ssl_request.rs ================================================ use crate::io::ProtocolEncode; use crate::protocol::Capabilities; // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_connection_phase_packets_protocol_handshake_response.html // https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::SSLRequest #[derive(Debug)] pub struct SslRequest { pub max_packet_size: u32, pub charset: u8, } impl ProtocolEncode<'_, Capabilities> for SslRequest { fn encode_with(&self, buf: &mut Vec, context: Capabilities) -> Result<(), crate::Error> { // truncation is intended #[allow(clippy::cast_possible_truncation)] buf.extend(&(context.bits() as u32).to_le_bytes()); buf.extend(&self.max_packet_size.to_le_bytes()); buf.push(self.charset); // reserved: string<19> buf.extend(&[0_u8; 19]); if context.contains(Capabilities::MYSQL) { // reserved: string<4> buf.extend(&[0_u8; 4]); } else { // extended client capabilities (MariaDB-specified): int<4> buf.extend(&((context.bits() >> 32) as u32).to_le_bytes()); } Ok(()) } } ================================================ FILE: sqlx-mysql/src/protocol/mod.rs ================================================ pub(crate) mod auth; mod capabilities; pub(crate) mod connect; mod packet; pub(crate) mod response; mod row; pub(crate) mod statement; pub(crate) mod text; pub(crate) use capabilities::Capabilities; pub(crate) use packet::Packet; pub(crate) use row::Row; ================================================ FILE: sqlx-mysql/src/protocol/packet.rs ================================================ use std::cmp::min; use std::ops::{Deref, DerefMut}; use bytes::Bytes; use crate::error::Error; use crate::io::{ProtocolDecode, ProtocolEncode}; use crate::protocol::response::{EofPacket, OkPacket}; use crate::protocol::Capabilities; #[derive(Debug)] pub struct Packet(pub(crate) T); impl<'en, 'stream, T> ProtocolEncode<'stream, (Capabilities, &'stream mut u8)> for Packet where T: ProtocolEncode<'en, Capabilities>, { fn encode_with( &self, buf: &mut Vec, (capabilities, sequence_id): (Capabilities, &'stream mut u8), ) -> Result<(), Error> { let mut next_header = |len: u32| { let mut buf = len.to_le_bytes(); buf[3] = *sequence_id; *sequence_id = sequence_id.wrapping_add(1); buf }; // reserve space to write the prefixed length let offset = buf.len(); buf.extend(&[0_u8; 4]); // encode the payload self.0.encode_with(buf, capabilities)?; // determine the length of the encoded payload // and write to our reserved space let len = buf.len() - offset - 4; let header = &mut buf[offset..]; // // `min(.., 0xFF_FF_FF)` cannot overflow #[allow(clippy::cast_possible_truncation)] header[..4].copy_from_slice(&next_header(min(len, 0xFF_FF_FF) as u32)); // add more packets if we need to split the data if len >= 0xFF_FF_FF { let rest = buf.split_off(offset + 4 + 0xFF_FF_FF); let mut chunks = rest.chunks_exact(0xFF_FF_FF); for chunk in chunks.by_ref() { buf.reserve(chunk.len() + 4); // `chunk.len() == 0xFF_FF_FF` #[allow(clippy::cast_possible_truncation)] buf.extend(&next_header(chunk.len() as u32)); buf.extend(chunk); } // this will also handle adding a zero sized packet if the data size is a multiple of 0xFF_FF_FF let remainder = chunks.remainder(); buf.reserve(remainder.len() + 4); // `remainder.len() < 0xFF_FF_FF` #[allow(clippy::cast_possible_truncation)] buf.extend(&next_header(remainder.len() as u32)); buf.extend(remainder); } Ok(()) } } impl Packet { pub(crate) fn decode<'de, T>(self) -> Result where T: ProtocolDecode<'de, ()>, { self.decode_with(()) } pub(crate) fn decode_with<'de, T, C>(self, context: C) -> Result where T: ProtocolDecode<'de, C>, { T::decode_with(self.0, context) } pub(crate) fn ok(self) -> Result { self.decode() } pub(crate) fn eof(self, capabilities: Capabilities) -> Result { if capabilities.contains(Capabilities::DEPRECATE_EOF) { let ok = self.ok()?; Ok(EofPacket { warnings: ok.warnings, status: ok.status, }) } else { self.decode_with(capabilities) } } } impl Deref for Packet { type Target = Bytes; fn deref(&self) -> &Bytes { &self.0 } } impl DerefMut for Packet { fn deref_mut(&mut self) -> &mut Bytes { &mut self.0 } } ================================================ FILE: sqlx-mysql/src/protocol/response/eof.rs ================================================ use bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::ProtocolDecode; use crate::protocol::response::Status; use crate::protocol::Capabilities; /// Marks the end of a result set, returning status and warnings. /// /// # Note /// /// The EOF packet is deprecated as of MySQL 5.7.5. SQLx only uses this packet for MySQL /// prior MySQL versions. #[derive(Debug)] pub struct EofPacket { #[allow(dead_code)] pub warnings: u16, pub status: Status, } impl ProtocolDecode<'_, Capabilities> for EofPacket { fn decode_with(mut buf: Bytes, _: Capabilities) -> Result { let header = buf.get_u8(); if header != 0xfe { return Err(err_protocol!( "expected 0xfe (EOF_Packet) but found 0x{:x}", header )); } let warnings = buf.get_u16_le(); let status = Status::from_bits_truncate(buf.get_u16_le()); Ok(Self { status, warnings }) } } ================================================ FILE: sqlx-mysql/src/protocol/response/err.rs ================================================ use bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::{BufExt, ProtocolDecode}; use crate::protocol::Capabilities; // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_basic_err_packet.html // https://mariadb.com/kb/en/err_packet/ /// Indicates that an error occurred. #[derive(Debug)] pub struct ErrPacket { pub error_code: u16, pub sql_state: Option, pub error_message: String, } impl ProtocolDecode<'_, Capabilities> for ErrPacket { fn decode_with(mut buf: Bytes, capabilities: Capabilities) -> Result { let header = buf.get_u8(); if header != 0xff { return Err(err_protocol!( "expected 0xff (ERR_Packet) but found 0x{:x}", header )); } let error_code = buf.get_u16_le(); let mut sql_state = None; if capabilities.contains(Capabilities::PROTOCOL_41) { // If the next byte is '#' then we have a SQL STATE if buf.starts_with(b"#") { buf.advance(1); sql_state = Some(buf.get_str(5)?); } } let error_message = buf.get_str(buf.len())?; Ok(Self { error_code, sql_state, error_message, }) } } #[test] fn test_decode_err_packet_out_of_order() { const ERR_PACKETS_OUT_OF_ORDER: &[u8] = b"\xff\x84\x04Got packets out of order"; let p = ErrPacket::decode_with(ERR_PACKETS_OUT_OF_ORDER.into(), Capabilities::PROTOCOL_41).unwrap(); assert_eq!(&p.error_message, "Got packets out of order"); assert_eq!(p.error_code, 1156); assert_eq!(p.sql_state, None); } #[test] fn test_decode_err_packet_unknown_database() { const ERR_HANDSHAKE_UNKNOWN_DB: &[u8] = b"\xff\x19\x04#42000Unknown database \'unknown\'"; let p = ErrPacket::decode_with(ERR_HANDSHAKE_UNKNOWN_DB.into(), Capabilities::PROTOCOL_41).unwrap(); assert_eq!(p.error_code, 1049); assert_eq!(p.sql_state.as_deref(), Some("42000")); assert_eq!(&p.error_message, "Unknown database \'unknown\'"); } ================================================ FILE: sqlx-mysql/src/protocol/response/mod.rs ================================================ //! Generic Response Packets //! //! //! mod eof; mod err; mod ok; mod status; pub use eof::EofPacket; pub use err::ErrPacket; pub use ok::OkPacket; pub use status::Status; ================================================ FILE: sqlx-mysql/src/protocol/response/ok.rs ================================================ use bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::MySqlBufExt; use crate::io::ProtocolDecode; use crate::protocol::response::Status; /// Indicates successful completion of a previous command sent by the client. #[derive(Debug)] pub struct OkPacket { pub affected_rows: u64, pub last_insert_id: u64, pub status: Status, pub warnings: u16, } impl ProtocolDecode<'_> for OkPacket { fn decode_with(mut buf: Bytes, _: ()) -> Result { let header = buf.get_u8(); if header != 0 && header != 0xfe { return Err(err_protocol!( "expected 0x00 or 0xfe (OK_Packet) but found 0x{:02x}", header )); } let affected_rows = buf.get_uint_lenenc()?; let last_insert_id = buf.get_uint_lenenc()?; if buf.remaining() < 4 { return Err(err_protocol!( "OK_Packet too short: expected at least 4 more bytes for status+warnings, got {}", buf.remaining() )); } let status = Status::from_bits_truncate(buf.get_u16_le()); let warnings = buf.get_u16_le(); Ok(Self { affected_rows, last_insert_id, status, warnings, }) } } #[test] fn test_decode_ok_packet() { const DATA: &[u8] = b"\x00\x00\x00\x02@\x00\x00"; let p = OkPacket::decode(DATA.into()).unwrap(); assert_eq!(p.affected_rows, 0); assert_eq!(p.last_insert_id, 0); assert_eq!(p.warnings, 0); assert!(p.status.contains(Status::SERVER_STATUS_AUTOCOMMIT)); assert!(p.status.contains(Status::SERVER_SESSION_STATE_CHANGED)); } #[test] fn test_decode_ok_packet_with_info() { // OK packet with 0xfe header and length >= 9 (with appended info) const DATA: &[u8] = b"\xfe\x01\x00\x02\x00\x00\x00\x05\x09info data"; let p = OkPacket::decode(DATA.into()).unwrap(); assert_eq!(p.affected_rows, 1); assert_eq!(p.last_insert_id, 0); assert_eq!(p.warnings, 0); assert!(p.status.contains(Status::SERVER_STATUS_AUTOCOMMIT)); } #[test] fn test_decode_ok_packet_with_extended_info() { // OK packet with 0xfe header, affected rows, last insert id, and extended info const DATA: &[u8] = b"\xfe\x05\x64\x02\x00\x01\x00\x0e\x14extended information"; let p = OkPacket::decode(DATA.into()).unwrap(); assert_eq!(p.affected_rows, 5); assert_eq!(p.last_insert_id, 100); assert_eq!(p.warnings, 1); assert!(p.status.contains(Status::SERVER_STATUS_AUTOCOMMIT)); } #[test] fn test_decode_ok_packet_truncated() { const DATA: &[u8] = b"\x00\x00\x00\x01"; let err = OkPacket::decode(DATA.into()).unwrap_err(); assert!(matches!(err, Error::Protocol(_)), "{err}"); } ================================================ FILE: sqlx-mysql/src/protocol/response/status.rs ================================================ // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/mysql__com_8h.html#a1d854e841086925be1883e4d7b4e8cad // https://mariadb.com/kb/en/library/mariadb-connectorc-types-and-definitions/#server-status bitflags::bitflags! { #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] pub struct Status: u16 { // Is raised when a multi-statement transaction has been started, either explicitly, // by means of BEGIN or COMMIT AND CHAIN, or implicitly, by the first // transactional statement, when autocommit=off. const SERVER_STATUS_IN_TRANS = 1; // Autocommit mode is set const SERVER_STATUS_AUTOCOMMIT = 2; // Multi query - next query exists. const SERVER_MORE_RESULTS_EXISTS = 8; const SERVER_QUERY_NO_GOOD_INDEX_USED = 16; const SERVER_QUERY_NO_INDEX_USED = 32; // When using COM_STMT_FETCH, indicate that current cursor still has result const SERVER_STATUS_CURSOR_EXISTS = 64; // When using COM_STMT_FETCH, indicate that current cursor has finished to send results const SERVER_STATUS_LAST_ROW_SENT = 128; // Database has been dropped const SERVER_STATUS_DB_DROPPED = (1 << 8); // Current escape mode is "no backslash escape" const SERVER_STATUS_NO_BACKSLASH_ESCAPES = (1 << 9); // A DDL change did have an impact on an existing PREPARE (an automatic // re-prepare has been executed) const SERVER_STATUS_METADATA_CHANGED = (1 << 10); // Last statement took more than the time value specified // in server variable long_query_time. const SERVER_QUERY_WAS_SLOW = (1 << 11); // This result-set contain stored procedure output parameter. const SERVER_PS_OUT_PARAMS = (1 << 12); // Current transaction is a read-only transaction. const SERVER_STATUS_IN_TRANS_READONLY = (1 << 13); // This status flag, when on, implies that one of the state information has changed // on the server because of the execution of the last statement. const SERVER_SESSION_STATE_CHANGED = (1 << 14); } } ================================================ FILE: sqlx-mysql/src/protocol/row.rs ================================================ use std::ops::Range; use bytes::Bytes; #[derive(Debug)] pub(crate) struct Row { pub(crate) storage: Bytes, pub(crate) values: Vec>>, } impl Row { pub(crate) fn get(&self, index: usize) -> Option<&[u8]> { self.values[index].clone().map(|col| &self.storage[col]) } } ================================================ FILE: sqlx-mysql/src/protocol/statement/execute.rs ================================================ use crate::io::ProtocolEncode; use crate::protocol::text::ColumnFlags; use crate::protocol::Capabilities; use crate::MySqlArguments; // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_com_stmt_execute.html #[derive(Debug)] pub struct Execute<'q> { pub statement: u32, pub arguments: &'q MySqlArguments, } impl ProtocolEncode<'_, Capabilities> for Execute<'_> { fn encode_with(&self, buf: &mut Vec, _: Capabilities) -> Result<(), crate::Error> { buf.push(0x17); // COM_STMT_EXECUTE buf.extend(&self.statement.to_le_bytes()); buf.push(0); // NO_CURSOR buf.extend(&1_u32.to_le_bytes()); // iterations (always 1): int<4> if !self.arguments.types.is_empty() { buf.extend_from_slice(&self.arguments.null_bitmap); buf.push(1); // send type to server for ty in &self.arguments.types { buf.push(ty.r#type as u8); buf.push(if ty.flags.contains(ColumnFlags::UNSIGNED) { 0x80 } else { 0 }); } buf.extend(&*self.arguments.values); } Ok(()) } } ================================================ FILE: sqlx-mysql/src/protocol/statement/mod.rs ================================================ mod execute; mod prepare; mod prepare_ok; mod row; mod stmt_close; pub(crate) use execute::Execute; pub(crate) use prepare::Prepare; pub(crate) use prepare_ok::PrepareOk; pub(crate) use row::BinaryRow; pub(crate) use stmt_close::StmtClose; ================================================ FILE: sqlx-mysql/src/protocol/statement/prepare.rs ================================================ use crate::io::ProtocolEncode; use crate::protocol::Capabilities; // https://dev.mysql.com/doc/internals/en/com-stmt-prepare.html#packet-COM_STMT_PREPARE pub struct Prepare<'a> { pub query: &'a str, } impl ProtocolEncode<'_, Capabilities> for Prepare<'_> { fn encode_with(&self, buf: &mut Vec, _: Capabilities) -> Result<(), crate::Error> { buf.push(0x16); // COM_STMT_PREPARE buf.extend(self.query.as_bytes()); Ok(()) } } ================================================ FILE: sqlx-mysql/src/protocol/statement/prepare_ok.rs ================================================ use bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::ProtocolDecode; use crate::protocol::Capabilities; // https://dev.mysql.com/doc/internals/en/com-stmt-prepare-response.html#packet-COM_STMT_PREPARE_OK #[derive(Debug)] pub(crate) struct PrepareOk { pub(crate) statement_id: u32, pub(crate) columns: u16, pub(crate) params: u16, #[allow(unused)] pub(crate) warnings: u16, } impl ProtocolDecode<'_, Capabilities> for PrepareOk { fn decode_with(buf: Bytes, _: Capabilities) -> Result { const SIZE: usize = 12; let mut slice = buf.get(..SIZE).ok_or_else(|| { err_protocol!("PrepareOk expected 12 bytes but got {} bytes", buf.len()) })?; let status = slice.get_u8(); if status != 0x00 { return Err(err_protocol!( "expected 0x00 (COM_STMT_PREPARE_OK) but found 0x{:02x}", status )); } let statement_id = slice.get_u32_le(); let columns = slice.get_u16_le(); let params = slice.get_u16_le(); slice.advance(1); // reserved: string<1> let warnings = slice.get_u16_le(); Ok(Self { statement_id, columns, params, warnings, }) } } ================================================ FILE: sqlx-mysql/src/protocol/statement/row.rs ================================================ use bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::MySqlBufExt; use crate::io::{BufExt, ProtocolDecode}; use crate::protocol::text::ColumnType; use crate::protocol::Row; use crate::MySqlColumn; // https://dev.mysql.com/doc/internals/en/binary-protocol-resultset-row.html#packet-ProtocolBinary::ResultsetRow // https://dev.mysql.com/doc/internals/en/binary-protocol-value.html #[derive(Debug)] pub(crate) struct BinaryRow(pub(crate) Row); impl<'de> ProtocolDecode<'de, &'de [MySqlColumn]> for BinaryRow { fn decode_with(mut buf: Bytes, columns: &'de [MySqlColumn]) -> Result { let header = buf.get_u8(); if header != 0 { return Err(err_protocol!( "expected 0x00 (ROW) but found 0x{:02x}", header )); } let storage = buf.clone(); let offset = buf.len(); let null_bitmap_len = (columns.len() + 9) / 8; let null_bitmap = buf.get_bytes(null_bitmap_len); let mut values = Vec::with_capacity(columns.len()); for (column_idx, column) in columns.iter().enumerate() { // NOTE: the column index starts at the 3rd bit let column_null_idx = column_idx + 2; let byte_idx = column_null_idx / 8; let bit_idx = column_null_idx % 8; let is_null = null_bitmap[byte_idx] & (1u8 << bit_idx) != 0; if is_null { values.push(None); continue; } // NOTE: MySQL will never generate NULL types for non-NULL values let type_info = &column.type_info; // Unlike Postgres, MySQL does not length-prefix every value in a binary row. // Values are *either* fixed-length or length-prefixed, // so we need to inspect the type code to be sure. let size: usize = match type_info.r#type { // All fixed-length types. ColumnType::LongLong => 8, ColumnType::Long | ColumnType::Int24 => 4, ColumnType::Short | ColumnType::Year => 2, ColumnType::Tiny => 1, ColumnType::Float => 4, ColumnType::Double => 8, // Blobs and strings are prefixed with their length, // which is itself a length-encoded integer. ColumnType::String | ColumnType::VarChar | ColumnType::VarString | ColumnType::Enum | ColumnType::Set | ColumnType::LongBlob | ColumnType::MediumBlob | ColumnType::Blob | ColumnType::TinyBlob | ColumnType::Geometry | ColumnType::Bit | ColumnType::Decimal | ColumnType::Json | ColumnType::NewDecimal => { let size = buf.get_uint_lenenc()?; usize::try_from(size) .map_err(|_| err_protocol!("BLOB length out of range: {size}"))? } // Like strings and blobs, these values are variable-length. // Unlike strings and blobs, however, they exclusively use one byte for length. ColumnType::Time | ColumnType::Timestamp | ColumnType::Date | ColumnType::Datetime => { // Leave the length byte on the front of the value because decoding uses it. buf[0] as usize + 1 } // NOTE: MySQL will never generate NULL types for non-NULL values ColumnType::Null => unreachable!(), }; let offset = offset - buf.len(); values.push(Some(offset..(offset + size))); buf.advance(size); } Ok(BinaryRow(Row { values, storage })) } } ================================================ FILE: sqlx-mysql/src/protocol/statement/stmt_close.rs ================================================ use crate::io::ProtocolEncode; use crate::protocol::Capabilities; // https://dev.mysql.com/doc/internals/en/com-stmt-close.html #[derive(Debug)] pub struct StmtClose { pub statement: u32, } impl ProtocolEncode<'_, Capabilities> for StmtClose { fn encode_with(&self, buf: &mut Vec, _: Capabilities) -> Result<(), crate::Error> { buf.push(0x19); // COM_STMT_CLOSE buf.extend(&self.statement.to_le_bytes()); Ok(()) } } ================================================ FILE: sqlx-mysql/src/protocol/text/column.rs ================================================ use std::str; use crate::collation::Collation; use crate::error::Error; use crate::io::MySqlBufExt; use crate::io::ProtocolDecode; use crate::protocol::Capabilities; use bitflags::bitflags; use bytes::{Buf, Bytes}; // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/group__group__cs__column__definition__flags.html bitflags! { #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(crate) struct ColumnFlags: u16 { /// Field can't be `NULL`. const NOT_NULL = 1; /// Field is part of a primary key. const PRIMARY_KEY = 2; /// Field is part of a unique key. const UNIQUE_KEY = 4; /// Field is part of a multi-part unique or primary key. const MULTIPLE_KEY = 8; /// Field is a blob. const BLOB = 16; /// Field is unsigned. const UNSIGNED = 32; /// Field is zero filled. const ZEROFILL = 64; /// Field is binary. const BINARY = 128; /// Field is an enumeration. const ENUM = 256; /// Field is an auto-increment field. const AUTO_INCREMENT = 512; /// Field is a timestamp. const TIMESTAMP = 1024; /// Field is a set. const SET = 2048; /// Field does not have a default value. const NO_DEFAULT_VALUE = 4096; /// Field is set to NOW on UPDATE. const ON_UPDATE_NOW = 8192; /// Field is a number. const NUM = 32768; } } // https://dev.mysql.com/doc/internals/en/com-query-response.html#column-type #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] #[repr(u8)] pub enum ColumnType { Decimal = 0x00, Tiny = 0x01, Short = 0x02, Long = 0x03, Float = 0x04, Double = 0x05, Null = 0x06, Timestamp = 0x07, LongLong = 0x08, Int24 = 0x09, Date = 0x0a, Time = 0x0b, Datetime = 0x0c, Year = 0x0d, VarChar = 0x0f, Bit = 0x10, Json = 0xf5, NewDecimal = 0xf6, Enum = 0xf7, Set = 0xf8, TinyBlob = 0xf9, MediumBlob = 0xfa, LongBlob = 0xfb, Blob = 0xfc, VarString = 0xfd, String = 0xfe, Geometry = 0xff, } // https://dev.mysql.com/doc/dev/mysql-server/8.0.12/page_protocol_com_query_response_text_resultset_column_definition.html // https://mariadb.com/kb/en/resultset/#column-definition-packet // https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnDefinition41 #[derive(Debug)] pub(crate) struct ColumnDefinition { #[allow(unused)] catalog: Bytes, schema: Bytes, #[allow(unused)] table_alias: Bytes, table: Bytes, alias: Bytes, name: Bytes, pub(crate) collation: Collation, pub(crate) max_size: u32, pub(crate) r#type: ColumnType, pub(crate) flags: ColumnFlags, #[allow(unused)] decimals: u8, } impl ColumnDefinition { // NOTE: strings in-protocol are transmitted according to the client character set // as this is UTF-8, all these strings should be UTF-8 pub(crate) fn schema(&self) -> Result<&str, Error> { str::from_utf8(&self.schema).map_err(Error::protocol) } pub(crate) fn table(&self) -> Result<&str, Error> { str::from_utf8(&self.table).map_err(Error::protocol) } pub(crate) fn name(&self) -> Result<&str, Error> { str::from_utf8(&self.name).map_err(Error::protocol) } pub(crate) fn alias(&self) -> Result<&str, Error> { str::from_utf8(&self.alias).map_err(Error::protocol) } } impl ProtocolDecode<'_, Capabilities> for ColumnDefinition { fn decode_with(mut buf: Bytes, _: Capabilities) -> Result { let catalog = buf.get_bytes_lenenc()?; let schema = buf.get_bytes_lenenc()?; let table_alias = buf.get_bytes_lenenc()?; let table = buf.get_bytes_lenenc()?; let alias = buf.get_bytes_lenenc()?; let name = buf.get_bytes_lenenc()?; let _next_len = buf.get_uint_lenenc()?; // always 0x0c let collation = buf.get_u16_le(); let max_size = buf.get_u32_le(); let type_id = buf.get_u8(); let flags = buf.get_u16_le(); let decimals = buf.get_u8(); Ok(Self { catalog, schema, table_alias, table, alias, name, collation: Collation(collation), max_size, r#type: ColumnType::try_from_u16(type_id)?, flags: ColumnFlags::from_bits_truncate(flags), decimals, }) } } impl ColumnType { pub(crate) fn name(self, flags: ColumnFlags, max_size: Option) -> &'static str { let is_binary = flags.contains(ColumnFlags::BINARY); let is_unsigned = flags.contains(ColumnFlags::UNSIGNED); let is_enum = flags.contains(ColumnFlags::ENUM); match self { ColumnType::Tiny if max_size == Some(1) => "BOOLEAN", ColumnType::Tiny if is_unsigned => "TINYINT UNSIGNED", ColumnType::Short if is_unsigned => "SMALLINT UNSIGNED", ColumnType::Long if is_unsigned => "INT UNSIGNED", ColumnType::Int24 if is_unsigned => "MEDIUMINT UNSIGNED", ColumnType::LongLong if is_unsigned => "BIGINT UNSIGNED", ColumnType::Tiny => "TINYINT", ColumnType::Short => "SMALLINT", ColumnType::Long => "INT", ColumnType::Int24 => "MEDIUMINT", ColumnType::LongLong => "BIGINT", ColumnType::Float => "FLOAT", ColumnType::Double => "DOUBLE", ColumnType::Null => "NULL", ColumnType::Timestamp => "TIMESTAMP", ColumnType::Date => "DATE", ColumnType::Time => "TIME", ColumnType::Datetime => "DATETIME", ColumnType::Year => "YEAR", ColumnType::Bit => "BIT", ColumnType::Enum => "ENUM", ColumnType::Set => "SET", ColumnType::Decimal | ColumnType::NewDecimal => "DECIMAL", ColumnType::Geometry => "GEOMETRY", ColumnType::Json => "JSON", ColumnType::String if is_binary => "BINARY", ColumnType::String if is_enum => "ENUM", ColumnType::VarChar | ColumnType::VarString if is_binary => "VARBINARY", ColumnType::String => "CHAR", ColumnType::VarChar | ColumnType::VarString => "VARCHAR", ColumnType::TinyBlob if is_binary => "TINYBLOB", ColumnType::TinyBlob => "TINYTEXT", ColumnType::Blob if is_binary => "BLOB", ColumnType::Blob => "TEXT", ColumnType::MediumBlob if is_binary => "MEDIUMBLOB", ColumnType::MediumBlob => "MEDIUMTEXT", ColumnType::LongBlob if is_binary => "LONGBLOB", ColumnType::LongBlob => "LONGTEXT", } } pub(crate) fn try_from_u16(id: u8) -> Result { Ok(match id { 0x00 => ColumnType::Decimal, 0x01 => ColumnType::Tiny, 0x02 => ColumnType::Short, 0x03 => ColumnType::Long, 0x04 => ColumnType::Float, 0x05 => ColumnType::Double, 0x06 => ColumnType::Null, 0x07 => ColumnType::Timestamp, 0x08 => ColumnType::LongLong, 0x09 => ColumnType::Int24, 0x0a => ColumnType::Date, 0x0b => ColumnType::Time, 0x0c => ColumnType::Datetime, 0x0d => ColumnType::Year, // [internal] 0x0e => ColumnType::NewDate, 0x0f => ColumnType::VarChar, 0x10 => ColumnType::Bit, // [internal] 0x11 => ColumnType::Timestamp2, // [internal] 0x12 => ColumnType::Datetime2, // [internal] 0x13 => ColumnType::Time2, 0xf5 => ColumnType::Json, 0xf6 => ColumnType::NewDecimal, 0xf7 => ColumnType::Enum, 0xf8 => ColumnType::Set, 0xf9 => ColumnType::TinyBlob, 0xfa => ColumnType::MediumBlob, 0xfb => ColumnType::LongBlob, 0xfc => ColumnType::Blob, 0xfd => ColumnType::VarString, 0xfe => ColumnType::String, 0xff => ColumnType::Geometry, _ => { return Err(err_protocol!("unknown column type 0x{:02x}", id)); } }) } } ================================================ FILE: sqlx-mysql/src/protocol/text/mod.rs ================================================ mod column; mod ping; mod query; mod quit; mod row; pub(crate) use column::{ColumnDefinition, ColumnFlags, ColumnType}; pub(crate) use ping::Ping; pub(crate) use query::Query; pub(crate) use quit::Quit; pub(crate) use row::TextRow; ================================================ FILE: sqlx-mysql/src/protocol/text/ping.rs ================================================ use crate::io::ProtocolEncode; use crate::protocol::Capabilities; // https://dev.mysql.com/doc/internals/en/com-ping.html #[derive(Debug)] pub(crate) struct Ping; impl ProtocolEncode<'_, Capabilities> for Ping { fn encode_with(&self, buf: &mut Vec, _: Capabilities) -> Result<(), crate::Error> { buf.push(0x0e); // COM_PING Ok(()) } } ================================================ FILE: sqlx-mysql/src/protocol/text/query.rs ================================================ use crate::io::ProtocolEncode; use crate::protocol::Capabilities; // https://dev.mysql.com/doc/internals/en/com-query.html #[derive(Debug)] pub(crate) struct Query<'q>(pub(crate) &'q str); impl ProtocolEncode<'_, Capabilities> for Query<'_> { fn encode_with(&self, buf: &mut Vec, _: Capabilities) -> Result<(), crate::Error> { buf.push(0x03); // COM_QUERY buf.extend(self.0.as_bytes()); Ok(()) } } ================================================ FILE: sqlx-mysql/src/protocol/text/quit.rs ================================================ use crate::io::ProtocolEncode; use crate::protocol::Capabilities; // https://dev.mysql.com/doc/internals/en/com-quit.html #[derive(Debug)] pub(crate) struct Quit; impl ProtocolEncode<'_, Capabilities> for Quit { fn encode_with(&self, buf: &mut Vec, _: Capabilities) -> Result<(), crate::Error> { buf.push(0x01); // COM_QUIT Ok(()) } } ================================================ FILE: sqlx-mysql/src/protocol/text/row.rs ================================================ use bytes::{Buf, Bytes}; use crate::column::MySqlColumn; use crate::error::Error; use crate::io::MySqlBufExt; use crate::io::ProtocolDecode; use crate::protocol::Row; #[derive(Debug)] pub(crate) struct TextRow(pub(crate) Row); impl<'de> ProtocolDecode<'de, &'de [MySqlColumn]> for TextRow { fn decode_with(mut buf: Bytes, columns: &'de [MySqlColumn]) -> Result { let storage = buf.clone(); let offset = buf.len(); let mut values = Vec::with_capacity(columns.len()); for c in columns { if buf[0] == 0xfb { // NULL is sent as 0xfb values.push(None); buf.advance(1); } else { let size = buf.get_uint_lenenc()?; if (buf.remaining() as u64) < size { return Err(err_protocol!( "buffer exhausted when reading data for column {:?}; decoded length is {}, but only {} bytes remain in buffer. Malformed packet or protocol error?", c, size, buf.remaining())); } let size = usize::try_from(size) .map_err(|_| err_protocol!("TextRow length out of range: {size}"))?; let offset = offset - buf.len(); values.push(Some(offset..(offset + size))); buf.advance(size); } } Ok(TextRow(Row { values, storage })) } } ================================================ FILE: sqlx-mysql/src/query_result.rs ================================================ use std::iter::{Extend, IntoIterator}; #[derive(Debug, Default)] pub struct MySqlQueryResult { pub(super) rows_affected: u64, pub(super) last_insert_id: u64, } impl MySqlQueryResult { pub fn last_insert_id(&self) -> u64 { self.last_insert_id } pub fn rows_affected(&self) -> u64 { self.rows_affected } } impl Extend for MySqlQueryResult { fn extend>(&mut self, iter: T) { for elem in iter { self.rows_affected += elem.rows_affected; self.last_insert_id = elem.last_insert_id; } } } #[cfg(feature = "any")] /// This conversion attempts to save last_insert_id by converting to i64. impl From for sqlx_core::any::AnyQueryResult { fn from(done: MySqlQueryResult) -> Self { sqlx_core::any::AnyQueryResult { rows_affected: done.rows_affected(), last_insert_id: done.last_insert_id().try_into().ok(), } } } ================================================ FILE: sqlx-mysql/src/row.rs ================================================ use std::sync::Arc; pub(crate) use sqlx_core::row::*; use crate::column::ColumnIndex; use crate::error::Error; use crate::ext::ustr::UStr; use crate::HashMap; use crate::{protocol, MySql, MySqlColumn, MySqlValueFormat, MySqlValueRef}; /// Implementation of [`Row`] for MySQL. pub struct MySqlRow { pub(crate) row: protocol::Row, pub(crate) format: MySqlValueFormat, pub(crate) columns: Arc>, pub(crate) column_names: Arc>, } impl Row for MySqlRow { type Database = MySql; fn columns(&self) -> &[MySqlColumn] { &self.columns } fn try_get_raw(&self, index: I) -> Result, Error> where I: ColumnIndex, { let index = index.index(self)?; let column = &self.columns[index]; let value = self.row.get(index); Ok(MySqlValueRef { format: self.format, row: Some(&self.row.storage), type_info: column.type_info.clone(), value, }) } } impl ColumnIndex for &'_ str { fn index(&self, row: &MySqlRow) -> Result { row.column_names .get(*self) .ok_or_else(|| Error::ColumnNotFound((*self).into())) .copied() } } impl std::fmt::Debug for MySqlRow { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { debug_row(self, f) } } ================================================ FILE: sqlx-mysql/src/statement.rs ================================================ use super::MySqlColumn; use crate::column::ColumnIndex; use crate::error::Error; use crate::ext::ustr::UStr; use crate::HashMap; use crate::{MySql, MySqlArguments, MySqlTypeInfo}; use either::Either; use sqlx_core::sql_str::SqlStr; use std::sync::Arc; pub(crate) use sqlx_core::statement::*; #[derive(Debug, Clone)] pub struct MySqlStatement { pub(crate) sql: SqlStr, pub(crate) metadata: MySqlStatementMetadata, } #[derive(Debug, Default, Clone)] pub(crate) struct MySqlStatementMetadata { pub(crate) columns: Arc>, pub(crate) column_names: Arc>, pub(crate) parameters: usize, } impl Statement for MySqlStatement { type Database = MySql; fn into_sql(self) -> SqlStr { self.sql } fn sql(&self) -> &SqlStr { &self.sql } fn parameters(&self) -> Option> { Some(Either::Right(self.metadata.parameters)) } fn columns(&self) -> &[MySqlColumn] { &self.metadata.columns } impl_statement_query!(MySqlArguments); } impl ColumnIndex for &'_ str { fn index(&self, statement: &MySqlStatement) -> Result { statement .metadata .column_names .get(*self) .ok_or_else(|| Error::ColumnNotFound((*self).into())) .copied() } } ================================================ FILE: sqlx-mysql/src/testing/mod.rs ================================================ use std::future::Future; use std::ops::Deref; use std::str::FromStr; use std::sync::OnceLock; use std::time::Duration; use crate::error::Error; use crate::executor::Executor; use crate::pool::{Pool, PoolOptions}; use crate::query::query; use crate::{MySql, MySqlConnectOptions, MySqlConnection, MySqlDatabaseError}; use sqlx_core::connection::Connection; use sqlx_core::query_builder::QueryBuilder; use sqlx_core::query_scalar::query_scalar; use sqlx_core::sql_str::AssertSqlSafe; pub(crate) use sqlx_core::testing::*; // Using a blocking `OnceLock` here because the critical sections are short. static MASTER_POOL: OnceLock> = OnceLock::new(); impl TestSupport for MySql { fn test_context( args: &TestArgs, ) -> impl Future, Error>> + Send + '_ { test_context(args) } async fn cleanup_test(db_name: &str) -> Result<(), Error> { let mut conn = MASTER_POOL .get() .expect("cleanup_test() invoked outside `#[sqlx::test]`") .acquire() .await?; do_cleanup(&mut conn, db_name).await } async fn cleanup_test_dbs() -> Result, Error> { let url = dotenvy::var("DATABASE_URL").expect("DATABASE_URL must be set"); let mut conn = MySqlConnection::connect(&url).await?; let delete_db_names: Vec = query_scalar("select db_name from _sqlx_test_databases") .fetch_all(&mut conn) .await?; if delete_db_names.is_empty() { return Ok(None); } let mut deleted_db_names = Vec::with_capacity(delete_db_names.len()); let mut builder = QueryBuilder::new("drop database if exists "); for db_name in &delete_db_names { builder.push(db_name); match builder.build().execute(&mut conn).await { Ok(_deleted) => { deleted_db_names.push(db_name); } // Assume a database error just means the DB is still in use. Err(Error::Database(dbe)) => { eprintln!("could not clean test database {db_name:?}: {dbe}") } // Bubble up other errors Err(e) => return Err(e), } builder.reset(); } if deleted_db_names.is_empty() { return Ok(None); } let mut query = QueryBuilder::new("delete from _sqlx_test_databases where db_name in ("); let mut separated = query.separated(","); for db_name in &deleted_db_names { separated.push_bind(db_name); } query.push(")").build().execute(&mut conn).await?; let _ = conn.close().await; Ok(Some(delete_db_names.len())) } async fn snapshot(_conn: &mut Self::Connection) -> Result, Error> { // TODO: I want to get the testing feature out the door so this will have to wait, // but I'm keeping the code around for now because I plan to come back to it. todo!() } } async fn test_context(args: &TestArgs) -> Result, Error> { let url = dotenvy::var("DATABASE_URL").expect("DATABASE_URL must be set"); let master_opts = MySqlConnectOptions::from_str(&url).expect("failed to parse DATABASE_URL"); let pool = PoolOptions::new() // MySql's normal connection limit is 150 plus 1 superuser connection // We don't want to use the whole cap and there may be fuzziness here due to // concurrently running tests anyway. .max_connections(20) // Immediately close master connections. Tokio's I/O streams don't like hopping runtimes. .after_release(|_conn, _| Box::pin(async move { Ok(false) })) .connect_lazy_with(master_opts); let master_pool = match once_lock_try_insert_polyfill(&MASTER_POOL, pool) { Ok(inserted) => inserted, Err((existing, pool)) => { // Sanity checks. assert_eq!( existing.connect_options().host, pool.connect_options().host, "DATABASE_URL changed at runtime, host differs" ); assert_eq!( existing.connect_options().database, pool.connect_options().database, "DATABASE_URL changed at runtime, database differs" ); existing } }; let mut conn = master_pool.acquire().await?; cleanup_old_dbs(&mut conn).await?; // language=MySQL conn.execute( r#" create table if not exists _sqlx_test_databases ( db_name text not null, test_path text not null, created_at timestamp not null default current_timestamp, -- BLOB/TEXT columns can only be used as index keys with a prefix length: -- https://dev.mysql.com/doc/refman/8.4/en/column-indexes.html#column-indexes-prefix primary key(db_name(63)) ); "#, ) .await?; let db_name = MySql::db_name(args); do_cleanup(&mut conn, &db_name).await?; query("insert into _sqlx_test_databases(db_name, test_path) values (?, ?)") .bind(&db_name) .bind(args.test_path) .execute(&mut *conn) .await?; conn.execute(AssertSqlSafe(format!("create database {db_name}"))) .await?; eprintln!("created database {db_name}"); Ok(TestContext { pool_opts: PoolOptions::new() // Don't allow a single test to take all the connections. // Most tests shouldn't require more than 5 connections concurrently, // or else they're likely doing too much in one test. .max_connections(5) // Close connections ASAP if left in the idle queue. .idle_timeout(Some(Duration::from_secs(1))) .parent(master_pool.clone()), connect_opts: master_pool .connect_options() .deref() .clone() .database(&db_name), db_name, }) } async fn do_cleanup(conn: &mut MySqlConnection, db_name: &str) -> Result<(), Error> { let delete_db_command = format!("drop database if exists {db_name};"); conn.execute(AssertSqlSafe(delete_db_command)).await?; query("delete from _sqlx_test_databases where db_name = ?") .bind(db_name) .execute(&mut *conn) .await?; Ok(()) } async fn cleanup_old_dbs(conn: &mut MySqlConnection) -> Result<(), Error> { let res: Result, Error> = query_scalar("select db_id from _sqlx_test_databases") .fetch_all(&mut *conn) .await; let db_ids = match res { Ok(db_ids) => db_ids, Err(e) => { if let Some(dbe) = e.as_database_error() { match dbe.downcast_ref::().number() { // Column `db_id` does not exist: // https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html#error_er_bad_field_error // // The table has already been migrated. 1054 => return Ok(()), // Table `_sqlx_test_databases` does not exist. // No cleanup needed. // https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html#error_er_no_such_table 1146 => return Ok(()), _ => (), } } return Err(e); } }; // Drop old-style test databases. for id in db_ids { match conn .execute(AssertSqlSafe(format!( "drop database if exists _sqlx_test_database_{id}" ))) .await { Ok(_deleted) => (), // Assume a database error just means the DB is still in use. Err(Error::Database(dbe)) => { eprintln!("could not clean old test database _sqlx_test_database_{id}: {dbe}"); } // Bubble up other errors Err(e) => return Err(e), } } conn.execute("drop table if exists _sqlx_test_databases") .await?; Ok(()) } fn once_lock_try_insert_polyfill(this: &OnceLock, value: T) -> Result<&T, (&T, T)> { let mut value = Some(value); let res = this.get_or_init(|| value.take().unwrap()); match value { None => Ok(res), Some(value) => Err((res, value)), } } ================================================ FILE: sqlx-mysql/src/transaction.rs ================================================ use sqlx_core::sql_str::SqlStr; use crate::connection::Waiting; use crate::error::Error; use crate::executor::Executor; use crate::protocol::text::Query; use crate::{MySql, MySqlConnection}; pub(crate) use sqlx_core::transaction::*; /// Implementation of [`TransactionManager`] for MySQL. pub struct MySqlTransactionManager; impl TransactionManager for MySqlTransactionManager { type Database = MySql; async fn begin(conn: &mut MySqlConnection, statement: Option) -> Result<(), Error> { let depth = conn.inner.transaction_depth; let statement = match statement { // custom `BEGIN` statements are not allowed if we're already in a transaction // (we need to issue a `SAVEPOINT` instead) Some(_) if depth > 0 => return Err(Error::InvalidSavePointStatement), Some(statement) => statement, None => begin_ansi_transaction_sql(depth), }; conn.execute(statement).await?; if !conn.in_transaction() { return Err(Error::BeginFailed); } conn.inner.transaction_depth += 1; Ok(()) } async fn commit(conn: &mut MySqlConnection) -> Result<(), Error> { let depth = conn.inner.transaction_depth; if depth > 0 { conn.execute(commit_ansi_transaction_sql(depth)).await?; conn.inner.transaction_depth = depth - 1; } Ok(()) } async fn rollback(conn: &mut MySqlConnection) -> Result<(), Error> { let depth = conn.inner.transaction_depth; if depth > 0 { conn.execute(rollback_ansi_transaction_sql(depth)).await?; conn.inner.transaction_depth = depth - 1; } Ok(()) } fn start_rollback(conn: &mut MySqlConnection) { let depth = conn.inner.transaction_depth; if depth > 0 { conn.inner.stream.waiting.push_back(Waiting::Result); conn.inner.stream.sequence_id = 0; conn.inner .stream .write_packet(Query(rollback_ansi_transaction_sql(depth).as_str())) .expect("BUG: unexpected error queueing ROLLBACK"); conn.inner.transaction_depth = depth - 1; } } fn get_transaction_depth(conn: &MySqlConnection) -> usize { conn.inner.transaction_depth } } ================================================ FILE: sqlx-mysql/src/type_checking.rs ================================================ // Type mappings used by the macros and `Debug` impls. #[allow(unused_imports)] use sqlx_core as sqlx; use crate::MySql; impl_type_checking!( MySql { u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, // ordering is important here as otherwise we might infer strings to be binary // CHAR, VAR_CHAR, TEXT String, // BINARY, VAR_BINARY, BLOB Vec, #[cfg(feature = "json")] sqlx::types::JsonValue, }, ParamChecking::Weak, feature-types: info => info.__type_feature_gate(), // The expansion of the macro automatically applies the correct feature name // and checks `[macros.preferred-crates]` datetime-types: { chrono: { sqlx::types::chrono::NaiveTime, sqlx::types::chrono::NaiveDate, sqlx::types::chrono::NaiveDateTime, sqlx::types::chrono::DateTime, }, time: { sqlx::types::time::Time, sqlx::types::time::Date, sqlx::types::time::PrimitiveDateTime, sqlx::types::time::OffsetDateTime, }, }, numeric-types: { bigdecimal: { sqlx::types::BigDecimal, }, rust_decimal: { sqlx::types::Decimal, }, }, ); ================================================ FILE: sqlx-mysql/src/type_info.rs ================================================ use std::fmt::{self, Display, Formatter}; use crate::collation::Collation; use crate::protocol::text::{ColumnDefinition, ColumnFlags, ColumnType}; pub(crate) use sqlx_core::type_info::*; /// Type information for a MySql type. #[derive(Debug, Clone)] #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] pub struct MySqlTypeInfo { pub(crate) r#type: ColumnType, pub(crate) flags: ColumnFlags, pub(crate) collation: Collation, // [max_size] for integer types, this is (M) in BIT(M) or TINYINT(M) #[cfg_attr(feature = "offline", serde(default))] pub(crate) max_size: Option, } impl MySqlTypeInfo { pub(crate) const fn binary(ty: ColumnType) -> Self { Self { r#type: ty, flags: ColumnFlags::BINARY, collation: Collation::BINARY, max_size: None, } } #[doc(hidden)] pub const fn __enum() -> Self { // Newer versions of MySQL seem to expect that a parameter binding of `MYSQL_TYPE_ENUM` // means that the value is encoded as an integer. // // For "strong" enums inputted as strings, we need to specify this type instead // for wider compatibility. This works on all covered versions of MySQL and MariaDB. // // Annoyingly, MySQL's developer documentation doesn't really explain this anywhere; // this had to be determined experimentally. Self { r#type: ColumnType::String, flags: ColumnFlags::ENUM, collation: Collation::UTF8MB4_GENERAL_CI, max_size: None, } } #[doc(hidden)] pub fn __type_feature_gate(&self) -> Option<&'static str> { match self.r#type { ColumnType::Date | ColumnType::Time | ColumnType::Timestamp | ColumnType::Datetime => { Some("time") } ColumnType::Json => Some("json"), ColumnType::NewDecimal => Some("bigdecimal"), _ => None, } } pub(crate) fn from_column(column: &ColumnDefinition) -> Self { Self { r#type: column.r#type, flags: column.flags, collation: column.collation, max_size: Some(column.max_size), } } } impl Display for MySqlTypeInfo { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.pad(self.name()) } } impl TypeInfo for MySqlTypeInfo { fn is_null(&self) -> bool { matches!(self.r#type, ColumnType::Null) } fn name(&self) -> &str { self.r#type.name(self.flags, self.max_size) } } impl PartialEq for MySqlTypeInfo { fn eq(&self, other: &MySqlTypeInfo) -> bool { if self.r#type != other.r#type { return false; } match self.r#type { ColumnType::Tiny | ColumnType::Short | ColumnType::Long | ColumnType::Int24 | ColumnType::LongLong => { return self.flags.contains(ColumnFlags::UNSIGNED) == other.flags.contains(ColumnFlags::UNSIGNED); } // for string types, check that our charset matches ColumnType::VarChar | ColumnType::Blob | ColumnType::TinyBlob | ColumnType::MediumBlob | ColumnType::LongBlob | ColumnType::String | ColumnType::VarString | ColumnType::Enum => { return self.flags == other.flags; } _ => {} } true } } impl Eq for MySqlTypeInfo {} ================================================ FILE: sqlx-mysql/src/types/bigdecimal.rs ================================================ use bigdecimal::BigDecimal; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::io::MySqlBufMutExt; use crate::protocol::text::ColumnType; use crate::types::Type; use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; impl Type for BigDecimal { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::NewDecimal) } fn compatible(ty: &MySqlTypeInfo) -> bool { matches!(ty.r#type, ColumnType::Decimal | ColumnType::NewDecimal) } } impl Encode<'_, MySql> for BigDecimal { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.put_str_lenenc(&self.to_string()); Ok(IsNull::No) } } impl Decode<'_, MySql> for BigDecimal { fn decode(value: MySqlValueRef<'_>) -> Result { Ok(value.as_str()?.parse()?) } } ================================================ FILE: sqlx-mysql/src/types/bool.rs ================================================ use crate::collation::Collation; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{ protocol::text::{ColumnFlags, ColumnType}, MySql, MySqlTypeInfo, MySqlValueRef, }; impl Type for bool { fn type_info() -> MySqlTypeInfo { // MySQL has no actual `BOOLEAN` type, the type is an alias of `[UNSIGNED] TINYINT(1)` MySqlTypeInfo { flags: ColumnFlags::BINARY | ColumnFlags::UNSIGNED, collation: Collation::BINARY, max_size: Some(1), r#type: ColumnType::Tiny, } } fn compatible(ty: &MySqlTypeInfo) -> bool { matches!( ty.r#type, ColumnType::Tiny | ColumnType::Short | ColumnType::Long | ColumnType::Int24 | ColumnType::LongLong | ColumnType::Bit ) } } impl Encode<'_, MySql> for bool { fn encode_by_ref(&self, buf: &mut Vec) -> Result { >::encode(*self as i8, buf) } } impl Decode<'_, MySql> for bool { fn decode(value: MySqlValueRef<'_>) -> Result { Ok(>::decode(value)? != 0) } } ================================================ FILE: sqlx-mysql/src/types/bytes.rs ================================================ use std::borrow::Cow; use std::rc::Rc; use std::sync::Arc; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::io::MySqlBufMutExt; use crate::protocol::text::ColumnType; use crate::types::Type; use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; impl Type for [u8] { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::Blob) } fn compatible(ty: &MySqlTypeInfo) -> bool { matches!( ty.r#type, ColumnType::VarChar | ColumnType::Blob | ColumnType::TinyBlob | ColumnType::MediumBlob | ColumnType::LongBlob | ColumnType::String | ColumnType::VarString | ColumnType::Enum ) } } impl Encode<'_, MySql> for &'_ [u8] { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.put_bytes_lenenc(self); Ok(IsNull::No) } } impl<'r> Decode<'r, MySql> for &'r [u8] { fn decode(value: MySqlValueRef<'r>) -> Result { value.as_bytes() } } impl Type for Vec { fn type_info() -> MySqlTypeInfo { <[u8] as Type>::type_info() } fn compatible(ty: &MySqlTypeInfo) -> bool { <&[u8] as Type>::compatible(ty) } } impl Encode<'_, MySql> for Vec { fn encode_by_ref(&self, buf: &mut Vec) -> Result { <&[u8] as Encode>::encode(&**self, buf) } } impl Decode<'_, MySql> for Vec { fn decode(value: MySqlValueRef<'_>) -> Result { <&[u8] as Decode>::decode(value).map(ToOwned::to_owned) } } forward_encode_impl!(Arc<[u8]>, &[u8], MySql); forward_encode_impl!(Rc<[u8]>, &[u8], MySql); forward_encode_impl!(Box<[u8]>, &[u8], MySql); forward_encode_impl!(Cow<'_, [u8]>, &[u8], MySql); ================================================ FILE: sqlx-mysql/src/types/chrono.rs ================================================ use bytes::Buf; use chrono::{ DateTime, Datelike, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike, Utc, }; use sqlx_core::database::Database; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::{BoxDynError, UnexpectedNullError}; use crate::protocol::text::ColumnType; use crate::type_info::MySqlTypeInfo; use crate::types::{MySqlTime, MySqlTimeSign, Type}; use crate::{MySql, MySqlValueFormat, MySqlValueRef}; impl Type for DateTime { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::Timestamp) } fn compatible(ty: &MySqlTypeInfo) -> bool { matches!(ty.r#type, ColumnType::Datetime | ColumnType::Timestamp) } } /// Note: assumes the connection's `time_zone` is set to `+00:00` (UTC). impl Encode<'_, MySql> for DateTime { fn encode_by_ref(&self, buf: &mut Vec) -> Result { Encode::::encode(self.naive_utc(), buf) } } /// Note: assumes the connection's `time_zone` is set to `+00:00` (UTC). impl<'r> Decode<'r, MySql> for DateTime { fn decode(value: MySqlValueRef<'r>) -> Result { let naive: NaiveDateTime = Decode::::decode(value)?; Ok(Utc.from_utc_datetime(&naive)) } } impl Type for DateTime { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::Timestamp) } fn compatible(ty: &MySqlTypeInfo) -> bool { matches!(ty.r#type, ColumnType::Datetime | ColumnType::Timestamp) } } /// Note: assumes the connection's `time_zone` is set to `+00:00` (UTC). impl Encode<'_, MySql> for DateTime { fn encode_by_ref(&self, buf: &mut Vec) -> Result { Encode::::encode(self.naive_utc(), buf) } } /// Note: assumes the connection's `time_zone` is set to `+00:00` (UTC). impl<'r> Decode<'r, MySql> for DateTime { fn decode(value: MySqlValueRef<'r>) -> Result { Ok( as Decode<'r, MySql>>::decode(value)?.with_timezone(&Local)) } } impl Type for NaiveTime { fn type_info() -> MySqlTypeInfo { MySqlTime::type_info() } } impl Encode<'_, MySql> for NaiveTime { fn encode_by_ref(&self, buf: &mut Vec) -> Result { let len = naive_time_encoded_len(self); buf.push(len); // NaiveTime is not negative buf.push(0); // Number of days in the interval; always 0 for time-of-day values. // https://mariadb.com/kb/en/resultset-row/#teimstamp-binary-encoding buf.extend_from_slice(&[0_u8; 4]); encode_time(self, len > 8, buf); Ok(IsNull::No) } fn size_hint(&self) -> usize { naive_time_encoded_len(self) as usize + 1 // plus length byte } } /// Decode from a `TIME` value. /// /// ### Errors /// Returns an error if the `TIME` value is negative or exceeds `23:59:59.999999`. impl<'r> Decode<'r, MySql> for NaiveTime { fn decode(value: MySqlValueRef<'r>) -> Result { match value.format() { MySqlValueFormat::Binary => { // Covers most possible failure modes. MySqlTime::decode(value)?.try_into() } // Retaining this parsing for now as it allows us to cross-check our impl. MySqlValueFormat::Text => { let s = value.as_str()?; NaiveTime::parse_from_str(s, "%H:%M:%S%.f").map_err(Into::into) } } } } impl TryFrom for NaiveTime { type Error = BoxDynError; fn try_from(time: MySqlTime) -> Result { NaiveTime::from_hms_micro_opt( time.hours(), time.minutes() as u32, time.seconds() as u32, time.microseconds(), ) .ok_or_else(|| format!("Cannot convert `MySqlTime` value to `NaiveTime`: {time}").into()) } } impl From for chrono::TimeDelta { fn from(time: MySqlTime) -> Self { chrono::TimeDelta::new(time.whole_seconds_signed(), time.subsec_nanos()) .expect("BUG: chrono::TimeDelta should have a greater range than MySqlTime") } } impl TryFrom for MySqlTime { type Error = BoxDynError; fn try_from(value: chrono::TimeDelta) -> Result { let sign = if value < chrono::TimeDelta::zero() { MySqlTimeSign::Negative } else { MySqlTimeSign::Positive }; Ok( // `std::time::Duration` has a greater positive range than `TimeDelta` // which makes it a great intermediate if you ignore the sign. MySqlTime::try_from(value.abs().to_std()?)?.with_sign(sign), ) } } impl Type for chrono::TimeDelta { fn type_info() -> MySqlTypeInfo { MySqlTime::type_info() } } impl<'r> Decode<'r, MySql> for chrono::TimeDelta { fn decode(value: ::ValueRef<'r>) -> Result { Ok(MySqlTime::decode(value)?.into()) } } impl Type for NaiveDate { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::Date) } } impl Encode<'_, MySql> for NaiveDate { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.push(4); encode_date(self, buf)?; Ok(IsNull::No) } fn size_hint(&self) -> usize { 5 } } impl<'r> Decode<'r, MySql> for NaiveDate { fn decode(value: MySqlValueRef<'r>) -> Result { match value.format() { MySqlValueFormat::Binary => { let buf = value.as_bytes()?; // Row decoding should have left the length prefix. if buf.is_empty() { return Err("empty buffer".into()); } decode_date(&buf[1..])?.ok_or_else(|| UnexpectedNullError.into()) } MySqlValueFormat::Text => { let s = value.as_str()?; NaiveDate::parse_from_str(s, "%Y-%m-%d").map_err(Into::into) } } } } impl Type for NaiveDateTime { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::Datetime) } } impl Encode<'_, MySql> for NaiveDateTime { fn encode_by_ref(&self, buf: &mut Vec) -> Result { let len = naive_dt_encoded_len(self); buf.push(len); encode_date(&self.date(), buf)?; if len > 4 { encode_time(&self.time(), len > 7, buf); } Ok(IsNull::No) } fn size_hint(&self) -> usize { naive_dt_encoded_len(self) as usize + 1 // plus length byte } } impl<'r> Decode<'r, MySql> for NaiveDateTime { fn decode(value: MySqlValueRef<'r>) -> Result { match value.format() { MySqlValueFormat::Binary => { let buf = value.as_bytes()?; if buf.is_empty() { return Err("empty buffer".into()); } let len = buf[0]; let date = decode_date(&buf[1..])?.ok_or(UnexpectedNullError)?; let dt = if len > 4 { date.and_time(decode_time(len - 4, &buf[5..])?) } else { date.and_hms_opt(0, 0, 0) .expect("expected `NaiveDate::and_hms_opt(0, 0, 0)` to be valid") }; Ok(dt) } MySqlValueFormat::Text => { let s = value.as_str()?; NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%.f").map_err(Into::into) } } } } fn encode_date(date: &NaiveDate, buf: &mut Vec) -> Result<(), BoxDynError> { // MySQL supports years 1000 - 9999 let year = u16::try_from(date.year()) .map_err(|_| format!("NaiveDateTime out of range for Mysql: {date}"))?; buf.extend_from_slice(&year.to_le_bytes()); // `NaiveDate` guarantees the ranges of these values #[allow(clippy::cast_possible_truncation)] { buf.push(date.month() as u8); buf.push(date.day() as u8); } Ok(()) } fn decode_date(mut buf: &[u8]) -> Result, BoxDynError> { match buf.len() { // MySQL specifies that if there are no bytes, this is all zeros 0 => Ok(None), 4.. => { let year = buf.get_u16_le() as i32; let month = buf[0] as u32; let day = buf[1] as u32; let date = NaiveDate::from_ymd_opt(year, month, day) .ok_or_else(|| format!("server returned invalid date: {year}/{month}/{day}"))?; Ok(Some(date)) } len => Err(format!("expected at least 4 bytes for date, got {len}").into()), } } fn encode_time(time: &NaiveTime, include_micros: bool, buf: &mut Vec) { // `NaiveTime` API guarantees the ranges of these values #[allow(clippy::cast_possible_truncation)] { buf.push(time.hour() as u8); buf.push(time.minute() as u8); buf.push(time.second() as u8); } if include_micros { buf.extend((time.nanosecond() / 1000).to_le_bytes()); } } fn decode_time(len: u8, mut buf: &[u8]) -> Result { let hour = buf.get_u8(); let minute = buf.get_u8(); let seconds = buf.get_u8(); let micros = if len > 3 { // microseconds : int buf.get_uint_le(buf.len()) } else { 0 }; let micros = u32::try_from(micros) .map_err(|_| format!("server returned microseconds out of range: {micros}"))?; NaiveTime::from_hms_micro_opt(hour as u32, minute as u32, seconds as u32, micros) .ok_or_else(|| format!("server returned invalid time: {hour:02}:{minute:02}:{seconds:02}; micros: {micros}").into()) } #[inline(always)] fn naive_dt_encoded_len(time: &NaiveDateTime) -> u8 { // to save space the packet can be compressed: match ( time.hour(), time.minute(), time.second(), #[allow(deprecated)] time.timestamp_subsec_nanos(), ) { // if hour, minutes, seconds and micro_seconds are all 0, // length is 4 and no other field is sent (0, 0, 0, 0) => 4, // if micro_seconds is 0, length is 7 // and micro_seconds is not sent (_, _, _, 0) => 7, // otherwise length is 11 (_, _, _, _) => 11, } } #[inline(always)] fn naive_time_encoded_len(time: &NaiveTime) -> u8 { if time.nanosecond() == 0 { // if micro_seconds is 0, length is 8 and micro_seconds is not sent 8 } else { // otherwise length is 12 12 } } ================================================ FILE: sqlx-mysql/src/types/float.rs ================================================ use byteorder::{ByteOrder, LittleEndian}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::protocol::text::ColumnType; use crate::types::Type; use crate::{MySql, MySqlTypeInfo, MySqlValueFormat, MySqlValueRef}; fn real_compatible(ty: &MySqlTypeInfo) -> bool { // NOTE: `DECIMAL` is explicitly excluded because floating-point numbers have different semantics. matches!(ty.r#type, ColumnType::Float | ColumnType::Double) } impl Type for f32 { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::Float) } fn compatible(ty: &MySqlTypeInfo) -> bool { real_compatible(ty) } } impl Type for f64 { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::Double) } fn compatible(ty: &MySqlTypeInfo) -> bool { real_compatible(ty) } } impl Encode<'_, MySql> for f32 { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.extend(&self.to_le_bytes()); Ok(IsNull::No) } } impl Encode<'_, MySql> for f64 { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.extend(&self.to_le_bytes()); Ok(IsNull::No) } } impl Decode<'_, MySql> for f32 { fn decode(value: MySqlValueRef<'_>) -> Result { Ok(match value.format() { MySqlValueFormat::Binary => { let buf = value.as_bytes()?; match buf.len() { // These functions panic if `buf` is not exactly the right size. 4 => LittleEndian::read_f32(buf), // MySQL can return 8-byte DOUBLE values for a FLOAT // We take and truncate to f32 as that's the same behavior as *in* MySQL, #[allow(clippy::cast_possible_truncation)] 8 => LittleEndian::read_f64(buf) as f32, other => { // Users may try to decode a DECIMAL as floating point; // inform them why that's a bad idea. return Err(format!( "expected a FLOAT as 4 or 8 bytes, got {other} bytes; \ note that decoding DECIMAL as `f32` is not supported \ due to differing semantics" ) .into()); } } } MySqlValueFormat::Text => value.as_str()?.parse()?, }) } } impl Decode<'_, MySql> for f64 { fn decode(value: MySqlValueRef<'_>) -> Result { Ok(match value.format() { MySqlValueFormat::Binary => { let buf = value.as_bytes()?; // The `read_*` functions panic if `buf` is not exactly the right size. match buf.len() { // Allow implicit widening here 4 => LittleEndian::read_f32(buf) as f64, 8 => LittleEndian::read_f64(buf), other => { // Users may try to decode a DECIMAL as floating point; // inform them why that's a bad idea. return Err(format!( "expected a DOUBLE as 4 or 8 bytes, got {other} bytes; \ note that decoding DECIMAL as `f64` is not supported \ due to differing semantics" ) .into()); } } } MySqlValueFormat::Text => value.as_str()?.parse()?, }) } } ================================================ FILE: sqlx-mysql/src/types/inet.rs ================================================ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::io::MySqlBufMutExt; use crate::types::Type; use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; impl Type for Ipv4Addr { fn type_info() -> MySqlTypeInfo { <&str as Type>::type_info() } fn compatible(ty: &MySqlTypeInfo) -> bool { <&str as Type>::compatible(ty) } } impl Encode<'_, MySql> for Ipv4Addr { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.put_str_lenenc(&self.to_string()); Ok(IsNull::No) } } impl Decode<'_, MySql> for Ipv4Addr { fn decode(value: MySqlValueRef<'_>) -> Result { // delegate to the &str type to decode from MySQL let text = <&str as Decode>::decode(value)?; // parse a Ipv4Addr from the text text.parse().map_err(Into::into) } } impl Type for Ipv6Addr { fn type_info() -> MySqlTypeInfo { <&str as Type>::type_info() } fn compatible(ty: &MySqlTypeInfo) -> bool { <&str as Type>::compatible(ty) } } impl Encode<'_, MySql> for Ipv6Addr { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.put_str_lenenc(&self.to_string()); Ok(IsNull::No) } } impl Decode<'_, MySql> for Ipv6Addr { fn decode(value: MySqlValueRef<'_>) -> Result { // delegate to the &str type to decode from MySQL let text = <&str as Decode>::decode(value)?; // parse a Ipv6Addr from the text text.parse().map_err(Into::into) } } impl Type for IpAddr { fn type_info() -> MySqlTypeInfo { <&str as Type>::type_info() } fn compatible(ty: &MySqlTypeInfo) -> bool { <&str as Type>::compatible(ty) } } impl Encode<'_, MySql> for IpAddr { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.put_str_lenenc(&self.to_string()); Ok(IsNull::No) } } impl Decode<'_, MySql> for IpAddr { fn decode(value: MySqlValueRef<'_>) -> Result { // delegate to the &str type to decode from MySQL let text = <&str as Decode>::decode(value)?; // parse a IpAddr from the text text.parse().map_err(Into::into) } } ================================================ FILE: sqlx-mysql/src/types/int.rs ================================================ use byteorder::{ByteOrder, LittleEndian}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::protocol::text::{ColumnFlags, ColumnType}; use crate::types::Type; use crate::{MySql, MySqlTypeInfo, MySqlValueFormat, MySqlValueRef}; fn int_compatible(ty: &MySqlTypeInfo) -> bool { matches!( ty.r#type, ColumnType::Tiny | ColumnType::Short | ColumnType::Long | ColumnType::Int24 | ColumnType::LongLong ) && !ty.flags.contains(ColumnFlags::UNSIGNED) } impl Type for i8 { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::Tiny) } fn compatible(ty: &MySqlTypeInfo) -> bool { int_compatible(ty) } } impl Type for i16 { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::Short) } fn compatible(ty: &MySqlTypeInfo) -> bool { int_compatible(ty) } } impl Type for i32 { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::Long) } fn compatible(ty: &MySqlTypeInfo) -> bool { int_compatible(ty) } } impl Type for i64 { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::LongLong) } fn compatible(ty: &MySqlTypeInfo) -> bool { int_compatible(ty) } } impl Encode<'_, MySql> for i8 { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.extend(&self.to_le_bytes()); Ok(IsNull::No) } } impl Encode<'_, MySql> for i16 { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.extend(&self.to_le_bytes()); Ok(IsNull::No) } } impl Encode<'_, MySql> for i32 { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.extend(&self.to_le_bytes()); Ok(IsNull::No) } } impl Encode<'_, MySql> for i64 { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.extend(&self.to_le_bytes()); Ok(IsNull::No) } } fn int_decode(value: MySqlValueRef<'_>) -> Result { Ok(match value.format() { MySqlValueFormat::Text => value.as_str()?.parse()?, MySqlValueFormat::Binary => { let buf = value.as_bytes()?; // Check conditions that could cause `read_int()` to panic. if buf.is_empty() { return Err("empty buffer".into()); } if buf.len() > 8 { return Err(format!( "expected no more than 8 bytes for integer value, got {}", buf.len() ) .into()); } LittleEndian::read_int(buf, buf.len()) } }) } impl Decode<'_, MySql> for i8 { fn decode(value: MySqlValueRef<'_>) -> Result { int_decode(value)?.try_into().map_err(Into::into) } } impl Decode<'_, MySql> for i16 { fn decode(value: MySqlValueRef<'_>) -> Result { int_decode(value)?.try_into().map_err(Into::into) } } impl Decode<'_, MySql> for i32 { fn decode(value: MySqlValueRef<'_>) -> Result { int_decode(value)?.try_into().map_err(Into::into) } } impl Decode<'_, MySql> for i64 { fn decode(value: MySqlValueRef<'_>) -> Result { int_decode(value) } } ================================================ FILE: sqlx-mysql/src/types/json.rs ================================================ use serde::{Deserialize, Serialize}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::protocol::text::ColumnType; use crate::types::{Json, Type}; use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; impl Type for Json { fn type_info() -> MySqlTypeInfo { // MySql uses the `CHAR` type to pass JSON data from and to the client // NOTE: This is forwards-compatible with MySQL v8+ as CHAR is a common transmission format // and has nothing to do with the native storage ability of MySQL v8+ MySqlTypeInfo::binary(ColumnType::String) } fn compatible(ty: &MySqlTypeInfo) -> bool { ty.r#type == ColumnType::Json || <&str as Type>::compatible(ty) || <&[u8] as Type>::compatible(ty) } } impl Encode<'_, MySql> for Json where T: Serialize, { fn encode_by_ref(&self, buf: &mut Vec) -> Result { // Encode JSON as a length-prefixed string. // // The previous implementation encoded into an intermediate buffer to get the final length. // This is because the length prefix for the string is itself length-encoded, so we have // to know the length first before we can start encoding in the buffer... or do we? // // The docs suggest that the integer length-encoding doesn't actually enforce a range on // the value itself as long as it fits in the chosen encoding, so why not just choose // the full length encoding to begin with? Then we can just reserve the space up-front // and encode directly into the buffer. // // If someone is storing a JSON value it's likely large enough that the overhead of using // the full-length integer encoding doesn't really matter. And if it's so large it overflows // a `u64` then the process is likely to run OOM during the encoding process first anyway. let lenenc_start = buf.len(); buf.extend_from_slice(&[0u8; 9]); let encode_start = buf.len(); self.encode_to(buf)?; let encoded_len = (buf.len() - encode_start) as u64; // This prefix indicates that the following 8 bytes are a little-endian integer. buf[lenenc_start] = 0xFE; buf[lenenc_start + 1..][..8].copy_from_slice(&encoded_len.to_le_bytes()); Ok(IsNull::No) } } impl<'r, T> Decode<'r, MySql> for Json where T: 'r + Deserialize<'r>, { fn decode(value: MySqlValueRef<'r>) -> Result { Json::decode_from_string(value.as_str()?) } } ================================================ FILE: sqlx-mysql/src/types/mod.rs ================================================ //! Conversions between Rust and **MySQL/MariaDB** types. //! //! # Types //! //! | Rust type | MySQL/MariaDB type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `bool` | TINYINT(1), BOOLEAN, BOOL (see below) | //! | `i8` | TINYINT | //! | `i16` | SMALLINT | //! | `i32` | INT | //! | `i64` | BIGINT | //! | `u8` | TINYINT UNSIGNED | //! | `u16` | SMALLINT UNSIGNED | //! | `u32` | INT UNSIGNED | //! | `u64` | BIGINT UNSIGNED | //! | `f32` | FLOAT | //! | `f64` | DOUBLE | //! | `&str`, [`String`] | VARCHAR, CHAR, TEXT | //! | `&[u8]`, `Vec` | VARBINARY, BINARY, BLOB | //! | `IpAddr` | VARCHAR, TEXT | //! | `Ipv4Addr` | INET4 (MariaDB-only), VARCHAR, TEXT | //! | `Ipv6Addr` | INET6 (MariaDB-only), VARCHAR, TEXT | //! | [`MySqlTime`] | TIME (encode and decode full range) | //! | [`Duration`][std::time::Duration] | TIME (for decoding positive values only) | //! //! ##### Note: `BOOLEAN`/`BOOL` Type //! MySQL and MariaDB treat `BOOLEAN` as an alias of the `TINYINT` type: //! //! * [Using Data Types from Other Database Engines (MySQL)](https://dev.mysql.com/doc/refman/8.0/en/other-vendor-data-types.html) //! * [BOOLEAN (MariaDB)](https://mariadb.com/kb/en/boolean/) //! //! For the most part, you can simply use the Rust type `bool` when encoding or decoding a value //! using the dynamic query interface, or passing a boolean as a parameter to the query macros //! (`query!()` _et al._). //! //! However, because the MySQL wire protocol does not distinguish between `TINYINT` and `BOOLEAN`, //! the query macros cannot know that a `TINYINT` column is semantically a boolean. //! By default, they will map a `TINYINT` column as `i8` instead, as that is the safer assumption. //! //! Thus, you must use the type override syntax in the query to tell the macros you are expecting //! a `bool` column. See the docs for `query!()` and `query_as!()` for details on this syntax. //! //! ### NOTE: MySQL's `TIME` type is signed //! MySQL's `TIME` type can be used as either a time-of-day value, or a signed interval. //! Thus, it may take on negative values. //! //! Decoding a [`std::time::Duration`] returns an error if the `TIME` value is negative. //! //! ### [`chrono`](https://crates.io/crates/chrono) //! //! Requires the `chrono` Cargo feature flag. //! //! | Rust type | MySQL/MariaDB type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `chrono::DateTime` | TIMESTAMP | //! | `chrono::DateTime` | TIMESTAMP | //! | `chrono::NaiveDateTime` | DATETIME | //! | `chrono::NaiveDate` | DATE | //! | `chrono::NaiveTime` | TIME (time-of-day only) | //! | `chrono::TimeDelta` | TIME (decodes full range; see note for encoding) | //! //! ### NOTE: MySQL's `TIME` type is dual-purpose //! MySQL's `TIME` type can be used as either a time-of-day value, or an interval. //! However, `chrono::NaiveTime` is designed only to represent a time-of-day. //! //! Decoding a `TIME` value as `chrono::NaiveTime` will return an error if the value is out of range. //! //! The [`MySqlTime`] type supports the full range and it also implements `TryInto`. //! //! Decoding a `chrono::TimeDelta` also supports the full range. //! //! To encode a `chrono::TimeDelta`, convert it to [`MySqlTime`] first using `TryFrom`/`TryInto`. //! //! ### [`time`](https://crates.io/crates/time) //! //! Requires the `time` Cargo feature flag. //! //! | Rust type | MySQL/MariaDB type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `time::PrimitiveDateTime` | DATETIME | //! | `time::OffsetDateTime` | TIMESTAMP | //! | `time::Date` | DATE | //! | `time::Time` | TIME (time-of-day only) | //! | `time::Duration` | TIME (decodes full range; see note for encoding) | //! //! ### NOTE: MySQL's `TIME` type is dual-purpose //! MySQL's `TIME` type can be used as either a time-of-day value, or an interval. //! However, `time::Time` is designed only to represent a time-of-day. //! //! Decoding a `TIME` value as `time::Time` will return an error if the value is out of range. //! //! The [`MySqlTime`] type supports the full range, and it also implements `TryInto`. //! //! Decoding a `time::Duration` also supports the full range. //! //! To encode a `time::Duration`, convert it to [`MySqlTime`] first using `TryFrom`/`TryInto`. //! //! ### [`bigdecimal`](https://crates.io/crates/bigdecimal) //! Requires the `bigdecimal` Cargo feature flag. //! //! | Rust type | MySQL/MariaDB type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `bigdecimal::BigDecimal` | DECIMAL | //! //! ### [`decimal`](https://crates.io/crates/rust_decimal) //! Requires the `decimal` Cargo feature flag. //! //! | Rust type | MySQL/MariaDB type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `rust_decimal::Decimal` | DECIMAL | //! //! ### [`uuid`](https://crates.io/crates/uuid) //! //! Requires the `uuid` Cargo feature flag. //! //! | Rust type | MySQL/MariaDB type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `uuid::Uuid` | BINARY(16) (see note) | //! | `uuid::fmt::Hyphenated` | CHAR(36), VARCHAR, TEXT, UUID (MariaDB-only) | //! | `uuid::fmt::Simple` | CHAR(32), VARCHAR, TEXT | //! //! #### Note: `Uuid` uses binary format //! //! MySQL does not have a native datatype for UUIDs. //! The `UUID()` function returns a 36-character `TEXT` value, //! which encourages storing UUIDs as text. //! //! MariaDB's `UUID` type stores and retrieves as text, though it has a better representation //! for index sorting (see [MariaDB manual: UUID data-type][mariadb-uuid] for details). //! //! As an opinionated library, SQLx chose to map `uuid::Uuid` to/from binary format by default //! (16 bytes, the raw value of a UUID; SQL type `BINARY(16)`). //! This saves 20 bytes over the text format for each value. //! //! The `impl Decode for Uuid` does not support the text format, and will return an error. //! //! If you want to use the text format compatible with the `UUID()` function, //! use [`uuid::fmt::Hyphenated`][::uuid::fmt::Hyphenated] in the place of `Uuid`. //! //! The MySQL official blog has an article showing how to support both binary and text format UUIDs //! by storing the binary and adding a generated column for the text format, though this is rather //! verbose and fiddly: //! //! [mariadb-uuid]: https://mariadb.com/kb/en/uuid-data-type/ //! //! ### [`json`](https://crates.io/crates/serde_json) //! //! Requires the `json` Cargo feature flag. //! //! | Rust type | MySQL/MariaDB type(s) | //! |---------------------------------------|------------------------------------------------------| //! | [`Json`] | JSON | //! | `serde_json::JsonValue` | JSON | //! | `&serde_json::value::RawValue` | JSON | //! //! # Nullable //! //! In addition, `Option` is supported where `T` implements `Type`. An `Option` represents //! a potentially `NULL` value from MySQL/MariaDB. pub(crate) use sqlx_core::types::*; pub use mysql_time::{MySqlTime, MySqlTimeError, MySqlTimeSign}; mod bool; mod bytes; mod float; mod inet; mod int; mod mysql_time; mod str; mod text; mod uint; #[cfg(feature = "json")] mod json; #[cfg(feature = "bigdecimal")] mod bigdecimal; #[cfg(feature = "rust_decimal")] mod rust_decimal; #[cfg(feature = "chrono")] mod chrono; #[cfg(feature = "time")] mod time; #[cfg(feature = "uuid")] mod uuid; ================================================ FILE: sqlx-mysql/src/types/mysql_time.rs ================================================ //! The [`MysqlTime`] type. use crate::protocol::text::ColumnType; use crate::{MySql, MySqlTypeInfo, MySqlValueFormat}; use bytes::{Buf, BufMut}; use sqlx_core::database::Database; use sqlx_core::decode::Decode; use sqlx_core::encode::{Encode, IsNull}; use sqlx_core::error::BoxDynError; use sqlx_core::types::Type; use std::cmp::Ordering; use std::fmt::{Debug, Display, Formatter, Write}; use std::time::Duration; // Similar to `PgInterval` /// Container for a MySQL `TIME` value, which may be an interval or a time-of-day. /// /// Allowed range is `-838:59:59.0` to `838:59:59.0`. /// /// If this value is used for a time-of-day, the range should be `00:00:00.0` to `23:59:59.999999`. /// You can use [`Self::is_valid_time_of_day()`] to check this easily. /// /// * [MySQL Manual 13.2.3: The TIME Type](https://dev.mysql.com/doc/refman/8.3/en/time.html) /// * [MariaDB Manual: TIME](https://mariadb.com/kb/en/time/) #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct MySqlTime { pub(crate) sign: MySqlTimeSign, pub(crate) magnitude: TimeMagnitude, } // By using a subcontainer for the actual time magnitude, // we can still use a derived `Ord` implementation and just flip the comparison for negative values. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] pub(crate) struct TimeMagnitude { pub(crate) hours: u32, pub(crate) minutes: u8, pub(crate) seconds: u8, pub(crate) microseconds: u32, } const MAGNITUDE_ZERO: TimeMagnitude = TimeMagnitude { hours: 0, minutes: 0, seconds: 0, microseconds: 0, }; /// Maximum magnitude (positive or negative). const MAGNITUDE_MAX: TimeMagnitude = TimeMagnitude { hours: MySqlTime::HOURS_MAX, minutes: 59, seconds: 59, // Surprisingly this is not 999_999 which is why `MySqlTimeError::SubsecondExcess`. microseconds: 0, }; /// The sign for a [`MySqlTime`] type. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] pub enum MySqlTimeSign { // The protocol actually specifies negative as 1 and positive as 0, // but by specifying variants this way we can derive `Ord` and it works as expected. /// The interval is negative (invalid for time-of-day values). Negative, /// The interval is positive, or represents a time-of-day. Positive, } /// Errors returned by [`MySqlTime::new()`]. #[derive(Debug, thiserror::Error)] pub enum MySqlTimeError { /// A field of [`MySqlTime`] exceeded its max range. #[error("`MySqlTime` field `{field}` cannot exceed {max}, got {value}")] FieldRange { field: &'static str, max: u32, value: u64, }, /// Error returned for time magnitudes (positive or negative) between `838:59:59.0` and `839:00:00.0`. /// /// Other range errors should be covered by [`Self::FieldRange`] for the `hours` field. /// /// For applications which can tolerate rounding, a valid truncated value is provided. #[error( "`MySqlTime` cannot exceed +/-838:59:59.000000; got {sign}838:59:59.{microseconds:06}" )] SubsecondExcess { /// The sign of the magnitude. sign: MySqlTimeSign, /// The number of microseconds over the maximum. microseconds: u32, /// The truncated value, /// either [`MySqlTime::MIN`] if negative or [`MySqlTime::MAX`] if positive. truncated: MySqlTime, }, /// MySQL coerces `-00:00:00` to `00:00:00` but this API considers that an error. /// /// For applications which can tolerate coercion, you can convert this error to [`MySqlTime::ZERO`]. #[error("attempted to construct a `MySqlTime` value of negative zero")] NegativeZero, } impl MySqlTime { /// The `MySqlTime` value corresponding to `TIME '0:00:00.0'` (zero). pub const ZERO: Self = MySqlTime { sign: MySqlTimeSign::Positive, magnitude: MAGNITUDE_ZERO, }; /// The `MySqlTime` value corresponding to `TIME '838:59:59.0'` (max value). pub const MAX: Self = MySqlTime { sign: MySqlTimeSign::Positive, magnitude: MAGNITUDE_MAX, }; /// The `MySqlTime` value corresponding to `TIME '-838:59:59.0'` (min value). pub const MIN: Self = MySqlTime { sign: MySqlTimeSign::Negative, // Same magnitude, opposite sign. magnitude: MAGNITUDE_MAX, }; // The maximums for the other values are self-evident, but not necessarily this one. pub(crate) const HOURS_MAX: u32 = 838; /// Construct a [`MySqlTime`] that is valid for use as a `TIME` value. /// /// ### Errors /// * [`MySqlTimeError::NegativeZero`] if all fields are 0 but `sign` is [`MySqlTimeSign::Negative`]. /// * [`MySqlTimeError::FieldRange`] if any field is out of range: /// * `hours > 838` /// * `minutes > 59` /// * `seconds > 59` /// * `microseconds > 999_999` /// * [`MySqlTimeError::SubsecondExcess`] if the magnitude is less than one second over the maximum. /// * Durations 839 hours or greater are covered by `FieldRange`. pub fn new( sign: MySqlTimeSign, hours: u32, minutes: u8, seconds: u8, microseconds: u32, ) -> Result { macro_rules! check_fields { ($($name:ident: $max:expr),+ $(,)?) => { $( if $name > $max { return Err(MySqlTimeError::FieldRange { field: stringify!($name), max: $max as u32, value: $name as u64 }) } )+ } } check_fields!( hours: Self::HOURS_MAX, minutes: 59, seconds: 59, microseconds: 999_999 ); let values = TimeMagnitude { hours, minutes, seconds, microseconds, }; if sign.is_negative() && values == MAGNITUDE_ZERO { return Err(MySqlTimeError::NegativeZero); } // This is only `true` if less than 1 second over the maximum magnitude if values > MAGNITUDE_MAX { return Err(MySqlTimeError::SubsecondExcess { sign, microseconds, truncated: if sign.is_positive() { Self::MAX } else { Self::MIN }, }); } Ok(Self { sign, magnitude: values, }) } /// Update the `sign` of this value. pub fn with_sign(self, sign: MySqlTimeSign) -> Self { Self { sign, ..self } } /// Return the sign (positive or negative) for this TIME value. pub fn sign(&self) -> MySqlTimeSign { self.sign } /// Returns `true` if `self` is zero (equal to [`Self::ZERO`]). pub fn is_zero(&self) -> bool { self == &Self::ZERO } /// Returns `true` if `self` is positive or zero, `false` if negative. pub fn is_positive(&self) -> bool { self.sign.is_positive() } /// Returns `true` if `self` is negative, `false` if positive or zero. pub fn is_negative(&self) -> bool { self.sign.is_positive() } /// Returns `true` if this interval is a valid time-of-day. /// /// If `true`, the sign is positive and `hours` is not greater than 23. pub fn is_valid_time_of_day(&self) -> bool { self.sign.is_positive() && self.hours() < 24 } /// Get the total number of hours in this interval, from 0 to 838. /// /// If this value represents a time-of-day, the range is 0 to 23. pub fn hours(&self) -> u32 { self.magnitude.hours } /// Get the number of minutes in this interval, from 0 to 59. pub fn minutes(&self) -> u8 { self.magnitude.minutes } /// Get the number of seconds in this interval, from 0 to 59. pub fn seconds(&self) -> u8 { self.magnitude.seconds } /// Get the number of seconds in this interval, from 0 to 999,999. pub fn microseconds(&self) -> u32 { self.magnitude.microseconds } /// Convert this TIME value to a [`std::time::Duration`]. /// /// Returns `None` if this value is negative (cannot be represented). pub fn to_duration(&self) -> Option { self.is_positive() .then(|| Duration::new(self.whole_seconds() as u64, self.subsec_nanos())) } /// Get the whole number of seconds (`seconds + (minutes * 60) + (hours * 3600)`) in this time. /// /// Sign is ignored. pub(crate) fn whole_seconds(&self) -> u32 { // If `hours` does not exceed 838 then this cannot overflow. self.hours() * 3600 + self.minutes() as u32 * 60 + self.seconds() as u32 } #[cfg_attr(not(any(feature = "time", feature = "chrono")), allow(dead_code))] pub(crate) fn whole_seconds_signed(&self) -> i64 { self.whole_seconds() as i64 * self.sign.signum() as i64 } pub(crate) fn subsec_nanos(&self) -> u32 { self.microseconds() * 1000 } fn encoded_len(&self) -> u8 { if self.is_zero() { 0 } else if self.microseconds() == 0 { 8 } else { 12 } } } impl PartialOrd for MySqlTime { fn partial_cmp(&self, other: &MySqlTime) -> Option { Some(self.cmp(other)) } } impl Ord for MySqlTime { fn cmp(&self, other: &Self) -> Ordering { // If the sides have different signs, we just need to compare those. if self.sign != other.sign { return self.sign.cmp(&other.sign); } // We've checked that both sides have the same sign match self.sign { MySqlTimeSign::Positive => self.magnitude.cmp(&other.magnitude), // Reverse the comparison for negative values (smaller negative magnitude = greater) MySqlTimeSign::Negative => other.magnitude.cmp(&self.magnitude), } } } impl Display for MySqlTime { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let TimeMagnitude { hours, minutes, seconds, microseconds, } = self.magnitude; // Obeys the `+` flag. Display::fmt(&self.sign(), f)?; write!(f, "{hours}:{minutes:02}:{seconds:02}")?; // Write microseconds if not zero or a nonzero precision was explicitly requested. if f.precision().map_or(microseconds != 0, |it| it != 0) { f.write_char('.')?; let mut remaining_precision = f.precision(); let mut remainder = microseconds; let mut power_of_10 = 10u32.pow(5); // Write digits from most-significant to least, up to the requested precision. while remainder > 0 && remaining_precision != Some(0) { let digit = remainder / power_of_10; // 1 % 1 = 0 remainder %= power_of_10; power_of_10 /= 10; write!(f, "{digit}")?; if let Some(remaining_precision) = &mut remaining_precision { *remaining_precision = remaining_precision.saturating_sub(1); } } // If any requested precision remains, pad with zeroes. if let Some(precision) = remaining_precision.filter(|it| *it != 0) { write!(f, "{:0precision$}", 0)?; } } Ok(()) } } impl Type for MySqlTime { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::Time) } } impl<'r> Decode<'r, MySql> for MySqlTime { fn decode(value: ::ValueRef<'r>) -> Result { match value.format() { MySqlValueFormat::Binary => { let mut buf = value.as_bytes()?; // Row decoding should have left the length byte on the front. if buf.is_empty() { return Err("empty buffer".into()); } let length = buf.get_u8(); // MySQL specifies that if all fields are 0 then the length is 0 and no further data is sent // https://dev.mysql.com/doc/internals/en/binary-protocol-value.html if length == 0 { return Ok(Self::ZERO); } if !matches!(buf.len(), 8 | 12) { return Err(format!( "expected 8 or 12 bytes for TIME value, got {}", buf.len() ) .into()); } let sign = MySqlTimeSign::from_byte(buf.get_u8())?; // The wire protocol includes days but the text format doesn't. Isn't that crazy? let days = buf.get_u32_le(); let hours = buf.get_u8(); let minutes = buf.get_u8(); let seconds = buf.get_u8(); let microseconds = if !buf.is_empty() { buf.get_u32_le() } else { 0 }; let whole_hours = days .checked_mul(24) .and_then(|days_to_hours| days_to_hours.checked_add(hours as u32)) .ok_or("overflow calculating whole hours from `days * 24 + hours`")?; Ok(Self::new( sign, whole_hours, minutes, seconds, microseconds, )?) } MySqlValueFormat::Text => parse(value.as_str()?), } } } impl Encode<'_, MySql> for MySqlTime { fn encode_by_ref( &self, buf: &mut ::ArgumentBuffer, ) -> Result { if self.is_zero() { buf.put_u8(0); return Ok(IsNull::No); } buf.put_u8(self.encoded_len()); buf.put_u8(self.sign.to_byte()); let TimeMagnitude { hours: whole_hours, minutes, seconds, microseconds, } = self.magnitude; let days = whole_hours / 24; let hours = (whole_hours % 24) as u8; buf.put_u32_le(days); buf.put_u8(hours); buf.put_u8(minutes); buf.put_u8(seconds); if microseconds != 0 { buf.put_u32_le(microseconds); } Ok(IsNull::No) } fn size_hint(&self) -> usize { self.encoded_len() as usize + 1 } } /// Convert [`MySqlTime`] from [`std::time::Duration`]. /// /// ### Note: Precision Truncation /// [`Duration`] supports nanosecond precision, but MySQL `TIME` values only support microsecond /// precision. /// /// For simplicity, higher precision values are truncated when converting. /// If you prefer another rounding mode instead, you should apply that to the `Duration` first. /// /// See also: [MySQL Manual, section 13.2.6: Fractional Seconds in Time Values](https://dev.mysql.com/doc/refman/8.3/en/fractional-seconds.html) /// /// ### Errors: /// Returns [`MySqlTimeError::FieldRange`] if the given duration is longer than `838:59:59.999999`. /// impl TryFrom for MySqlTime { type Error = MySqlTimeError; fn try_from(value: Duration) -> Result { let hours = value.as_secs() / 3600; let rem_seconds = value.as_secs() % 3600; let minutes = (rem_seconds / 60) as u8; let seconds = (rem_seconds % 60) as u8; // Simply divides by 1000 let microseconds = value.subsec_micros(); Self::new( MySqlTimeSign::Positive, hours.try_into().map_err(|_| MySqlTimeError::FieldRange { field: "hours", max: Self::HOURS_MAX, value: hours, })?, minutes, seconds, microseconds, ) } } impl MySqlTimeSign { fn from_byte(b: u8) -> Result { match b { 0 => Ok(Self::Positive), 1 => Ok(Self::Negative), other => Err(format!("expected 0 or 1 for TIME sign byte, got {other}").into()), } } fn to_byte(self) -> u8 { match self { // We can't use `#[repr(u8)]` because this is opposite of the ordering we want from `Ord` Self::Negative => 1, Self::Positive => 0, } } fn signum(&self) -> i32 { match self { Self::Negative => -1, Self::Positive => 1, } } /// Returns `true` if positive, `false` if negative. pub fn is_positive(&self) -> bool { matches!(self, Self::Positive) } /// Returns `true` if negative, `false` if positive. pub fn is_negative(&self) -> bool { matches!(self, Self::Negative) } } impl Display for MySqlTimeSign { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::Positive if f.sign_plus() => f.write_char('+'), Self::Negative => f.write_char('-'), _ => Ok(()), } } } impl Type for Duration { fn type_info() -> MySqlTypeInfo { MySqlTime::type_info() } } impl<'r> Decode<'r, MySql> for Duration { fn decode(value: ::ValueRef<'r>) -> Result { let time = MySqlTime::decode(value)?; time.to_duration().ok_or_else(|| { format!("`std::time::Duration` can only decode positive TIME values; got {time}").into() }) } } // Not exposing this as a `FromStr` impl currently because `MySqlTime` is not designed to be // a general interchange type. fn parse(text: &str) -> Result { let mut segments = text.split(':'); let hours = segments .next() .ok_or("expected hours segment, got nothing")?; let minutes = segments .next() .ok_or("expected minutes segment, got nothing")?; let seconds = segments .next() .ok_or("expected seconds segment, got nothing")?; // Include the sign in parsing for convenience; // the allowed range of whole hours is much smaller than `i32`'s positive range. let hours: i32 = hours .parse() .map_err(|e| format!("error parsing hours from {text:?} (segment {hours:?}): {e}"))?; let sign = if hours.is_negative() { MySqlTimeSign::Negative } else { MySqlTimeSign::Positive }; let hours = hours.unsigned_abs(); let minutes: u8 = minutes .parse() .map_err(|e| format!("error parsing minutes from {text:?} (segment {minutes:?}): {e}"))?; let (seconds, microseconds): (u8, u32) = if let Some((seconds, microseconds)) = seconds.split_once('.') { ( seconds.parse().map_err(|e| { format!("error parsing seconds from {text:?} (segment {seconds:?}): {e}") })?, parse_microseconds(microseconds).map_err(|e| { format!("error parsing microseconds from {text:?} (segment {microseconds:?}): {e}") })?, ) } else { ( seconds.parse().map_err(|e| { format!("error parsing seconds from {text:?} (segment {seconds:?}): {e}") })?, 0, ) }; Ok(MySqlTime::new(sign, hours, minutes, seconds, microseconds)?) } /// Parse microseconds from a fractional seconds string. fn parse_microseconds(micros: &str) -> Result { const EXPECTED_DIGITS: usize = 6; match micros.len() { 0 => Err("empty string".into()), len @ ..=EXPECTED_DIGITS => { // Fewer than 6 digits, multiply to the correct magnitude let micros: u32 = micros.parse()?; // cast cannot overflow #[allow(clippy::cast_possible_truncation)] Ok(micros * 10u32.pow((EXPECTED_DIGITS - len) as u32)) } // More digits than expected, truncate _ => Ok(micros[..EXPECTED_DIGITS].parse()?), } } #[cfg(test)] mod tests { use super::MySqlTime; use crate::types::MySqlTimeSign; use super::parse_microseconds; #[test] fn test_display() { assert_eq!(MySqlTime::ZERO.to_string(), "0:00:00"); assert_eq!(format!("{:.0}", MySqlTime::ZERO), "0:00:00"); assert_eq!(format!("{:.3}", MySqlTime::ZERO), "0:00:00.000"); assert_eq!(format!("{:.6}", MySqlTime::ZERO), "0:00:00.000000"); assert_eq!(format!("{:.9}", MySqlTime::ZERO), "0:00:00.000000000"); assert_eq!(format!("{:.0}", MySqlTime::MAX), "838:59:59"); assert_eq!(format!("{:.3}", MySqlTime::MAX), "838:59:59.000"); assert_eq!(format!("{:.6}", MySqlTime::MAX), "838:59:59.000000"); assert_eq!(format!("{:.9}", MySqlTime::MAX), "838:59:59.000000000"); assert_eq!(format!("{:+.0}", MySqlTime::MAX), "+838:59:59"); assert_eq!(format!("{:+.3}", MySqlTime::MAX), "+838:59:59.000"); assert_eq!(format!("{:+.6}", MySqlTime::MAX), "+838:59:59.000000"); assert_eq!(format!("{:+.9}", MySqlTime::MAX), "+838:59:59.000000000"); assert_eq!(format!("{:.0}", MySqlTime::MIN), "-838:59:59"); assert_eq!(format!("{:.3}", MySqlTime::MIN), "-838:59:59.000"); assert_eq!(format!("{:.6}", MySqlTime::MIN), "-838:59:59.000000"); assert_eq!(format!("{:.9}", MySqlTime::MIN), "-838:59:59.000000000"); let positive = MySqlTime::new(MySqlTimeSign::Positive, 123, 45, 56, 890011).unwrap(); assert_eq!(positive.to_string(), "123:45:56.890011"); assert_eq!(format!("{positive:.0}"), "123:45:56"); assert_eq!(format!("{positive:.3}"), "123:45:56.890"); assert_eq!(format!("{positive:.6}"), "123:45:56.890011"); assert_eq!(format!("{positive:.9}"), "123:45:56.890011000"); assert_eq!(format!("{positive:+.0}"), "+123:45:56"); assert_eq!(format!("{positive:+.3}"), "+123:45:56.890"); assert_eq!(format!("{positive:+.6}"), "+123:45:56.890011"); assert_eq!(format!("{positive:+.9}"), "+123:45:56.890011000"); let negative = MySqlTime::new(MySqlTimeSign::Negative, 123, 45, 56, 890011).unwrap(); assert_eq!(negative.to_string(), "-123:45:56.890011"); assert_eq!(format!("{negative:.0}"), "-123:45:56"); assert_eq!(format!("{negative:.3}"), "-123:45:56.890"); assert_eq!(format!("{negative:.6}"), "-123:45:56.890011"); assert_eq!(format!("{negative:.9}"), "-123:45:56.890011000"); } #[test] fn test_parse_microseconds() { assert_eq!(parse_microseconds("010").unwrap(), 10_000); assert_eq!(parse_microseconds("0100000000").unwrap(), 10_000); assert_eq!(parse_microseconds("890").unwrap(), 890_000); assert_eq!(parse_microseconds("0890").unwrap(), 89_000); assert_eq!( // Case in point about not exposing this: // we always truncate excess precision because it's simpler than rounding // and MySQL should never return a higher precision. parse_microseconds("123456789").unwrap(), 123456, ); } } ================================================ FILE: sqlx-mysql/src/types/rust_decimal.rs ================================================ use rust_decimal::Decimal; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::io::MySqlBufMutExt; use crate::protocol::text::ColumnType; use crate::types::Type; use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; impl Type for Decimal { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::NewDecimal) } fn compatible(ty: &MySqlTypeInfo) -> bool { matches!(ty.r#type, ColumnType::Decimal | ColumnType::NewDecimal) } } impl Encode<'_, MySql> for Decimal { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.put_str_lenenc(&self.to_string()); Ok(IsNull::No) } } impl Decode<'_, MySql> for Decimal { fn decode(value: MySqlValueRef<'_>) -> Result { Ok(value.as_str()?.parse()?) } } ================================================ FILE: sqlx-mysql/src/types/str.rs ================================================ use std::borrow::Cow; use std::rc::Rc; use std::sync::Arc; use crate::collation::Collation; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::io::MySqlBufMutExt; use crate::protocol::text::{ColumnFlags, ColumnType}; use crate::types::Type; use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; impl Type for str { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo { r#type: ColumnType::VarString, // VARCHAR flags: ColumnFlags::empty(), // Doesn't matter because we never send this. collation: Collation::UTF8MB4_GENERAL_CI, max_size: None, } } fn compatible(ty: &MySqlTypeInfo) -> bool { // TODO: Support more collations being returned from SQL? matches!( ty.r#type, ColumnType::VarChar | ColumnType::Blob | ColumnType::TinyBlob | ColumnType::MediumBlob | ColumnType::LongBlob | ColumnType::String | ColumnType::VarString | ColumnType::Enum ) // Any collation that *isn't* `binary` generally indicates string data. // The actual collation used for storage doesn't matter, // because the server will transcode to the charset we specify. // // See comment in `src/collation.rs` for details. && ty.collation != Collation::BINARY } } impl Encode<'_, MySql> for &'_ str { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.put_str_lenenc(self); Ok(IsNull::No) } } impl<'r> Decode<'r, MySql> for &'r str { fn decode(value: MySqlValueRef<'r>) -> Result { value.as_str() } } impl Type for String { fn type_info() -> MySqlTypeInfo { >::type_info() } fn compatible(ty: &MySqlTypeInfo) -> bool { >::compatible(ty) } } impl Decode<'_, MySql> for String { fn decode(value: MySqlValueRef<'_>) -> Result { <&str as Decode>::decode(value).map(ToOwned::to_owned) } } forward_encode_impl!(Arc, &str, MySql); forward_encode_impl!(Rc, &str, MySql); forward_encode_impl!(Cow<'_, str>, &str, MySql); forward_encode_impl!(Box, &str, MySql); forward_encode_impl!(String, &str, MySql); ================================================ FILE: sqlx-mysql/src/types/text.rs ================================================ use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; use sqlx_core::decode::Decode; use sqlx_core::encode::{Encode, IsNull}; use sqlx_core::error::BoxDynError; use sqlx_core::types::{Text, Type}; use std::fmt::Display; use std::str::FromStr; impl Type for Text { fn type_info() -> MySqlTypeInfo { >::type_info() } fn compatible(ty: &MySqlTypeInfo) -> bool { >::compatible(ty) } } impl Encode<'_, MySql> for Text where T: Display, { fn encode_by_ref(&self, buf: &mut Vec) -> Result { // We can't really do the trick like with Postgres where we reserve the space for the // length up-front and then overwrite it later, because MySQL appears to enforce that // length-encoded integers use the smallest encoding for the value: // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_dt_integers.html#sect_protocol_basic_dt_int_le // // So we'd have to reserve space for the max-width encoding, format into the buffer, // then figure out how many bytes our length-encoded integer needs to be and move the // value bytes down to use up the empty space. // // Copying from a completely separate buffer instead is easier. It may or may not be faster // or slower depending on a ton of different variables, but I don't currently have the time // to implement both approaches and compare their performance. Encode::::encode(self.0.to_string(), buf) } } impl<'r, T> Decode<'r, MySql> for Text where T: FromStr, BoxDynError: From<::Err>, { fn decode(value: MySqlValueRef<'r>) -> Result { let s: &str = Decode::::decode(value)?; Ok(Self(s.parse()?)) } } ================================================ FILE: sqlx-mysql/src/types/time.rs ================================================ use byteorder::{ByteOrder, LittleEndian}; use bytes::Buf; use sqlx_core::database::Database; use time::macros::format_description; use time::{Date, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::{BoxDynError, UnexpectedNullError}; use crate::protocol::text::ColumnType; use crate::type_info::MySqlTypeInfo; use crate::types::{MySqlTime, MySqlTimeSign, Type}; use crate::{MySql, MySqlValueFormat, MySqlValueRef}; impl Type for OffsetDateTime { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::Timestamp) } fn compatible(ty: &MySqlTypeInfo) -> bool { matches!(ty.r#type, ColumnType::Datetime | ColumnType::Timestamp) } } impl Encode<'_, MySql> for OffsetDateTime { fn encode_by_ref(&self, buf: &mut Vec) -> Result { let utc_dt = self.to_offset(UtcOffset::UTC); let primitive_dt = PrimitiveDateTime::new(utc_dt.date(), utc_dt.time()); Encode::::encode(primitive_dt, buf) } } impl<'r> Decode<'r, MySql> for OffsetDateTime { fn decode(value: MySqlValueRef<'r>) -> Result { let primitive: PrimitiveDateTime = Decode::::decode(value)?; Ok(primitive.assume_utc()) } } impl Type for Time { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::Time) } } impl Encode<'_, MySql> for Time { fn encode_by_ref(&self, buf: &mut Vec) -> Result { let len = time_encoded_len(self); buf.push(len); // sign byte: Time is never negative buf.push(0); // Number of days in the interval; always 0 for time-of-day values. // https://mariadb.com/kb/en/resultset-row/#teimstamp-binary-encoding buf.extend_from_slice(&[0_u8; 4]); encode_time(self, len > 8, buf); Ok(IsNull::No) } fn size_hint(&self) -> usize { time_encoded_len(self) as usize + 1 // plus length byte } } impl<'r> Decode<'r, MySql> for Time { fn decode(value: MySqlValueRef<'r>) -> Result { match value.format() { MySqlValueFormat::Binary => { // Should never panic. MySqlTime::decode(value)?.try_into() } // Retaining this parsing for now as it allows us to cross-check our impl. MySqlValueFormat::Text => Time::parse( value.as_str()?, &format_description!("[hour]:[minute]:[second].[subsecond]"), ) .map_err(Into::into), } } } impl TryFrom for Time { type Error = BoxDynError; fn try_from(time: MySqlTime) -> Result { if !time.is_valid_time_of_day() { return Err(format!("MySqlTime value out of range for `time::Time`: {time}").into()); } #[allow(clippy::cast_possible_truncation)] Ok(Time::from_hms_micro( // `is_valid_time_of_day()` ensures this won't overflow time.hours() as u8, time.minutes(), time.seconds(), time.microseconds(), )?) } } impl From for time::Duration { fn from(time: MySqlTime) -> Self { // `subsec_nanos()` is guaranteed to be between 0 and 10^9 #[allow(clippy::cast_possible_wrap)] time::Duration::new(time.whole_seconds_signed(), time.subsec_nanos() as i32) } } impl TryFrom for MySqlTime { type Error = BoxDynError; fn try_from(value: time::Duration) -> Result { let sign = if value.is_negative() { MySqlTimeSign::Negative } else { MySqlTimeSign::Positive }; // Similar to `TryFrom`, use `std::time::Duration` as an intermediate. Ok(MySqlTime::try_from(std::time::Duration::try_from(value.abs())?)?.with_sign(sign)) } } impl Type for time::Duration { fn type_info() -> MySqlTypeInfo { MySqlTime::type_info() } } impl<'r> Decode<'r, MySql> for time::Duration { fn decode(value: ::ValueRef<'r>) -> Result { Ok(MySqlTime::decode(value)?.into()) } } impl Type for Date { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::Date) } } impl Encode<'_, MySql> for Date { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.push(4); encode_date(self, buf)?; Ok(IsNull::No) } fn size_hint(&self) -> usize { 5 } } impl<'r> Decode<'r, MySql> for Date { fn decode(value: MySqlValueRef<'r>) -> Result { match value.format() { MySqlValueFormat::Binary => { let buf = value.as_bytes()?; // Row decoding should leave the length byte on the front. if buf.is_empty() { return Err("empty buffer".into()); } Ok(decode_date(&buf[1..])?.ok_or(UnexpectedNullError)?) } MySqlValueFormat::Text => { let s = value.as_str()?; Date::parse(s, &format_description!("[year]-[month]-[day]")).map_err(Into::into) } } } } impl Type for PrimitiveDateTime { fn type_info() -> MySqlTypeInfo { MySqlTypeInfo::binary(ColumnType::Datetime) } } impl Encode<'_, MySql> for PrimitiveDateTime { fn encode_by_ref(&self, buf: &mut Vec) -> Result { let len = primitive_dt_encoded_len(self); buf.push(len); encode_date(&self.date(), buf)?; if len > 4 { encode_time(&self.time(), len > 7, buf); } Ok(IsNull::No) } fn size_hint(&self) -> usize { primitive_dt_encoded_len(self) as usize + 1 // plus length byte } } impl<'r> Decode<'r, MySql> for PrimitiveDateTime { fn decode(value: MySqlValueRef<'r>) -> Result { match value.format() { MySqlValueFormat::Binary => { let mut buf = value.as_bytes()?; if buf.is_empty() { return Err("empty buffer".into()); } let len = buf.get_u8(); let date = decode_date(buf)?.ok_or(UnexpectedNullError)?; let dt = if len > 4 { date.with_time(decode_time(&buf[4..])?) } else { date.midnight() }; Ok(dt) } MySqlValueFormat::Text => { let s = value.as_str()?; // If there are no nanoseconds parse without them if s.contains('.') { PrimitiveDateTime::parse( s, &format_description!( "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]" ), ) .map_err(Into::into) } else { PrimitiveDateTime::parse( s, &format_description!("[year]-[month]-[day] [hour]:[minute]:[second]"), ) .map_err(Into::into) } } } } } fn encode_date(date: &Date, buf: &mut Vec) -> Result<(), BoxDynError> { // MySQL supports years from 1000 - 9999 let year = u16::try_from(date.year()).map_err(|_| format!("Date out of range for Mysql: {date}"))?; buf.extend_from_slice(&year.to_le_bytes()); buf.push(date.month().into()); buf.push(date.day()); Ok(()) } fn decode_date(buf: &[u8]) -> Result, BoxDynError> { if buf.is_empty() { // zero buffer means a zero date (null) return Ok(None); } Date::from_calendar_date( LittleEndian::read_u16(buf) as i32, time::Month::try_from(buf[2])?, buf[3], ) .map_err(Into::into) .map(Some) } fn encode_time(time: &Time, include_micros: bool, buf: &mut Vec) { buf.push(time.hour()); buf.push(time.minute()); buf.push(time.second()); if include_micros { buf.extend(&(time.nanosecond() / 1000).to_le_bytes()); } } fn decode_time(mut buf: &[u8]) -> Result { let hour = buf.get_u8(); let minute = buf.get_u8(); let seconds = buf.get_u8(); let micros = if !buf.is_empty() { // microseconds : int buf.get_uint_le(buf.len()) } else { 0 }; let micros = u32::try_from(micros) .map_err(|_| format!("MySQL returned microseconds out of range: {micros}"))?; Time::from_hms_micro(hour, minute, seconds, micros) .map_err(|e| format!("Time out of range for MySQL: {e}").into()) } #[inline(always)] fn primitive_dt_encoded_len(time: &PrimitiveDateTime) -> u8 { // to save space the packet can be compressed: match (time.hour(), time.minute(), time.second(), time.nanosecond()) { // if hour, minutes, seconds and micro_seconds are all 0, // length is 4 and no other field is sent (0, 0, 0, 0) => 4, // if micro_seconds is 0, length is 7 // and micro_seconds is not sent (_, _, _, 0) => 7, // otherwise length is 11 (_, _, _, _) => 11, } } #[inline(always)] fn time_encoded_len(time: &Time) -> u8 { if time.nanosecond() == 0 { // if micro_seconds is 0, length is 8 and micro_seconds is not sent 8 } else { // otherwise length is 12 12 } } ================================================ FILE: sqlx-mysql/src/types/uint.rs ================================================ use crate::collation::Collation; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::protocol::text::{ColumnFlags, ColumnType}; use crate::types::Type; use crate::{MySql, MySqlTypeInfo, MySqlValueFormat, MySqlValueRef}; use byteorder::{ByteOrder, LittleEndian}; fn uint_type_info(ty: ColumnType) -> MySqlTypeInfo { MySqlTypeInfo { r#type: ty, flags: ColumnFlags::BINARY | ColumnFlags::UNSIGNED, collation: Collation::BINARY, max_size: None, } } fn uint_compatible(ty: &MySqlTypeInfo) -> bool { matches!( ty.r#type, ColumnType::Tiny | ColumnType::Short | ColumnType::Long | ColumnType::Int24 | ColumnType::LongLong | ColumnType::Year | ColumnType::Bit ) && ty.flags.contains(ColumnFlags::UNSIGNED) } impl Type for u8 { fn type_info() -> MySqlTypeInfo { uint_type_info(ColumnType::Tiny) } fn compatible(ty: &MySqlTypeInfo) -> bool { uint_compatible(ty) } } impl Type for u16 { fn type_info() -> MySqlTypeInfo { uint_type_info(ColumnType::Short) } fn compatible(ty: &MySqlTypeInfo) -> bool { uint_compatible(ty) } } impl Type for u32 { fn type_info() -> MySqlTypeInfo { uint_type_info(ColumnType::Long) } fn compatible(ty: &MySqlTypeInfo) -> bool { uint_compatible(ty) } } impl Type for u64 { fn type_info() -> MySqlTypeInfo { uint_type_info(ColumnType::LongLong) } fn compatible(ty: &MySqlTypeInfo) -> bool { uint_compatible(ty) } } impl Encode<'_, MySql> for u8 { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.extend(&self.to_le_bytes()); Ok(IsNull::No) } } impl Encode<'_, MySql> for u16 { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.extend(&self.to_le_bytes()); Ok(IsNull::No) } } impl Encode<'_, MySql> for u32 { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.extend(&self.to_le_bytes()); Ok(IsNull::No) } } impl Encode<'_, MySql> for u64 { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.extend(&self.to_le_bytes()); Ok(IsNull::No) } } fn uint_decode(value: MySqlValueRef<'_>) -> Result { if value.type_info.r#type == ColumnType::Bit { // NOTE: Regardless of the value format, there is raw binary data here let buf = value.as_bytes()?; let mut value: u64 = 0; for b in buf { value = (*b as u64) | (value << 8); } return Ok(value); } Ok(match value.format() { MySqlValueFormat::Text => value.as_str()?.parse()?, MySqlValueFormat::Binary => { let buf = value.as_bytes()?; // Check conditions that could cause `read_uint()` to panic. if buf.is_empty() { return Err("empty buffer".into()); } if buf.len() > 8 { return Err(format!( "expected no more than 8 bytes for unsigned integer value, got {}", buf.len() ) .into()); } LittleEndian::read_uint(buf, buf.len()) } }) } impl Decode<'_, MySql> for u8 { fn decode(value: MySqlValueRef<'_>) -> Result { uint_decode(value)?.try_into().map_err(Into::into) } } impl Decode<'_, MySql> for u16 { fn decode(value: MySqlValueRef<'_>) -> Result { uint_decode(value)?.try_into().map_err(Into::into) } } impl Decode<'_, MySql> for u32 { fn decode(value: MySqlValueRef<'_>) -> Result { uint_decode(value)?.try_into().map_err(Into::into) } } impl Decode<'_, MySql> for u64 { fn decode(value: MySqlValueRef<'_>) -> Result { uint_decode(value) } } ================================================ FILE: sqlx-mysql/src/types/uuid.rs ================================================ use uuid::{ fmt::{Hyphenated, Simple}, Uuid, }; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::io::MySqlBufMutExt; use crate::types::Type; use crate::{MySql, MySqlTypeInfo, MySqlValueRef}; impl Type for Uuid { fn type_info() -> MySqlTypeInfo { <&[u8] as Type>::type_info() } fn compatible(ty: &MySqlTypeInfo) -> bool { <&[u8] as Type>::compatible(ty) } } impl Encode<'_, MySql> for Uuid { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.put_bytes_lenenc(self.as_bytes()); Ok(IsNull::No) } } impl Decode<'_, MySql> for Uuid { fn decode(value: MySqlValueRef<'_>) -> Result { // delegate to the &[u8] type to decode from MySQL let bytes = <&[u8] as Decode>::decode(value)?; if bytes.len() != 16 { return Err(format!( "Expected 16 bytes, got {}; `Uuid` uses binary format for MySQL/MariaDB. \ For text-formatted UUIDs, use `uuid::fmt::Hyphenated` instead of `Uuid`.", bytes.len(), ) .into()); } // construct a Uuid from the returned bytes Uuid::from_slice(bytes).map_err(Into::into) } } impl Type for Hyphenated { fn type_info() -> MySqlTypeInfo { <&str as Type>::type_info() } fn compatible(ty: &MySqlTypeInfo) -> bool { <&str as Type>::compatible(ty) } } impl Encode<'_, MySql> for Hyphenated { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.put_str_lenenc(&self.to_string()); Ok(IsNull::No) } } impl Decode<'_, MySql> for Hyphenated { fn decode(value: MySqlValueRef<'_>) -> Result { // delegate to the &str type to decode from MySQL let text = <&str as Decode>::decode(value)?; // parse a UUID from the text Uuid::parse_str(text) .map_err(Into::into) .map(|u| u.hyphenated()) } } impl Type for Simple { fn type_info() -> MySqlTypeInfo { <&str as Type>::type_info() } fn compatible(ty: &MySqlTypeInfo) -> bool { <&str as Type>::compatible(ty) } } impl Encode<'_, MySql> for Simple { fn encode_by_ref(&self, buf: &mut Vec) -> Result { buf.put_str_lenenc(&self.to_string()); Ok(IsNull::No) } } impl Decode<'_, MySql> for Simple { fn decode(value: MySqlValueRef<'_>) -> Result { // delegate to the &str type to decode from MySQL let text = <&str as Decode>::decode(value)?; // parse a UUID from the text Uuid::parse_str(text) .map_err(Into::into) .map(|u| u.simple()) } } ================================================ FILE: sqlx-mysql/src/value.rs ================================================ use std::borrow::Cow; use std::str::from_utf8; use bytes::Bytes; pub(crate) use sqlx_core::value::*; use crate::error::{BoxDynError, UnexpectedNullError}; use crate::protocol::text::ColumnType; use crate::{MySql, MySqlTypeInfo}; #[derive(Debug, Clone, Copy)] #[repr(u8)] pub enum MySqlValueFormat { Text, Binary, } /// Implementation of [`Value`] for MySQL. #[derive(Clone)] pub struct MySqlValue { value: Option, type_info: MySqlTypeInfo, format: MySqlValueFormat, } /// Implementation of [`ValueRef`] for MySQL. #[derive(Clone)] pub struct MySqlValueRef<'r> { pub(crate) value: Option<&'r [u8]>, pub(crate) row: Option<&'r Bytes>, pub(crate) type_info: MySqlTypeInfo, pub(crate) format: MySqlValueFormat, } impl<'r> MySqlValueRef<'r> { pub(crate) fn format(&self) -> MySqlValueFormat { self.format } pub(crate) fn as_bytes(&self) -> Result<&'r [u8], BoxDynError> { match &self.value { Some(v) => Ok(v), None => Err(UnexpectedNullError.into()), } } pub(crate) fn as_str(&self) -> Result<&'r str, BoxDynError> { Ok(from_utf8(self.as_bytes()?)?) } } impl Value for MySqlValue { type Database = MySql; fn as_ref(&self) -> MySqlValueRef<'_> { MySqlValueRef { value: self.value.as_deref(), row: None, type_info: self.type_info.clone(), format: self.format, } } fn type_info(&self) -> Cow<'_, MySqlTypeInfo> { Cow::Borrowed(&self.type_info) } fn is_null(&self) -> bool { is_null(self.value.as_deref(), &self.type_info) } } impl<'r> ValueRef<'r> for MySqlValueRef<'r> { type Database = MySql; fn to_owned(&self) -> MySqlValue { let value = match (self.row, self.value) { (Some(row), Some(value)) => Some(row.slice_ref(value)), (None, Some(value)) => Some(Bytes::copy_from_slice(value)), _ => None, }; MySqlValue { value, format: self.format, type_info: self.type_info.clone(), } } fn type_info(&self) -> Cow<'_, MySqlTypeInfo> { Cow::Borrowed(&self.type_info) } #[inline] fn is_null(&self) -> bool { is_null(self.value, &self.type_info) } } fn is_null(value: Option<&[u8]>, ty: &MySqlTypeInfo) -> bool { if let Some(value) = value { // zero dates and date times should be treated the same as NULL if matches!( ty.r#type, ColumnType::Date | ColumnType::Timestamp | ColumnType::Datetime ) && value.starts_with(b"\0") { return true; } } value.is_none() } ================================================ FILE: sqlx-postgres/Cargo.toml ================================================ [package] name = "sqlx-postgres" documentation = "https://docs.rs/sqlx" description = "PostgreSQL driver implementation for SQLx. Not for direct use; see the `sqlx` crate for details." version.workspace = true license.workspace = true edition.workspace = true authors.workspace = true repository.workspace = true rust-version.workspace = true [features] any = ["sqlx-core/any"] json = ["dep:serde", "dep:serde_json", "sqlx-core/json"] migrate = ["sqlx-core/migrate"] offline = ["json", "sqlx-core/offline", "smallvec/serde"] # Type Integration features bigdecimal = ["dep:bigdecimal", "dep:num-bigint", "sqlx-core/bigdecimal"] bit-vec = ["dep:bit-vec", "sqlx-core/bit-vec"] chrono = ["dep:chrono", "sqlx-core/chrono"] ipnet = ["dep:ipnet", "sqlx-core/ipnet"] ipnetwork = ["dep:ipnetwork", "sqlx-core/ipnetwork"] mac_address = ["dep:mac_address", "sqlx-core/mac_address"] rust_decimal = ["dep:rust_decimal", "rust_decimal/maths", "sqlx-core/rust_decimal"] time = ["dep:time", "sqlx-core/time"] uuid = ["dep:uuid", "sqlx-core/uuid"] [dependencies] # Futures crates futures-channel = { version = "0.3.19", default-features = false, features = ["sink", "alloc", "std"] } futures-core = { version = "0.3.19", default-features = false } futures-util = { version = "0.3.19", default-features = false, features = ["alloc", "sink", "io"] } # Cryptographic Primitives crc = "3.0.0" hkdf = "0.12.0" hmac = { version = "0.12.0", default-features = false, features = ["reset"]} md-5 = { version = "0.10.0", default-features = false } rand = { version = "0.8.4", default-features = false, features = ["std", "std_rng"] } sha2 = { version = "0.10.0", default-features = false } # Type Integrations (versions inherited from `[workspace.dependencies]`) bigdecimal = { workspace = true, optional = true } bit-vec = { workspace = true, optional = true } chrono = { workspace = true, optional = true } ipnet = { workspace = true, optional = true } ipnetwork = { workspace = true, optional = true } mac_address = { workspace = true, optional = true } rust_decimal = { workspace = true, optional = true } time = { workspace = true, optional = true } uuid = { workspace = true, optional = true } # Misc atoi = "2.0" base64 = { version = "0.22.0", default-features = false, features = ["std"] } bitflags = { version = "2", default-features = false } byteorder = { version = "1.4.3", default-features = false, features = ["std"] } hex = "0.4.3" itoa = "1.0.1" log = "0.4.18" memchr = { version = "2.4.1", default-features = false } num-bigint = { version = "0.4.3", optional = true } smallvec = { version = "1.7.0" } stringprep = "0.1.2" tracing = { version = "0.1.37", features = ["log"] } whoami = { version = "2.0.2", default-features = false } dotenvy.workspace = true thiserror.workspace = true serde = { version = "1.0.144", optional = true, features = ["derive"] } serde_json = { version = "1.0.85", optional = true, features = ["raw_value"] } [dependencies.sqlx-core] workspace = true [dev-dependencies.sqlx] # FIXME: https://github.com/rust-lang/cargo/issues/15622 # workspace = true path = ".." features = ["postgres", "derive"] [target.'cfg(target_os = "windows")'.dependencies] etcetera = "0.10.0" [lints] workspace = true ================================================ FILE: sqlx-postgres/src/advisory_lock.rs ================================================ use crate::error::Result; use crate::Either; use crate::PgConnection; use hkdf::Hkdf; use sha2::Sha256; use std::ops::{Deref, DerefMut}; use std::sync::Arc; use std::sync::OnceLock; /// A mutex-like type utilizing [Postgres advisory locks]. /// /// Advisory locks are a mechanism provided by Postgres to have mutually exclusive or shared /// locks tracked in the database with application-defined semantics, as opposed to the standard /// row-level or table-level locks which may not fit all use-cases. /// /// This API provides a convenient wrapper for generating and storing the integer keys that /// advisory locks use, as well as RAII guards for releasing advisory locks when they fall out /// of scope. /// /// This API only handles session-scoped advisory locks (explicitly locked and unlocked, or /// automatically released when a connection is closed). /// /// It is also possible to use transaction-scoped locks but those can be used by beginning a /// transaction and calling the appropriate lock functions (e.g. `SELECT pg_advisory_xact_lock()`) /// manually, and cannot be explicitly released, but are automatically released when a transaction /// ends (is committed or rolled back). /// /// Session-level locks can be acquired either inside or outside a transaction and are not /// tied to transaction semantics; a lock acquired inside a transaction is still held when that /// transaction is committed or rolled back, until explicitly released or the connection is closed. /// /// Locks can be acquired in either shared or exclusive modes, which can be thought of as read locks /// and write locks, respectively. Multiple shared locks are allowed for the same key, but a single /// exclusive lock prevents any other lock being taken for a given key until it is released. /// /// [Postgres advisory locks]: https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS #[derive(Debug, Clone)] pub struct PgAdvisoryLock { key: PgAdvisoryLockKey, /// The query to execute to release this lock. release_query: Arc>, } /// A key type natively used by Postgres advisory locks. /// /// Currently, Postgres advisory locks have two different key spaces: one keyed by a single /// 64-bit integer, and one keyed by a pair of two 32-bit integers. The Postgres docs /// specify that these key spaces "do not overlap": /// /// /// /// The documentation for the `pg_locks` system view explains further how advisory locks /// are treated in Postgres: /// /// #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] pub enum PgAdvisoryLockKey { /// The keyspace designated by a single 64-bit integer. /// /// When [PgAdvisoryLock] is constructed with [::new()][PgAdvisoryLock::new()], /// this is the keyspace used. BigInt(i64), /// The keyspace designated by two 32-bit integers. IntPair(i32, i32), } /// A wrapper for `PgConnection` (or a similar type) that represents a held Postgres advisory lock. /// /// Can be acquired by [`PgAdvisoryLock::acquire()`] or [`PgAdvisoryLock::try_acquire()`]. /// Released on-drop or via [`Self::release_now()`]. /// /// ### Note: Release-on-drop is not immediate! /// On drop, this guard queues a `pg_advisory_unlock()` call on the connection which will be /// flushed to the server the next time it is used, or when it is returned to /// a [`PgPool`][crate::PgPool] in the case of /// [`PoolConnection`][crate::pool::PoolConnection]. /// /// This means the lock is not actually released as soon as the guard is dropped. To ensure the /// lock is eagerly released, you can call [`.release_now().await`][Self::release_now()]. pub struct PgAdvisoryLockGuard> { lock: PgAdvisoryLock, conn: Option, } impl PgAdvisoryLock { /// Construct a `PgAdvisoryLock` using the given string as a key. /// /// This is intended to make it easier to use an advisory lock by using a human-readable string /// for a key as opposed to manually generating a unique integer key. The generated integer key /// is guaranteed to be stable and in the single 64-bit integer keyspace /// (see [`PgAdvisoryLockKey`] for details). /// /// This is done by applying the [Hash-based Key Derivation Function (HKDF; IETF RFC 5869)][hkdf] /// to the bytes of the input string, but in a way that the calculated integer is unlikely /// to collide with any similar implementations (although we don't currently know of any). /// See the source of this method for details. /// /// [hkdf]: https://datatracker.ietf.org/doc/html/rfc5869 /// ### Example /// ```rust /// use sqlx::postgres::{PgAdvisoryLock, PgAdvisoryLockKey}; /// /// let lock = PgAdvisoryLock::new("my first Postgres advisory lock!"); /// // Negative values are fine because of how Postgres treats advisory lock keys. /// // See the documentation for the `pg_locks` system view for details. /// assert_eq!(lock.key(), &PgAdvisoryLockKey::BigInt(-5560419505042474287)); /// ``` pub fn new(key_string: impl AsRef) -> Self { let input_key_material = key_string.as_ref(); // HKDF was chosen because it is designed to concentrate the entropy in a variable-length // input key and produce a higher quality but reduced-length output key with a // well-specified and reproducible algorithm. // // Granted, the input key is usually meant to be pseudorandom and not human readable, // but we're not trying to produce an unguessable value by any means; just one that's as // unlikely to already be in use as possible, but still deterministic. // // SHA-256 was chosen as the hash function because it's already used in the Postgres driver, // which should save on codegen and optimization. // We don't supply a salt as that is intended to be random, but we want a deterministic key. let hkdf = Hkdf::::new(None, input_key_material.as_bytes()); let mut output_key_material = [0u8; 8]; // The first string is the "info" string of the HKDF which is intended to tie the output // exclusively to SQLx. This should avoid collisions with implementations using a similar // strategy. If you _want_ this to match some other implementation then you should get // the calculated integer key from it and use that directly. // // Do *not* change this string as it will affect the output! hkdf.expand( b"SQLx (Rust) Postgres advisory lock", &mut output_key_material, ) // `Hkdf::expand()` only returns an error if you ask for more than 255 times the digest size. // This is specified by RFC 5869 but not elaborated upon: // https://datatracker.ietf.org/doc/html/rfc5869#section-2.3 // Since we're only asking for 8 bytes, this error shouldn't be returned. .expect("BUG: `output_key_material` should be of acceptable length"); // For ease of use, this method assumes the user doesn't care which keyspace is used. // // It doesn't seem likely that someone would care about using the `(int, int)` keyspace // specifically unless they already had keys to use, in which case they wouldn't // care about this method. That's why we also provide `with_key()`. // // The choice of `from_le_bytes()` is mostly due to x86 being the most popular // architecture for server software, so it should be a no-op there. let key = PgAdvisoryLockKey::BigInt(i64::from_le_bytes(output_key_material)); tracing::trace!( ?key, key_string = ?input_key_material, "generated key from key string", ); Self::with_key(key) } /// Construct a `PgAdvisoryLock` with a manually supplied key. pub fn with_key(key: PgAdvisoryLockKey) -> Self { Self { key, release_query: Arc::new(OnceLock::new()), } } /// Returns the current key. pub fn key(&self) -> &PgAdvisoryLockKey { &self.key } // Why doesn't this use `Acquire`? Well, I tried it and got really useless errors // about "cannot project lifetimes to parent scope". // // It has something to do with how lifetimes work on the `Acquire` trait, I couldn't // be bothered to figure it out. Probably another issue with a lack of `async fn` in traits // or lazy normalization. /// Acquires an exclusive lock using `pg_advisory_lock()`, waiting until the lock is acquired. /// /// For a version that returns immediately instead of waiting, see [`Self::try_acquire()`]. /// /// A connection-like type is required to execute the call. Allowed types include `PgConnection`, /// `PoolConnection` and `Transaction`, as well as mutable references to /// any of these. /// /// The returned guard queues a `pg_advisory_unlock()` call on the connection when dropped, /// which will be executed the next time the connection is used, or when returned to a /// [`PgPool`][crate::PgPool] in the case of `PoolConnection`. /// /// Postgres allows a single connection to acquire a given lock more than once without releasing /// it first, so in that sense the lock is re-entrant. However, the number of unlock operations /// must match the number of lock operations for the lock to actually be released. /// /// See [Postgres' documentation for the Advisory Lock Functions][advisory-funcs] for details. /// /// [advisory-funcs]: https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS pub async fn acquire>( &self, mut conn: C, ) -> Result> { match &self.key { PgAdvisoryLockKey::BigInt(key) => { crate::query::query("SELECT pg_advisory_lock($1)") .bind(key) .execute(conn.as_mut()) .await?; } PgAdvisoryLockKey::IntPair(key1, key2) => { crate::query::query("SELECT pg_advisory_lock($1, $2)") .bind(key1) .bind(key2) .execute(conn.as_mut()) .await?; } } Ok(PgAdvisoryLockGuard::new(self.clone(), conn)) } /// Acquires an exclusive lock using `pg_try_advisory_lock()`, returning immediately /// if the lock could not be acquired. /// /// For a version that waits until the lock is acquired, see [`Self::acquire()`]. /// /// A connection-like type is required to execute the call. Allowed types include `PgConnection`, /// `PoolConnection` and `Transaction`, as well as mutable references to /// any of these. The connection is returned if the lock could not be acquired. /// /// The returned guard queues a `pg_advisory_unlock()` call on the connection when dropped, /// which will be executed the next time the connection is used, or when returned to a /// [`PgPool`][crate::PgPool] in the case of `PoolConnection`. /// /// Postgres allows a single connection to acquire a given lock more than once without releasing /// it first, so in that sense the lock is re-entrant. However, the number of unlock operations /// must match the number of lock operations for the lock to actually be released. /// /// See [Postgres' documentation for the Advisory Lock Functions][advisory-funcs] for details. /// /// [advisory-funcs]: https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS pub async fn try_acquire>( &self, mut conn: C, ) -> Result, C>> { let locked: bool = match &self.key { PgAdvisoryLockKey::BigInt(key) => { crate::query_scalar::query_scalar("SELECT pg_try_advisory_lock($1)") .bind(key) .fetch_one(conn.as_mut()) .await? } PgAdvisoryLockKey::IntPair(key1, key2) => { crate::query_scalar::query_scalar("SELECT pg_try_advisory_lock($1, $2)") .bind(key1) .bind(key2) .fetch_one(conn.as_mut()) .await? } }; if locked { Ok(Either::Left(PgAdvisoryLockGuard::new(self.clone(), conn))) } else { Ok(Either::Right(conn)) } } /// Execute `pg_advisory_unlock()` for this lock's key on the given connection. /// /// This is used by [`PgAdvisoryLockGuard::release_now()`] and is also provided for manually /// releasing the lock from connections returned by [`PgAdvisoryLockGuard::leak()`]. /// /// An error should only be returned if there is something wrong with the connection, /// in which case the lock will be automatically released by the connection closing anyway. /// /// The `boolean` value is that returned by `pg_advisory_lock()`. If it is `false`, it /// indicates that the lock was not actually held by the given connection and that a warning /// has been logged by the Postgres server. pub async fn force_release>(&self, mut conn: C) -> Result<(C, bool)> { let released: bool = match &self.key { PgAdvisoryLockKey::BigInt(key) => { crate::query_scalar::query_scalar("SELECT pg_advisory_unlock($1)") .bind(key) .fetch_one(conn.as_mut()) .await? } PgAdvisoryLockKey::IntPair(key1, key2) => { crate::query_scalar::query_scalar("SELECT pg_advisory_unlock($1, $2)") .bind(key1) .bind(key2) .fetch_one(conn.as_mut()) .await? } }; Ok((conn, released)) } fn get_release_query(&self) -> &str { self.release_query.get_or_init(|| match &self.key { PgAdvisoryLockKey::BigInt(key) => format!("SELECT pg_advisory_unlock({key})"), PgAdvisoryLockKey::IntPair(key1, key2) => { format!("SELECT pg_advisory_unlock({key1}, {key2})") } }) } } impl PgAdvisoryLockKey { /// Converts `Self::Bigint(bigint)` to `Some(bigint)` and all else to `None`. pub fn as_bigint(&self) -> Option { if let Self::BigInt(bigint) = self { Some(*bigint) } else { None } } } const NONE_ERR: &str = "BUG: PgAdvisoryLockGuard.conn taken"; impl> PgAdvisoryLockGuard { fn new(lock: PgAdvisoryLock, conn: C) -> Self { PgAdvisoryLockGuard { lock, conn: Some(conn), } } /// Immediately release the held advisory lock instead of when the connection is next used. /// /// An error should only be returned if there is something wrong with the connection, /// in which case the lock will be automatically released by the connection closing anyway. /// /// If `pg_advisory_unlock()` returns `false`, a warning will be logged, both by SQLx as /// well as the Postgres server. This would only happen if the lock was released without /// using this guard, or the connection was swapped using [`std::mem::replace()`]. pub async fn release_now(mut self) -> Result { let (conn, released) = self .lock .force_release(self.conn.take().expect(NONE_ERR)) .await?; if !released { tracing::warn!( lock = ?self.lock.key, "PgAdvisoryLockGuard: advisory lock was not held by the contained connection", ); } Ok(conn) } /// Cancel the release of the advisory lock, keeping it held until the connection is closed. /// /// To manually release the lock later, see [`PgAdvisoryLock::force_release()`]. pub fn leak(mut self) -> C { self.conn.take().expect(NONE_ERR) } } impl + AsRef> Deref for PgAdvisoryLockGuard { type Target = PgConnection; fn deref(&self) -> &Self::Target { self.conn.as_ref().expect(NONE_ERR).as_ref() } } /// Mutable access to the underlying connection is provided so it can still be used like normal, /// even allowing locks to be taken recursively. /// /// However, replacing the connection with a different one using, e.g. [`std::mem::replace()`] /// is a logic error and will cause a warning to be logged by the PostgreSQL server when this /// guard attempts to release the lock. impl + AsRef> DerefMut for PgAdvisoryLockGuard { fn deref_mut(&mut self) -> &mut Self::Target { self.conn.as_mut().expect(NONE_ERR).as_mut() } } impl + AsRef> AsRef for PgAdvisoryLockGuard { fn as_ref(&self) -> &PgConnection { self.conn.as_ref().expect(NONE_ERR).as_ref() } } /// Mutable access to the underlying connection is provided so it can still be used like normal, /// even allowing locks to be taken recursively. /// /// However, replacing the connection with a different one using, e.g. [`std::mem::replace()`] /// is a logic error and will cause a warning to be logged by the PostgreSQL server when this /// guard attempts to release the lock. impl> AsMut for PgAdvisoryLockGuard { fn as_mut(&mut self) -> &mut PgConnection { self.conn.as_mut().expect(NONE_ERR).as_mut() } } /// Queues a `pg_advisory_unlock()` call on the wrapped connection which will be flushed /// to the server the next time it is used, or when it is returned to [`PgPool`][crate::PgPool] /// in the case of [`PoolConnection`][crate::pool::PoolConnection]. impl> Drop for PgAdvisoryLockGuard { fn drop(&mut self) { if let Some(mut conn) = self.conn.take() { // Queue a simple query message to execute next time the connection is used. // The `async fn` versions can safely use the prepared statement protocol, // but this is the safest way to queue a query to execute on the next opportunity. conn.as_mut() .queue_simple_query(self.lock.get_release_query()) .expect("BUG: PgAdvisoryLock::get_release_query() somehow too long for protocol"); } } } ================================================ FILE: sqlx-postgres/src/any.rs ================================================ use crate::{ Either, PgColumn, PgConnectOptions, PgConnection, PgQueryResult, PgRow, PgTransactionManager, PgTypeInfo, Postgres, }; use futures_core::future::BoxFuture; use futures_core::stream::BoxStream; use futures_util::{stream, FutureExt, StreamExt, TryFutureExt, TryStreamExt}; use sqlx_core::sql_str::SqlStr; use std::{future, pin::pin}; use sqlx_core::any::{ AnyArguments, AnyColumn, AnyConnectOptions, AnyConnectionBackend, AnyQueryResult, AnyRow, AnyStatement, AnyTypeInfo, AnyTypeInfoKind, }; use crate::type_info::PgType; use sqlx_core::connection::Connection; use sqlx_core::database::Database; use sqlx_core::executor::Executor; use sqlx_core::ext::ustr::UStr; use sqlx_core::transaction::TransactionManager; sqlx_core::declare_driver_with_optional_migrate!(DRIVER = Postgres); impl AnyConnectionBackend for PgConnection { fn name(&self) -> &str { ::NAME } fn close(self: Box) -> BoxFuture<'static, sqlx_core::Result<()>> { Connection::close(*self).boxed() } fn close_hard(self: Box) -> BoxFuture<'static, sqlx_core::Result<()>> { Connection::close_hard(*self).boxed() } fn ping(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { Connection::ping(self).boxed() } fn begin(&mut self, statement: Option) -> BoxFuture<'_, sqlx_core::Result<()>> { PgTransactionManager::begin(self, statement).boxed() } fn commit(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { PgTransactionManager::commit(self).boxed() } fn rollback(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { PgTransactionManager::rollback(self).boxed() } fn start_rollback(&mut self) { PgTransactionManager::start_rollback(self) } fn get_transaction_depth(&self) -> usize { PgTransactionManager::get_transaction_depth(self) } fn shrink_buffers(&mut self) { Connection::shrink_buffers(self); } fn flush(&mut self) -> BoxFuture<'_, sqlx_core::Result<()>> { Connection::flush(self).boxed() } fn should_flush(&self) -> bool { Connection::should_flush(self) } #[cfg(feature = "migrate")] fn as_migrate( &mut self, ) -> sqlx_core::Result<&mut (dyn sqlx_core::migrate::Migrate + Send + 'static)> { Ok(self) } fn fetch_many( &mut self, query: SqlStr, persistent: bool, arguments: Option, ) -> BoxStream>> { let persistent = persistent && arguments.is_some(); let arguments = match arguments.map(AnyArguments::convert_into).transpose() { Ok(arguments) => arguments, Err(error) => { return stream::once(future::ready(Err(sqlx_core::Error::Encode(error)))).boxed() } }; Box::pin( self.run(query, arguments, persistent, None) .try_flatten_stream() .map( move |res: sqlx_core::Result>| match res? { Either::Left(result) => Ok(Either::Left(map_result(result))), Either::Right(row) => Ok(Either::Right(AnyRow::try_from(&row)?)), }, ), ) } fn fetch_optional( &mut self, query: SqlStr, persistent: bool, arguments: Option, ) -> BoxFuture>> { let persistent = persistent && arguments.is_some(); let arguments = arguments .map(AnyArguments::convert_into) .transpose() .map_err(sqlx_core::Error::Encode); Box::pin(async move { let arguments = arguments?; let mut stream = pin!(self.run(query, arguments, persistent, None).await?); if let Some(Either::Right(row)) = stream.try_next().await? { return Ok(Some(AnyRow::try_from(&row)?)); } Ok(None) }) } fn prepare_with<'c, 'q: 'c>( &'c mut self, sql: SqlStr, _parameters: &[AnyTypeInfo], ) -> BoxFuture<'c, sqlx_core::Result> { Box::pin(async move { let statement = Executor::prepare_with(self, sql, &[]).await?; let column_names = statement.metadata.column_names.clone(); AnyStatement::try_from_statement(statement, column_names) }) } #[cfg(feature = "offline")] fn describe<'c>( &mut self, sql: SqlStr, ) -> BoxFuture<'_, sqlx_core::Result>> { Box::pin(async move { let describe = Executor::describe(self, sql).await?; let columns = describe .columns .iter() .map(AnyColumn::try_from) .collect::, _>>()?; let parameters = match describe.parameters { Some(Either::Left(parameters)) => Some(Either::Left( parameters .iter() .enumerate() .map(|(i, type_info)| { AnyTypeInfo::try_from(type_info).map_err(|_| { sqlx_core::Error::AnyDriverError( format!( "Any driver does not support type {type_info} of parameter {i}" ) .into(), ) }) }) .collect::, _>>()?, )), Some(Either::Right(count)) => Some(Either::Right(count)), None => None, }; Ok(sqlx_core::describe::Describe { columns, parameters, nullable: describe.nullable, }) }) } } impl<'a> TryFrom<&'a PgTypeInfo> for AnyTypeInfo { type Error = sqlx_core::Error; fn try_from(pg_type: &'a PgTypeInfo) -> Result { Ok(AnyTypeInfo { kind: match &pg_type.0 { PgType::Bool => AnyTypeInfoKind::Bool, PgType::Void => AnyTypeInfoKind::Null, PgType::Int2 => AnyTypeInfoKind::SmallInt, PgType::Int4 => AnyTypeInfoKind::Integer, PgType::Int8 => AnyTypeInfoKind::BigInt, PgType::Float4 => AnyTypeInfoKind::Real, PgType::Float8 => AnyTypeInfoKind::Double, PgType::Bytea => AnyTypeInfoKind::Blob, PgType::Text | PgType::Varchar => AnyTypeInfoKind::Text, PgType::DeclareWithName(UStr::Static("citext")) => AnyTypeInfoKind::Text, _ => { return Err(sqlx_core::Error::AnyDriverError( format!("Any driver does not support the Postgres type {pg_type:?}").into(), )) } }, }) } } impl<'a> TryFrom<&'a PgColumn> for AnyColumn { type Error = sqlx_core::Error; fn try_from(col: &'a PgColumn) -> Result { let type_info = AnyTypeInfo::try_from(&col.type_info).map_err(|e| sqlx_core::Error::ColumnDecode { index: col.name.to_string(), source: e.into(), })?; Ok(AnyColumn { ordinal: col.ordinal, name: col.name.clone(), type_info, }) } } impl<'a> TryFrom<&'a PgRow> for AnyRow { type Error = sqlx_core::Error; fn try_from(row: &'a PgRow) -> Result { AnyRow::map_from(row, row.metadata.column_names.clone()) } } impl<'a> TryFrom<&'a AnyConnectOptions> for PgConnectOptions { type Error = sqlx_core::Error; fn try_from(value: &'a AnyConnectOptions) -> Result { let mut opts = PgConnectOptions::parse_from_url(&value.database_url)?; opts.log_settings = value.log_settings.clone(); Ok(opts) } } fn map_result(res: PgQueryResult) -> AnyQueryResult { AnyQueryResult { rows_affected: res.rows_affected(), last_insert_id: None, } } ================================================ FILE: sqlx-postgres/src/arguments.rs ================================================ use std::fmt::{self, Write}; use std::ops::{Deref, DerefMut}; use std::sync::Arc; use crate::encode::{Encode, IsNull}; use crate::error::Error; use crate::ext::ustr::UStr; use crate::types::Type; use crate::{PgConnection, PgTypeInfo, Postgres}; use crate::type_info::PgArrayOf; pub(crate) use sqlx_core::arguments::Arguments; use sqlx_core::error::BoxDynError; // TODO: buf.patch(|| ...) is a poor name, can we think of a better name? Maybe `buf.lazy(||)` ? // TODO: Extend the patch system to support dynamic lengths // Considerations: // - The prefixed-len offset needs to be back-tracked and updated // - message::Bind needs to take a &PgArguments and use a `write` method instead of // referencing a buffer directly // - The basic idea is that we write bytes for the buffer until we get somewhere // that has a patch, we then apply the patch which should write to &mut Vec, // backtrack and update the prefixed-len, then write until the next patch offset #[derive(Default, Debug, Clone)] pub struct PgArgumentBuffer { buffer: Vec, // Number of arguments count: usize, // Whenever an `Encode` impl needs to defer some work until after we resolve parameter types // it can use `patch`. // // This currently is only setup to be useful if there is a *fixed-size* slot that needs to be // tweaked from the input type. However, that's the only use case we currently have. patches: Vec, // Whenever an `Encode` impl encounters a `PgTypeInfo` object that does not have an OID // It pushes a "hole" that must be patched later. // // The hole is a `usize` offset into the buffer with the type name that should be resolved // This is done for Records and Arrays as the OID is needed well before we are in an async // function and can just ask postgres. // type_holes: Vec<(usize, HoleKind)>, // Vec<{ offset, type_name }> } #[derive(Debug, Clone)] enum HoleKind { Type { name: UStr }, Array(Arc), } #[derive(Clone)] struct Patch { buf_offset: usize, arg_index: usize, #[allow(clippy::type_complexity)] callback: Arc, } impl fmt::Debug for Patch { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Patch") .field("buf_offset", &self.buf_offset) .field("arg_index", &self.arg_index) .field("callback", &"") .finish() } } /// Implementation of [`Arguments`] for PostgreSQL. #[derive(Default, Debug, Clone)] pub struct PgArguments { // Types of each bind parameter pub(crate) types: Vec, // Buffer of encoded bind parameters pub(crate) buffer: PgArgumentBuffer, } impl PgArguments { pub(crate) fn add<'q, T>(&mut self, value: T) -> Result<(), BoxDynError> where T: Encode<'q, Postgres> + Type, { let type_info = value.produces().unwrap_or_else(T::type_info); let buffer_snapshot = self.buffer.snapshot(); // encode the value into our buffer if let Err(error) = self.buffer.encode(value) { // reset the value buffer to its previous value if encoding failed, // so we don't leave a half-encoded value behind self.buffer.reset_to_snapshot(buffer_snapshot); return Err(error); }; // remember the type information for this value self.types.push(type_info); // increment the number of arguments we are tracking self.buffer.count += 1; Ok(()) } // Apply patches // This should only go out and ask postgres if we have not seen the type name yet pub(crate) async fn apply_patches( &mut self, conn: &mut PgConnection, parameters: &[PgTypeInfo], ) -> Result<(), Error> { let PgArgumentBuffer { ref patches, ref type_holes, ref mut buffer, .. } = self.buffer; for patch in patches { let buf = &mut buffer[patch.buf_offset..]; let ty = ¶meters[patch.arg_index]; (patch.callback)(buf, ty); } for (offset, kind) in type_holes { let oid = match kind { HoleKind::Type { name } => conn.fetch_type_id_by_name(name).await?, HoleKind::Array(array) => conn.fetch_array_type_id(array).await?, }; buffer[*offset..(*offset + 4)].copy_from_slice(&oid.0.to_be_bytes()); } Ok(()) } } impl Arguments for PgArguments { type Database = Postgres; fn reserve(&mut self, additional: usize, size: usize) { self.types.reserve(additional); self.buffer.reserve(size); } fn add<'t, T>(&mut self, value: T) -> Result<(), BoxDynError> where T: Encode<'t, Self::Database> + Type, { self.add(value) } fn format_placeholder(&self, writer: &mut W) -> fmt::Result { write!(writer, "${}", self.buffer.count) } #[inline(always)] fn len(&self) -> usize { self.buffer.count } } impl PgArgumentBuffer { pub(crate) fn encode<'q, T>(&mut self, value: T) -> Result<(), BoxDynError> where T: Encode<'q, Postgres>, { // Won't catch everything but is a good sanity check value_size_int4_checked(value.size_hint())?; // reserve space to write the prefixed length of the value let offset = self.len(); self.extend(&[0; 4]); // encode the value into our buffer let len = if let IsNull::No = value.encode(self)? { // Ensure that the value size does not overflow i32 value_size_int4_checked(self.len() - offset - 4)? } else { // Write a -1 to indicate NULL // NOTE: It is illegal for [encode] to write any data debug_assert_eq!(self.len(), offset + 4); -1_i32 }; // write the len to the beginning of the value // (offset + 4) cannot overflow because it would have failed at `self.extend()`. self[offset..(offset + 4)].copy_from_slice(&len.to_be_bytes()); Ok(()) } // Adds a callback to be invoked later when we know the parameter type #[allow(dead_code)] pub(crate) fn patch(&mut self, callback: F) where F: Fn(&mut [u8], &PgTypeInfo) + 'static + Send + Sync, { let offset = self.len(); let arg_index = self.count; self.patches.push(Patch { buf_offset: offset, arg_index, callback: Arc::new(callback), }); } // Extends the inner buffer by enough space to have an OID // Remembers where the OID goes and type name for the OID pub(crate) fn patch_type_by_name(&mut self, type_name: &UStr) { let offset = self.len(); self.extend_from_slice(&0_u32.to_be_bytes()); self.type_holes.push(( offset, HoleKind::Type { name: type_name.clone(), }, )); } pub(crate) fn patch_array_type(&mut self, array: Arc) { let offset = self.len(); self.extend_from_slice(&0_u32.to_be_bytes()); self.type_holes.push((offset, HoleKind::Array(array))); } fn snapshot(&self) -> PgArgumentBufferSnapshot { let Self { buffer, count, patches, type_holes, } = self; PgArgumentBufferSnapshot { buffer_length: buffer.len(), count: *count, patches_length: patches.len(), type_holes_length: type_holes.len(), } } fn reset_to_snapshot( &mut self, PgArgumentBufferSnapshot { buffer_length, count, patches_length, type_holes_length, }: PgArgumentBufferSnapshot, ) { self.buffer.truncate(buffer_length); self.count = count; self.patches.truncate(patches_length); self.type_holes.truncate(type_holes_length); } } struct PgArgumentBufferSnapshot { buffer_length: usize, count: usize, patches_length: usize, type_holes_length: usize, } impl Deref for PgArgumentBuffer { type Target = Vec; #[inline] fn deref(&self) -> &Self::Target { &self.buffer } } impl DerefMut for PgArgumentBuffer { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.buffer } } pub(crate) fn value_size_int4_checked(size: usize) -> Result { i32::try_from(size).map_err(|_| { format!( "value size would overflow in the binary protocol encoding: {size} > {}", i32::MAX ) }) } ================================================ FILE: sqlx-postgres/src/bind_iter.rs ================================================ use crate::{type_info::PgType, PgArgumentBuffer, PgHasArrayType, PgTypeInfo, Postgres}; use core::cell::Cell; use sqlx_core::{ database::Database, encode::{Encode, IsNull}, error::BoxDynError, types::Type, }; // not exported but pub because it is used in the extension trait pub struct PgBindIter(Cell>); /// Iterator extension trait enabling iterators to encode arrays in Postgres. /// /// Because of the blanket impl of `PgHasArrayType` for all references /// we can borrow instead of needing to clone or copy in the iterators /// and it still works /// /// Previously, 3 separate arrays would be needed in this example which /// requires iterating 3 times to collect items into the array and then /// iterating over them again to encode. /// /// This now requires only iterating over the array once for each field /// while using less memory giving both speed and memory usage improvements /// along with allowing much more flexibility in the underlying collection. /// /// ```rust,no_run /// # async fn test_bind_iter() -> Result<(), sqlx::error::BoxDynError> { /// # use sqlx::types::chrono::{DateTime, Utc}; /// # use sqlx::Connection; /// # fn people() -> &'static [Person] { /// # &[] /// # } /// # let mut conn = ::Connection::connect("dummyurl").await?; /// use sqlx::postgres::PgBindIterExt; /// /// #[derive(sqlx::FromRow)] /// struct Person { /// id: i64, /// name: String, /// birthdate: DateTime, /// } /// /// # let people: &[Person] = people(); /// sqlx::query("insert into person(id, name, birthdate) select * from unnest($1, $2, $3)") /// .bind(people.iter().map(|p| p.id).bind_iter()) /// .bind(people.iter().map(|p| &p.name).bind_iter()) /// .bind(people.iter().map(|p| &p.birthdate).bind_iter()) /// .execute(&mut conn) /// .await?; /// /// # Ok(()) /// # } /// ``` pub trait PgBindIterExt: Iterator + Sized { fn bind_iter(self) -> PgBindIter; } impl PgBindIterExt for I { fn bind_iter(self) -> PgBindIter { PgBindIter(Cell::new(Some(self))) } } impl Type for PgBindIter where I: Iterator, ::Item: Type + PgHasArrayType, { fn type_info() -> ::TypeInfo { ::Item::array_type_info() } fn compatible(ty: &PgTypeInfo) -> bool { ::Item::array_compatible(ty) } } impl<'q, I> PgBindIter where I: Iterator, ::Item: Type + Encode<'q, Postgres>, { fn encode_inner( // need ownership to iterate mut iter: I, buf: &mut PgArgumentBuffer, ) -> Result { let lower_size_hint = iter.size_hint().0; let first = iter.next(); let type_info = first .as_ref() .and_then(Encode::produces) .unwrap_or_else(::Item::type_info); buf.extend(&1_i32.to_be_bytes()); // number of dimensions buf.extend(&0_i32.to_be_bytes()); // flags match type_info.0 { PgType::DeclareWithName(name) => buf.patch_type_by_name(&name), PgType::DeclareArrayOf(array) => buf.patch_array_type(array), ty => { buf.extend(&ty.oid().0.to_be_bytes()); } } let len_start = buf.len(); buf.extend(0_i32.to_be_bytes()); // len (unknown so far) buf.extend(1_i32.to_be_bytes()); // lower bound match first { Some(first) => buf.encode(first)?, None => return Ok(IsNull::No), } let mut count = 1_i32; const MAX: usize = i32::MAX as usize - 1; for value in (&mut iter).take(MAX) { buf.encode(value)?; count += 1; } const OVERFLOW: usize = i32::MAX as usize + 1; if iter.next().is_some() { let iter_size = std::cmp::max(lower_size_hint, OVERFLOW); return Err(format!("encoded iterator is too large for Postgres: {iter_size}").into()); } // set the length now that we know what it is. buf[len_start..(len_start + 4)].copy_from_slice(&count.to_be_bytes()); Ok(IsNull::No) } } impl<'q, I> Encode<'q, Postgres> for PgBindIter where I: Iterator, ::Item: Type + Encode<'q, Postgres>, { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { Self::encode_inner(self.0.take().expect("PgBindIter is only used once"), buf) } fn encode(self, buf: &mut PgArgumentBuffer) -> Result where Self: Sized, { Self::encode_inner( self.0.into_inner().expect("PgBindIter is only used once"), buf, ) } } ================================================ FILE: sqlx-postgres/src/column.rs ================================================ use crate::ext::ustr::UStr; use crate::{PgTypeInfo, Postgres}; use sqlx_core::column::ColumnOrigin; pub(crate) use sqlx_core::column::{Column, ColumnIndex}; #[derive(Debug, Clone)] #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] pub struct PgColumn { pub(crate) ordinal: usize, pub(crate) name: UStr, pub(crate) type_info: PgTypeInfo, #[cfg_attr(feature = "offline", serde(default))] pub(crate) origin: ColumnOrigin, #[cfg_attr(feature = "offline", serde(skip))] pub(crate) relation_id: Option, #[cfg_attr(feature = "offline", serde(skip))] pub(crate) relation_attribute_no: Option, } impl PgColumn { /// Returns the OID of the table this column is from, if applicable. /// /// This will be `None` if the column is the result of an expression. /// /// Corresponds to column `attrelid` of the `pg_catalog.pg_attribute` table: /// pub fn relation_id(&self) -> Option { self.relation_id } /// Returns the 1-based index of this column in its parent table, if applicable. /// /// This will be `None` if the column is the result of an expression. /// /// Corresponds to column `attnum` of the `pg_catalog.pg_attribute` table: /// pub fn relation_attribute_no(&self) -> Option { self.relation_attribute_no } } impl Column for PgColumn { type Database = Postgres; fn ordinal(&self) -> usize { self.ordinal } fn name(&self) -> &str { &self.name } fn type_info(&self) -> &PgTypeInfo { &self.type_info } fn origin(&self) -> ColumnOrigin { self.origin.clone() } } ================================================ FILE: sqlx-postgres/src/connection/describe.rs ================================================ use crate::error::Error; use crate::io::StatementId; use crate::query_as::query_as; use crate::statement::PgStatementMetadata; use crate::types::Json; use crate::PgConnection; use smallvec::SmallVec; use sqlx_core::query_builder::QueryBuilder; use sqlx_core::sql_str::AssertSqlSafe; impl PgConnection { /// Check whether EXPLAIN statements are supported by the current connection fn is_explain_available(&self) -> bool { let parameter_statuses = &self.inner.stream.parameter_statuses; let is_cockroachdb = parameter_statuses.contains_key("crdb_version"); let is_materialize = parameter_statuses.contains_key("mz_version"); let is_questdb = parameter_statuses.contains_key("questdb_version"); !is_cockroachdb && !is_materialize && !is_questdb } pub(crate) async fn get_nullable_for_columns( &mut self, stmt_id: StatementId, meta: &PgStatementMetadata, ) -> Result>, Error> { if meta.columns.is_empty() { return Ok(vec![]); } if meta.columns.len() * 3 > 65535 { tracing::debug!( ?stmt_id, num_columns = meta.columns.len(), "number of columns in query is too large to pull nullability for" ); } // Query for NOT NULL constraints for each column in the query. // // This will include columns that don't have a `relation_id` (are not from a table); // assuming those are a minority of columns, it's less code to _not_ work around it // and just let Postgres return `NULL`. // // Use `UNION ALL` syntax instead of `VALUES` due to frequent lack of // support for `VALUES` in pgwire supported databases. let mut nullable_query = QueryBuilder::new("SELECT NOT attnotnull FROM ( "); let mut separated = nullable_query.separated("UNION ALL "); let mut column_iter = meta.columns.iter().zip(0i32..); if let Some((column, i)) = column_iter.next() { separated.push("( SELECT "); separated .push_bind_unseparated(i) .push_unseparated("::int4 AS idx, "); separated .push_bind_unseparated(column.relation_id) .push_unseparated("::int4 AS table_id, "); separated .push_bind_unseparated(column.relation_attribute_no) .push_unseparated("::int2 AS col_idx ) "); } for (column, i) in column_iter { separated.push("( SELECT "); separated .push_bind_unseparated(i) .push_unseparated("::int4, "); separated .push_bind_unseparated(column.relation_id) .push_unseparated("::int4, "); separated .push_bind_unseparated(column.relation_attribute_no) .push_unseparated("::int2 ) "); } nullable_query.push( ") AS col LEFT JOIN pg_catalog.pg_attribute \ ON table_id IS NOT NULL \ AND attrelid = table_id \ AND attnum = col_idx \ ORDER BY idx", ); let mut nullables: Vec> = nullable_query .build_query_scalar() .fetch_all(&mut *self) .await .map_err(|e| { err_protocol!( "error from nullables query: {e}; query: {:?}", nullable_query.sql() ) })?; // If the server doesn't support EXPLAIN statements, skip this step (#1248). if self.is_explain_available() { // patch up our null inference with data from EXPLAIN let nullable_patch = self .nullables_from_explain(stmt_id, meta.parameters.len()) .await?; for (nullable, patch) in nullables.iter_mut().zip(nullable_patch) { *nullable = patch.or(*nullable); } } Ok(nullables) } /// Infer nullability for columns of this statement using EXPLAIN VERBOSE. /// /// This currently only marks columns that are on the inner half of an outer join /// and returns `None` for all others. async fn nullables_from_explain( &mut self, stmt_id: StatementId, params_len: usize, ) -> Result>, Error> { let stmt_id_display = stmt_id .display() .ok_or_else(|| err_protocol!("cannot EXPLAIN unnamed statement: {stmt_id:?}"))?; let mut explain = format!("EXPLAIN (VERBOSE, FORMAT JSON) EXECUTE {stmt_id_display}"); let mut comma = false; if params_len > 0 { explain += "("; // fill the arguments list with NULL, which should theoretically be valid for _ in 0..params_len { if comma { explain += ", "; } explain += "NULL"; comma = true; } explain += ")"; } let (Json(explains),): (Json>,) = query_as(AssertSqlSafe(explain)).fetch_one(self).await?; let mut nullables = Vec::new(); if let Some(Explain::Plan { plan: plan @ Plan { output: Some(ref outputs), .. }, }) = explains.first() { nullables.resize(outputs.len(), None); visit_plan(plan, outputs, &mut nullables); } Ok(nullables) } } fn visit_plan(plan: &Plan, outputs: &[String], nullables: &mut Vec>) { if let Some(plan_outputs) = &plan.output { // all outputs of a Full Join must be marked nullable // otherwise, all outputs of the inner half of an outer join must be marked nullable if plan.join_type.as_deref() == Some("Full") || plan.parent_relation.as_deref() == Some("Inner") { for output in plan_outputs { if let Some(i) = outputs.iter().position(|o| o == output) { // N.B. this may produce false positives but those don't cause runtime errors nullables[i] = Some(true); } } } } if let Some(plans) = &plan.plans { if let Some("Left") | Some("Right") = plan.join_type.as_deref() { for plan in plans { visit_plan(plan, outputs, nullables); } } } } #[derive(serde::Deserialize, Debug)] #[serde(untagged)] enum Explain { // NOTE: the returned JSON may not contain a `plan` field, for example, with `CALL` statements: // https://github.com/launchbadge/sqlx/issues/1449 // // In this case, we should just fall back to assuming all is nullable. // // It may also contain additional fields we don't care about, which should not break parsing: // https://github.com/launchbadge/sqlx/issues/2587 // https://github.com/launchbadge/sqlx/issues/2622 Plan { #[serde(rename = "Plan")] plan: Plan, }, // This ensures that parsing never technically fails. // // We don't want to specifically expect `"Utility Statement"` because there might be other cases // and we don't care unless it contains a query plan anyway. Other(serde::de::IgnoredAny), } #[derive(serde::Deserialize, Debug)] struct Plan { #[serde(rename = "Join Type")] join_type: Option, #[serde(rename = "Parent Relationship")] parent_relation: Option, #[serde(rename = "Output")] output: Option>, #[serde(rename = "Plans")] plans: Option>, } #[test] fn explain_parsing() { let normal_plan = r#"[ { "Plan": { "Node Type": "Result", "Parallel Aware": false, "Async Capable": false, "Startup Cost": 0.00, "Total Cost": 0.01, "Plan Rows": 1, "Plan Width": 4, "Output": ["1"] } } ]"#; // https://github.com/launchbadge/sqlx/issues/2622 let extra_field = r#"[ { "Plan": { "Node Type": "Result", "Parallel Aware": false, "Async Capable": false, "Startup Cost": 0.00, "Total Cost": 0.01, "Plan Rows": 1, "Plan Width": 4, "Output": ["1"] }, "Query Identifier": 1147616880456321454 } ]"#; // https://github.com/launchbadge/sqlx/issues/1449 let utility_statement = r#"["Utility Statement"]"#; let normal_plan_parsed = serde_json::from_str::<[Explain; 1]>(normal_plan).unwrap(); let extra_field_parsed = serde_json::from_str::<[Explain; 1]>(extra_field).unwrap(); let utility_statement_parsed = serde_json::from_str::<[Explain; 1]>(utility_statement).unwrap(); assert!( matches!(normal_plan_parsed, [Explain::Plan { plan: Plan { .. } }]), "unexpected parse from {normal_plan:?}: {normal_plan_parsed:?}" ); assert!( matches!(extra_field_parsed, [Explain::Plan { plan: Plan { .. } }]), "unexpected parse from {extra_field:?}: {extra_field_parsed:?}" ); assert!( matches!(utility_statement_parsed, [Explain::Other(_)]), "unexpected parse from {utility_statement:?}: {utility_statement_parsed:?}" ) } ================================================ FILE: sqlx-postgres/src/connection/establish.rs ================================================ use crate::HashMap; use crate::common::StatementCache; use crate::connection::{sasl, stream::PgStream}; use crate::error::Error; use crate::io::StatementId; use crate::message::{ Authentication, BackendKeyData, BackendMessageFormat, Password, ReadyForQuery, Startup, }; use crate::{PgConnectOptions, PgConnection}; use super::PgConnectionInner; // https://www.postgresql.org/docs/current/protocol-flow.html#id-1.10.5.7.3 // https://www.postgresql.org/docs/current/protocol-flow.html#id-1.10.5.7.11 impl PgConnection { pub(crate) async fn establish(options: &PgConnectOptions) -> Result { // Upgrade to TLS if we were asked to and the server supports it let mut stream = PgStream::connect(options).await?; // To begin a session, a frontend opens a connection to the server // and sends a startup message. let mut params = vec![ // Sets the display format for date and time values, // as well as the rules for interpreting ambiguous date input values. ("DateStyle", "ISO, MDY"), // Sets the client-side encoding (character set). // ("client_encoding", "UTF8"), // Sets the time zone for displaying and interpreting time stamps. ("TimeZone", "UTC"), ]; if let Some(ref extra_float_digits) = options.extra_float_digits { params.push(("extra_float_digits", extra_float_digits)); } if let Some(ref application_name) = options.application_name { params.push(("application_name", application_name)); } if let Some(ref options) = options.options { params.push(("options", options)); } stream.write(Startup { username: Some(&options.username), database: options.database.as_deref(), params: ¶ms, })?; stream.flush().await?; // The server then uses this information and the contents of // its configuration files (such as pg_hba.conf) to determine whether the connection is // provisionally acceptable, and what additional // authentication is required (if any). let mut process_id = 0; let mut secret_key = 0; let transaction_status; loop { let message = stream.recv().await?; match message.format { BackendMessageFormat::Authentication => match message.decode()? { Authentication::Ok => { // the authentication exchange is successfully completed // do nothing; no more information is required to continue } Authentication::CleartextPassword => { // The frontend must now send a [PasswordMessage] containing the // password in clear-text form. stream .send(Password::Cleartext( options.password.as_deref().unwrap_or_default(), )) .await?; } Authentication::Md5Password(body) => { // The frontend must now send a [PasswordMessage] containing the // password (with user name) encrypted via MD5, then encrypted again // using the 4-byte random salt specified in the // [AuthenticationMD5Password] message. stream .send(Password::Md5 { username: &options.username, password: options.password.as_deref().unwrap_or_default(), salt: body.salt, }) .await?; } Authentication::Sasl(body) => { sasl::authenticate(&mut stream, options, body).await?; } method => { return Err(err_protocol!( "unsupported authentication method: {:?}", method )); } }, BackendMessageFormat::BackendKeyData => { // provides secret-key data that the frontend must save if it wants to be // able to issue cancel requests later let data: BackendKeyData = message.decode()?; process_id = data.process_id; secret_key = data.secret_key; } BackendMessageFormat::ReadyForQuery => { // start-up is completed. The frontend can now issue commands transaction_status = message.decode::()?.transaction_status; break; } _ => { return Err(err_protocol!( "establish: unexpected message: {:?}", message.format )) } } } Ok(PgConnection { inner: Box::new(PgConnectionInner { stream, process_id, secret_key, transaction_status, transaction_depth: 0, pending_ready_for_query_count: 0, next_statement_id: StatementId::NAMED_START, cache_statement: StatementCache::new(options.statement_cache_capacity), cache_type_oid: HashMap::new(), cache_type_info: HashMap::new(), cache_elem_type_to_array: HashMap::new(), cache_table_to_column_names: HashMap::new(), log_settings: options.log_settings.clone(), }), }) } } ================================================ FILE: sqlx-postgres/src/connection/executor.rs ================================================ use crate::error::Error; use crate::executor::{Execute, Executor}; use crate::io::{PortalId, StatementId}; use crate::logger::QueryLogger; use crate::message::{ self, BackendMessageFormat, Bind, Close, CommandComplete, DataRow, ParameterDescription, Parse, ParseComplete, Query, RowDescription, }; use crate::statement::PgStatementMetadata; use crate::{ statement::PgStatement, PgArguments, PgConnection, PgQueryResult, PgRow, PgTypeInfo, PgValueFormat, Postgres, }; use futures_core::future::BoxFuture; use futures_core::stream::BoxStream; use futures_core::Stream; use futures_util::TryStreamExt; use sqlx_core::arguments::Arguments; use sqlx_core::sql_str::SqlStr; use sqlx_core::Either; use std::{pin::pin, sync::Arc}; async fn prepare( conn: &mut PgConnection, sql: &str, parameters: &[PgTypeInfo], metadata: Option>, persistent: bool, fetch_column_origin: bool, ) -> Result<(StatementId, Arc), Error> { let id = if persistent { let id = conn.inner.next_statement_id; conn.inner.next_statement_id = id.next(); id } else { StatementId::UNNAMED }; // build a list of type OIDs to send to the database in the PARSE command // we have not yet started the query sequence, so we are *safe* to cleanly make // additional queries here to get any missing OIDs let mut param_types = Vec::with_capacity(parameters.len()); for ty in parameters { param_types.push(conn.resolve_type_id(&ty.0).await?); } // flush and wait until we are re-ready conn.wait_until_ready().await?; // next we send the PARSE command to the server conn.inner.stream.write_msg(Parse { param_types: ¶m_types, query: sql, statement: id, })?; if metadata.is_none() { // get the statement columns and parameters conn.inner .stream .write_msg(message::Describe::Statement(id))?; } // we ask for the server to immediately send us the result of the PARSE command conn.write_sync(); conn.inner.stream.flush().await?; // indicates that the SQL query string is now successfully parsed and has semantic validity conn.inner.stream.recv_expect::().await?; let metadata = if let Some(metadata) = metadata { // each SYNC produces one READY FOR QUERY conn.recv_ready_for_query().await?; // we already have metadata metadata } else { let parameters = recv_desc_params(conn).await?; let rows = recv_desc_rows(conn).await?; // each SYNC produces one READY FOR QUERY conn.recv_ready_for_query().await?; let parameters = conn.handle_parameter_description(parameters).await?; let (columns, column_names) = conn .handle_row_description(rows, true, fetch_column_origin) .await?; // ensure that if we did fetch custom data, we wait until we are fully ready before // continuing conn.wait_until_ready().await?; Arc::new(PgStatementMetadata { parameters, columns, column_names: Arc::new(column_names), }) }; Ok((id, metadata)) } async fn recv_desc_params(conn: &mut PgConnection) -> Result { conn.inner.stream.recv_expect().await } async fn recv_desc_rows(conn: &mut PgConnection) -> Result, Error> { let rows: Option = match conn.inner.stream.recv().await? { // describes the rows that will be returned when the statement is eventually executed message if message.format == BackendMessageFormat::RowDescription => { Some(message.decode()?) } // no data would be returned if this statement was executed message if message.format == BackendMessageFormat::NoData => None, message => { return Err(err_protocol!( "expecting RowDescription or NoData but received {:?}", message.format )); } }; Ok(rows) } impl PgConnection { // wait for CloseComplete to indicate a statement was closed pub(super) async fn wait_for_close_complete(&mut self, mut count: usize) -> Result<(), Error> { // we need to wait for the [CloseComplete] to be returned from the server while count > 0 { match self.inner.stream.recv().await? { message if message.format == BackendMessageFormat::PortalSuspended => { // there was an open portal // this can happen if the last time a statement was used it was not fully executed } message if message.format == BackendMessageFormat::CloseComplete => { // successfully closed the statement (and freed up the server resources) count -= 1; } message => { return Err(err_protocol!( "expecting PortalSuspended or CloseComplete but received {:?}", message.format )); } } } Ok(()) } #[inline(always)] pub(crate) fn write_sync(&mut self) { self.inner .stream .write_msg(message::Sync) .expect("BUG: Sync should not be too big for protocol"); // all SYNC messages will return a ReadyForQuery self.inner.pending_ready_for_query_count += 1; } async fn get_or_prepare( &mut self, sql: &str, parameters: &[PgTypeInfo], persistent: bool, // optional metadata that was provided by the user, this means they are reusing // a statement object metadata: Option>, fetch_column_origin: bool, ) -> Result<(StatementId, Arc), Error> { if let Some(statement) = self.inner.cache_statement.get_mut(sql) { return Ok((*statement).clone()); } let statement = prepare( self, sql, parameters, metadata, persistent, fetch_column_origin, ) .await?; if persistent && self.inner.cache_statement.is_enabled() { if let Some((id, _)) = self.inner.cache_statement.insert(sql, statement.clone()) { self.inner.stream.write_msg(Close::Statement(id))?; self.write_sync(); self.inner.stream.flush().await?; self.wait_for_close_complete(1).await?; self.recv_ready_for_query().await?; } } Ok(statement) } pub(crate) async fn run<'e, 'c: 'e, 'q: 'e>( &'c mut self, query: SqlStr, arguments: Option, persistent: bool, metadata_opt: Option>, ) -> Result, Error>> + 'e, Error> { let mut logger = QueryLogger::new(query, self.inner.log_settings.clone()); let sql = logger.sql().as_str(); // before we continue, wait until we are "ready" to accept more queries self.wait_until_ready().await?; let mut metadata: Arc; let format = if let Some(mut arguments) = arguments { // Check this before we write anything to the stream. // // Note: Postgres actually interprets this value as unsigned, // making the max number of parameters 65535, not 32767 // https://github.com/launchbadge/sqlx/issues/3464 // https://www.postgresql.org/docs/current/limits.html let num_params = u16::try_from(arguments.len()).map_err(|_| { err_protocol!( "PgConnection::run(): too many arguments for query: {}", arguments.len() ) })?; // prepare the statement if this our first time executing it // always return the statement ID here let (statement, metadata_) = self .get_or_prepare(sql, &arguments.types, persistent, metadata_opt, false) .await?; metadata = metadata_; // patch holes created during encoding arguments.apply_patches(self, &metadata.parameters).await?; // consume messages till `ReadyForQuery` before bind and execute self.wait_until_ready().await?; // bind to attach the arguments to the statement and create a portal self.inner.stream.write_msg(Bind { portal: PortalId::UNNAMED, statement, formats: &[PgValueFormat::Binary], num_params, params: &arguments.buffer, result_formats: &[PgValueFormat::Binary], })?; // executes the portal up to the passed limit // the protocol-level limit acts nearly identically to the `LIMIT` in SQL self.inner.stream.write_msg(message::Execute { portal: PortalId::UNNAMED, // Non-zero limits cause query plan pessimization by disabling parallel workers: // https://github.com/launchbadge/sqlx/issues/3673 limit: 0, })?; // From https://www.postgresql.org/docs/current/protocol-flow.html: // // "An unnamed portal is destroyed at the end of the transaction, or as // soon as the next Bind statement specifying the unnamed portal as // destination is issued. (Note that a simple Query message also // destroys the unnamed portal." // we ask the database server to close the unnamed portal and free the associated resources // earlier - after the execution of the current query. self.inner .stream .write_msg(Close::Portal(PortalId::UNNAMED))?; // finally, [Sync] asks postgres to process the messages that we sent and respond with // a [ReadyForQuery] message when it's completely done. Theoretically, we could send // dozens of queries before a [Sync] and postgres can handle that. Execution on the server // is still serial but it would reduce round-trips. Some kind of builder pattern that is // termed batching might suit this. self.write_sync(); // prepared statements are binary PgValueFormat::Binary } else { // Query will trigger a ReadyForQuery self.inner.stream.write_msg(Query(sql))?; self.inner.pending_ready_for_query_count += 1; // metadata starts out as "nothing" metadata = Arc::new(PgStatementMetadata::default()); // and unprepared statements are text PgValueFormat::Text }; self.inner.stream.flush().await?; Ok(try_stream! { loop { let message = self.inner.stream.recv().await?; match message.format { BackendMessageFormat::BindComplete | BackendMessageFormat::ParseComplete | BackendMessageFormat::ParameterDescription | BackendMessageFormat::NoData // unnamed portal has been closed | BackendMessageFormat::CloseComplete => { // harmless messages to ignore } // "Execute phase is always terminated by the appearance of // exactly one of these messages: CommandComplete, // EmptyQueryResponse (if the portal was created from an // empty query string), ErrorResponse, or PortalSuspended" BackendMessageFormat::CommandComplete => { // a SQL command completed normally let cc: CommandComplete = message.decode()?; let rows_affected = cc.rows_affected(); logger.increase_rows_affected(rows_affected); r#yield!(Either::Left(PgQueryResult { rows_affected, })); } BackendMessageFormat::EmptyQueryResponse => { // empty query string passed to an unprepared execute } // Message::ErrorResponse is handled in self.stream.recv() // incomplete query execution has finished BackendMessageFormat::PortalSuspended => {} BackendMessageFormat::RowDescription => { // indicates that a *new* set of rows are about to be returned let (columns, column_names) = self .handle_row_description(Some(message.decode()?), false, false) .await?; metadata = Arc::new(PgStatementMetadata { column_names: Arc::new(column_names), columns, parameters: Vec::default(), }); } BackendMessageFormat::DataRow => { logger.increment_rows_returned(); // one of the set of rows returned by a SELECT, FETCH, etc query let data: DataRow = message.decode()?; let row = PgRow { data, format, metadata: Arc::clone(&metadata), }; r#yield!(Either::Right(row)); } BackendMessageFormat::ReadyForQuery => { // processing of the query string is complete self.handle_ready_for_query(message)?; break; } _ => { return Err(err_protocol!( "execute: unexpected message: {:?}", message.format )); } } } Ok(()) }) } } impl<'c> Executor<'c> for &'c mut PgConnection { type Database = Postgres; fn fetch_many<'e, 'q, E>( self, mut query: E, ) -> BoxStream<'e, Result, Error>> where 'c: 'e, E: Execute<'q, Self::Database>, 'q: 'e, E: 'q, { // False positive: https://github.com/rust-lang/rust-clippy/issues/12560 #[allow(clippy::map_clone)] let metadata = query.statement().map(|s| Arc::clone(&s.metadata)); let arguments = query.take_arguments().map_err(Error::Encode); let persistent = query.persistent(); let sql = query.sql(); Box::pin(try_stream! { let arguments = arguments?; let mut s = pin!(self.run(sql, arguments, persistent, metadata).await?); while let Some(v) = s.try_next().await? { r#yield!(v); } Ok(()) }) } fn fetch_optional<'e, 'q, E>(self, mut query: E) -> BoxFuture<'e, Result, Error>> where 'c: 'e, E: Execute<'q, Self::Database>, 'q: 'e, E: 'q, { // False positive: https://github.com/rust-lang/rust-clippy/issues/12560 #[allow(clippy::map_clone)] let metadata = query.statement().map(|s| Arc::clone(&s.metadata)); let arguments = query.take_arguments().map_err(Error::Encode); let persistent = query.persistent(); Box::pin(async move { let sql = query.sql(); let arguments = arguments?; let mut s = pin!(self.run(sql, arguments, persistent, metadata).await?); // With deferred constraints we need to check all responses as we // could get a OK response (with uncommitted data), only to get an // error response after (when the deferred constraint is actually // checked). let mut ret = None; while let Some(result) = s.try_next().await? { match result { Either::Right(r) if ret.is_none() => ret = Some(r), _ => {} } } Ok(ret) }) } fn prepare_with<'e>( self, sql: SqlStr, parameters: &'e [PgTypeInfo], ) -> BoxFuture<'e, Result> where 'c: 'e, { Box::pin(async move { self.wait_until_ready().await?; let (_, metadata) = self .get_or_prepare(sql.as_str(), parameters, true, None, true) .await?; Ok(PgStatement { sql, metadata }) }) } #[cfg(feature = "offline")] fn describe<'e>( self, sql: SqlStr, ) -> BoxFuture<'e, Result, Error>> where 'c: 'e, { Box::pin(async move { self.wait_until_ready().await?; let (stmt_id, metadata) = self .get_or_prepare(sql.as_str(), &[], true, None, true) .await?; let nullable = self.get_nullable_for_columns(stmt_id, &metadata).await?; Ok(crate::describe::Describe { columns: metadata.columns.clone(), nullable, parameters: Some(Either::Left(metadata.parameters.clone())), }) }) } } ================================================ FILE: sqlx-postgres/src/connection/mod.rs ================================================ use std::collections::BTreeMap; use std::fmt::{self, Debug, Formatter}; use std::future::Future; use std::sync::Arc; use crate::HashMap; use crate::common::StatementCache; use crate::error::Error; use crate::ext::ustr::UStr; use crate::io::StatementId; use crate::message::{ BackendMessageFormat, Close, Query, ReadyForQuery, ReceivedMessage, Terminate, TransactionStatus, }; use crate::statement::PgStatementMetadata; use crate::transaction::Transaction; use crate::types::Oid; use crate::{PgConnectOptions, PgTypeInfo, Postgres}; pub(crate) use sqlx_core::connection::*; use sqlx_core::sql_str::SqlSafeStr; pub use self::stream::PgStream; #[cfg(feature = "offline")] mod describe; mod establish; mod executor; mod resolve; mod sasl; mod stream; mod tls; /// A connection to a PostgreSQL database. /// /// See [`PgConnectOptions`] for connection URL reference. pub struct PgConnection { pub(crate) inner: Box, } pub struct PgConnectionInner { // underlying TCP or UDS stream, // wrapped in a potentially TLS stream, // wrapped in a buffered stream pub(crate) stream: PgStream, // process id of this backend // used to send cancel requests #[allow(dead_code)] process_id: u32, // secret key of this backend // used to send cancel requests #[allow(dead_code)] secret_key: u32, // sequence of statement IDs for use in preparing statements // in PostgreSQL, the statement is prepared to a user-supplied identifier next_statement_id: StatementId, // cache statement by query string to the id and columns cache_statement: StatementCache<(StatementId, Arc)>, // cache user-defined types by id <-> info cache_type_info: HashMap, cache_type_oid: HashMap, cache_elem_type_to_array: HashMap, cache_table_to_column_names: HashMap, // number of ReadyForQuery messages that we are currently expecting pub(crate) pending_ready_for_query_count: usize, // current transaction status transaction_status: TransactionStatus, pub(crate) transaction_depth: usize, log_settings: LogSettings, } pub(crate) struct TableColumns { table_name: Arc, /// Attribute number -> name. columns: BTreeMap>, } impl PgConnection { /// the version number of the server in `libpq` format pub fn server_version_num(&self) -> Option { self.inner.stream.server_version_num } // will return when the connection is ready for another query pub(crate) async fn wait_until_ready(&mut self) -> Result<(), Error> { if !self.inner.stream.write_buffer_mut().is_empty() { self.inner.stream.flush().await?; } while self.inner.pending_ready_for_query_count > 0 { let message = self.inner.stream.recv().await?; if let BackendMessageFormat::ReadyForQuery = message.format { self.handle_ready_for_query(message)?; } } Ok(()) } async fn recv_ready_for_query(&mut self) -> Result<(), Error> { let r: ReadyForQuery = self.inner.stream.recv_expect().await?; self.inner.pending_ready_for_query_count -= 1; self.inner.transaction_status = r.transaction_status; Ok(()) } #[inline(always)] fn handle_ready_for_query(&mut self, message: ReceivedMessage) -> Result<(), Error> { self.inner.pending_ready_for_query_count = self .inner .pending_ready_for_query_count .checked_sub(1) .ok_or_else(|| err_protocol!("received more ReadyForQuery messages than expected"))?; self.inner.transaction_status = message.decode::()?.transaction_status; Ok(()) } /// Queue a simple query (not prepared) to execute the next time this connection is used. /// /// Used for rolling back transactions and releasing advisory locks. #[inline(always)] pub(crate) fn queue_simple_query(&mut self, query: &str) -> Result<(), Error> { self.inner.stream.write_msg(Query(query))?; self.inner.pending_ready_for_query_count += 1; Ok(()) } pub(crate) fn in_transaction(&self) -> bool { match self.inner.transaction_status { TransactionStatus::Transaction => true, TransactionStatus::Error | TransactionStatus::Idle => false, } } } impl Debug for PgConnection { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("PgConnection").finish() } } impl Connection for PgConnection { type Database = Postgres; type Options = PgConnectOptions; async fn close(mut self) -> Result<(), Error> { // The normal, graceful termination procedure is that the frontend sends a Terminate // message and immediately closes the connection. // On receipt of this message, the backend closes the // connection and terminates. self.inner.stream.send(Terminate).await?; self.inner.stream.shutdown().await?; Ok(()) } async fn close_hard(mut self) -> Result<(), Error> { self.inner.stream.shutdown().await?; Ok(()) } async fn ping(&mut self) -> Result<(), Error> { // Users were complaining about this showing up in query statistics on the server. // By sending a comment we avoid an error if the connection was in the middle of a rowset // self.execute("/* SQLx ping */").map_ok(|_| ()).boxed() // The simplest call-and-response that's possible. self.write_sync(); self.wait_until_ready().await } fn begin( &mut self, ) -> impl Future, Error>> + Send + '_ { Transaction::begin(self, None) } fn begin_with( &mut self, statement: impl SqlSafeStr, ) -> impl Future, Error>> + Send + '_ where Self: Sized, { Transaction::begin(self, Some(statement.into_sql_str())) } fn cached_statements_size(&self) -> usize { self.inner.cache_statement.len() } async fn clear_cached_statements(&mut self) -> Result<(), Error> { self.inner.cache_type_oid.clear(); let mut cleared = 0_usize; self.wait_until_ready().await?; while let Some((id, _)) = self.inner.cache_statement.remove_lru() { self.inner.stream.write_msg(Close::Statement(id))?; cleared += 1; } if cleared > 0 { self.write_sync(); self.inner.stream.flush().await?; self.wait_for_close_complete(cleared).await?; self.recv_ready_for_query().await?; } Ok(()) } fn shrink_buffers(&mut self) { self.inner.stream.shrink_buffers(); } #[doc(hidden)] fn flush(&mut self) -> impl Future> + Send + '_ { self.wait_until_ready() } #[doc(hidden)] fn should_flush(&self) -> bool { !self.inner.stream.write_buffer().is_empty() } } // Implement `AsMut` so that `PgConnection` can be wrapped in // a `PgAdvisoryLockGuard`. // // See: https://github.com/launchbadge/sqlx/issues/2520 impl AsMut for PgConnection { fn as_mut(&mut self) -> &mut PgConnection { self } } ================================================ FILE: sqlx-postgres/src/connection/resolve.rs ================================================ use crate::connection::TableColumns; use crate::error::Error; use crate::ext::ustr::UStr; use crate::message::{ParameterDescription, RowDescription}; use crate::query_as::query_as; use crate::query_scalar::query_scalar; use crate::type_info::{PgArrayOf, PgCustomType, PgType, PgTypeKind}; use crate::types::Oid; use crate::HashMap; use crate::{PgColumn, PgConnection, PgTypeInfo}; use sqlx_core::column::{ColumnOrigin, TableColumn}; use std::sync::Arc; /// Describes the type of the `pg_type.typtype` column /// /// See #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum TypType { Base, Composite, Domain, Enum, Pseudo, Range, } impl TryFrom for TypType { type Error = (); fn try_from(t: i8) -> Result { let t = u8::try_from(t).or(Err(()))?; let t = match t { b'b' => Self::Base, b'c' => Self::Composite, b'd' => Self::Domain, b'e' => Self::Enum, b'p' => Self::Pseudo, b'r' => Self::Range, _ => return Err(()), }; Ok(t) } } /// Describes the type of the `pg_type.typcategory` column /// /// See #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum TypCategory { Array, Boolean, Composite, DateTime, Enum, Geometric, Network, Numeric, Pseudo, Range, String, Timespan, User, BitString, Unknown, } impl TryFrom for TypCategory { type Error = (); fn try_from(c: i8) -> Result { let c = u8::try_from(c).or(Err(()))?; let c = match c { b'A' => Self::Array, b'B' => Self::Boolean, b'C' => Self::Composite, b'D' => Self::DateTime, b'E' => Self::Enum, b'G' => Self::Geometric, b'I' => Self::Network, b'N' => Self::Numeric, b'P' => Self::Pseudo, b'R' => Self::Range, b'S' => Self::String, b'T' => Self::Timespan, b'U' => Self::User, b'V' => Self::BitString, b'X' => Self::Unknown, _ => return Err(()), }; Ok(c) } } impl PgConnection { pub(super) async fn handle_row_description( &mut self, desc: Option, fetch_type_info: bool, fetch_column_description: bool, ) -> Result<(Vec, HashMap), Error> { let mut columns = Vec::new(); let mut column_names = HashMap::new(); let desc = if let Some(desc) = desc { desc } else { // no rows return Ok((columns, column_names)); }; columns.reserve(desc.fields.len()); column_names.reserve(desc.fields.len()); for (index, field) in desc.fields.into_iter().enumerate() { let name = UStr::from(field.name); let type_info = self .maybe_fetch_type_info_by_oid(field.data_type_id, fetch_type_info) .await?; let origin = if let (Some(relation_oid), Some(attribute_no)) = (field.relation_id, field.relation_attribute_no) { self.maybe_fetch_column_origin(relation_oid, attribute_no, fetch_column_description) .await? } else { ColumnOrigin::Expression }; let column = PgColumn { ordinal: index, name: name.clone(), type_info, relation_id: field.relation_id, relation_attribute_no: field.relation_attribute_no, origin, }; columns.push(column); column_names.insert(name, index); } Ok((columns, column_names)) } pub(super) async fn handle_parameter_description( &mut self, desc: ParameterDescription, ) -> Result, Error> { let mut params = Vec::with_capacity(desc.types.len()); for ty in desc.types { params.push(self.maybe_fetch_type_info_by_oid(ty, true).await?); } Ok(params) } async fn maybe_fetch_type_info_by_oid( &mut self, oid: Oid, should_fetch: bool, ) -> Result { // first we check if this is a built-in type // in the average application, the vast majority of checks should flow through this if let Some(info) = PgTypeInfo::try_from_oid(oid) { return Ok(info); } // next we check a local cache for user-defined type names <-> object id if let Some(info) = self.inner.cache_type_info.get(&oid) { return Ok(info.clone()); } // fallback to asking the database directly for a type name if should_fetch { // we're boxing this future here so we can use async recursion let info = Box::pin(async { self.fetch_type_by_oid(oid).await }).await?; // cache the type name <-> oid relationship in a paired hashmap // so we don't come down this road again self.inner.cache_type_info.insert(oid, info.clone()); self.inner .cache_type_oid .insert(info.0.name().to_string().into(), oid); Ok(info) } else { // we are not in a place that *can* run a query // this generally means we are in the middle of another query // this _should_ only happen for complex types sent through the TEXT protocol // we're open to ideas to correct this.. but it'd probably be more efficient to figure // out a way to "prime" the type cache for connections rather than make this // fallback work correctly for complex user-defined types for the TEXT protocol Ok(PgTypeInfo(PgType::DeclareWithOid(oid))) } } async fn maybe_fetch_column_origin( &mut self, relation_id: Oid, attribute_no: i16, should_fetch: bool, ) -> Result { if let Some(origin) = self .inner .cache_table_to_column_names .get(&relation_id) .and_then(|table_columns| { let column_name = table_columns.columns.get(&attribute_no).cloned()?; Some(ColumnOrigin::Table(TableColumn { table: table_columns.table_name.clone(), name: column_name, })) }) { return Ok(origin); } if !should_fetch { return Ok(ColumnOrigin::Unknown); } // Looking up the table name _may_ end up being redundant, // but the round-trip to the server is by far the most expensive part anyway. let Some((table_name, column_name)): Option<(String, String)> = query_as( // language=PostgreSQL "SELECT $1::oid::regclass::text, attname \ FROM pg_catalog.pg_attribute \ WHERE attrelid = $1 AND attnum = $2", ) .bind(relation_id) .bind(attribute_no) .fetch_optional(&mut *self) .await? else { // The column/table doesn't exist anymore for whatever reason. return Ok(ColumnOrigin::Unknown); }; let table_columns = self .inner .cache_table_to_column_names .entry(relation_id) .or_insert_with(|| TableColumns { table_name: table_name.into(), columns: Default::default(), }); let column_name = table_columns .columns .entry(attribute_no) .or_insert(column_name.into()); Ok(ColumnOrigin::Table(TableColumn { table: table_columns.table_name.clone(), name: Arc::clone(column_name), })) } async fn fetch_type_by_oid(&mut self, oid: Oid) -> Result { let (name, typ_type, category, relation_id, element, base_type): ( String, i8, i8, Oid, Oid, Oid, ) = query_as( // Converting the OID to `regtype` and then `text` will give us the name that // the type will need to be found at by search_path. "SELECT oid::regtype::text, \ typtype, \ typcategory, \ typrelid, \ typelem, \ typbasetype \ FROM pg_catalog.pg_type \ WHERE oid = $1", ) .bind(oid) .fetch_one(&mut *self) .await?; let typ_type = TypType::try_from(typ_type); let category = TypCategory::try_from(category); match (typ_type, category) { (Ok(TypType::Domain), _) => self.fetch_domain_by_oid(oid, base_type, name).await, (Ok(TypType::Base), Ok(TypCategory::Array)) => { Ok(PgTypeInfo(PgType::Custom(Arc::new(PgCustomType { kind: PgTypeKind::Array( self.maybe_fetch_type_info_by_oid(element, true).await?, ), name: name.into(), oid, })))) } (Ok(TypType::Pseudo), Ok(TypCategory::Pseudo)) => { Ok(PgTypeInfo(PgType::Custom(Arc::new(PgCustomType { kind: PgTypeKind::Pseudo, name: name.into(), oid, })))) } (Ok(TypType::Range), Ok(TypCategory::Range)) => { self.fetch_range_by_oid(oid, name).await } (Ok(TypType::Enum), Ok(TypCategory::Enum)) => self.fetch_enum_by_oid(oid, name).await, (Ok(TypType::Composite), Ok(TypCategory::Composite)) => { self.fetch_composite_by_oid(oid, relation_id, name).await } _ => Ok(PgTypeInfo(PgType::Custom(Arc::new(PgCustomType { kind: PgTypeKind::Simple, name: name.into(), oid, })))), } } async fn fetch_enum_by_oid(&mut self, oid: Oid, name: String) -> Result { let variants: Vec = query_scalar( r#" SELECT enumlabel FROM pg_catalog.pg_enum WHERE enumtypid = $1 ORDER BY enumsortorder "#, ) .bind(oid) .fetch_all(self) .await?; Ok(PgTypeInfo(PgType::Custom(Arc::new(PgCustomType { oid, name: name.into(), kind: PgTypeKind::Enum(Arc::from(variants)), })))) } async fn fetch_composite_by_oid( &mut self, oid: Oid, relation_id: Oid, name: String, ) -> Result { let raw_fields: Vec<(String, Oid)> = query_as( r#" SELECT attname, atttypid FROM pg_catalog.pg_attribute WHERE attrelid = $1 AND NOT attisdropped AND attnum > 0 ORDER BY attnum "#, ) .bind(relation_id) .fetch_all(&mut *self) .await?; let mut fields = Vec::new(); for (field_name, field_oid) in raw_fields.into_iter() { let field_type = self.maybe_fetch_type_info_by_oid(field_oid, true).await?; fields.push((field_name, field_type)); } Ok(PgTypeInfo(PgType::Custom(Arc::new(PgCustomType { oid, name: name.into(), kind: PgTypeKind::Composite(Arc::from(fields)), })))) } async fn fetch_domain_by_oid( &mut self, oid: Oid, base_type: Oid, name: String, ) -> Result { let base_type = self.maybe_fetch_type_info_by_oid(base_type, true).await?; Ok(PgTypeInfo(PgType::Custom(Arc::new(PgCustomType { oid, name: name.into(), kind: PgTypeKind::Domain(base_type), })))) } async fn fetch_range_by_oid(&mut self, oid: Oid, name: String) -> Result { let element_oid: Oid = query_scalar( r#" SELECT rngsubtype FROM pg_catalog.pg_range WHERE rngtypid = $1 "#, ) .bind(oid) .fetch_one(&mut *self) .await?; let element = self.maybe_fetch_type_info_by_oid(element_oid, true).await?; Ok(PgTypeInfo(PgType::Custom(Arc::new(PgCustomType { kind: PgTypeKind::Range(element), name: name.into(), oid, })))) } pub(crate) async fn resolve_type_id(&mut self, ty: &PgType) -> Result { if let Some(oid) = ty.try_oid() { return Ok(oid); } match ty { PgType::DeclareWithName(name) => self.fetch_type_id_by_name(name).await, PgType::DeclareArrayOf(array) => self.fetch_array_type_id(array).await, // `.try_oid()` should return `Some()` or it should be covered here _ => unreachable!("(bug) OID should be resolvable for type {ty:?}"), } } pub(crate) async fn fetch_type_id_by_name(&mut self, name: &str) -> Result { if let Some(oid) = self.inner.cache_type_oid.get(name) { return Ok(*oid); } // language=SQL let (oid,): (Oid,) = query_as("SELECT $1::regtype::oid") .bind(name) .fetch_optional(&mut *self) .await? .ok_or_else(|| Error::TypeNotFound { type_name: name.into(), })?; self.inner .cache_type_oid .insert(name.to_string().into(), oid); Ok(oid) } pub(crate) async fn fetch_array_type_id(&mut self, array: &PgArrayOf) -> Result { if let Some(oid) = self .inner .cache_type_oid .get(&array.elem_name) .and_then(|elem_oid| self.inner.cache_elem_type_to_array.get(elem_oid)) { return Ok(*oid); } // language=SQL let (elem_oid, array_oid): (Oid, Oid) = query_as("SELECT oid, typarray FROM pg_catalog.pg_type WHERE oid = $1::regtype::oid") .bind(&*array.elem_name) .fetch_optional(&mut *self) .await? .ok_or_else(|| Error::TypeNotFound { type_name: array.name.to_string(), })?; // Avoids copying `elem_name` until necessary self.inner .cache_type_oid .entry_ref(&array.elem_name) .insert(elem_oid); self.inner .cache_elem_type_to_array .insert(elem_oid, array_oid); Ok(array_oid) } } ================================================ FILE: sqlx-postgres/src/connection/sasl.rs ================================================ use crate::connection::stream::PgStream; use crate::error::Error; use crate::message::{Authentication, AuthenticationSasl, SaslInitialResponse, SaslResponse}; use crate::rt; use crate::PgConnectOptions; use hmac::{Hmac, Mac}; use rand::Rng; use sha2::{Digest, Sha256}; use stringprep::saslprep; use base64::prelude::{Engine as _, BASE64_STANDARD}; const GS2_HEADER: &str = "n,,"; const CHANNEL_ATTR: &str = "c"; const USERNAME_ATTR: &str = "n"; const CLIENT_PROOF_ATTR: &str = "p"; const NONCE_ATTR: &str = "r"; pub(crate) async fn authenticate( stream: &mut PgStream, options: &PgConnectOptions, data: AuthenticationSasl, ) -> Result<(), Error> { let mut has_sasl = false; let mut has_sasl_plus = false; let mut unknown = Vec::new(); for mechanism in data.mechanisms() { match mechanism { "SCRAM-SHA-256" => { has_sasl = true; } "SCRAM-SHA-256-PLUS" => { has_sasl_plus = true; } _ => { unknown.push(mechanism.to_owned()); } } } if !has_sasl_plus && !has_sasl { return Err(err_protocol!( "unsupported SASL authentication mechanisms: {}", unknown.join(", ") )); } // channel-binding = "c=" base64 let mut channel_binding = format!("{CHANNEL_ATTR}="); BASE64_STANDARD.encode_string(GS2_HEADER, &mut channel_binding); // "n=" saslname ;; Usernames are prepared using SASLprep. let username = format!("{}={}", USERNAME_ATTR, options.username); let username = match saslprep(&username) { Ok(v) => v, // TODO(danielakhterov): Remove panic when we have proper support for configuration errors Err(_) => panic!("Failed to saslprep username"), }; // nonce = "r=" c-nonce [s-nonce] ;; Second part provided by server. let nonce = gen_nonce(); // client-first-message-bare = [reserved-mext ","] username "," nonce ["," extensions] let client_first_message_bare = format!("{username},{nonce}"); let client_first_message = format!("{GS2_HEADER}{client_first_message_bare}"); stream .send(SaslInitialResponse { response: &client_first_message, plus: false, }) .await?; let cont = match stream.recv_expect().await? { Authentication::SaslContinue(data) => data, auth => { return Err(err_protocol!( "expected SASLContinue but received {:?}", auth )); } }; // SaltedPassword := Hi(Normalize(password), salt, i) let salted_password = hi( options.password.as_deref().unwrap_or_default(), &cont.salt, cont.iterations, ) .await?; // ClientKey := HMAC(SaltedPassword, "Client Key") let mut mac = Hmac::::new_from_slice(&salted_password).map_err(Error::protocol)?; mac.update(b"Client Key"); let client_key = mac.finalize().into_bytes(); // StoredKey := H(ClientKey) let stored_key = Sha256::digest(client_key); // client-final-message-without-proof let client_final_message_wo_proof = format!( "{channel_binding},r={nonce}", channel_binding = channel_binding, nonce = &cont.nonce ); // AuthMessage := client-first-message-bare + "," + server-first-message + "," + client-final-message-without-proof let auth_message = format!( "{client_first_message_bare},{server_first_message},{client_final_message_wo_proof}", client_first_message_bare = client_first_message_bare, server_first_message = cont.message, client_final_message_wo_proof = client_final_message_wo_proof ); // ClientSignature := HMAC(StoredKey, AuthMessage) let mut mac = Hmac::::new_from_slice(&stored_key).map_err(Error::protocol)?; mac.update(auth_message.as_bytes()); let client_signature = mac.finalize().into_bytes(); // ClientProof := ClientKey XOR ClientSignature let client_proof: Vec = client_key .iter() .zip(client_signature.iter()) .map(|(&a, &b)| a ^ b) .collect(); // ServerKey := HMAC(SaltedPassword, "Server Key") let mut mac = Hmac::::new_from_slice(&salted_password).map_err(Error::protocol)?; mac.update(b"Server Key"); let server_key = mac.finalize().into_bytes(); // ServerSignature := HMAC(ServerKey, AuthMessage) let mut mac = Hmac::::new_from_slice(&server_key).map_err(Error::protocol)?; mac.update(auth_message.as_bytes()); // client-final-message = client-final-message-without-proof "," proof let mut client_final_message = format!("{client_final_message_wo_proof},{CLIENT_PROOF_ATTR}="); BASE64_STANDARD.encode_string(client_proof, &mut client_final_message); stream.send(SaslResponse(&client_final_message)).await?; let data = match stream.recv_expect().await? { Authentication::SaslFinal(data) => data, auth => { return Err(err_protocol!("expected SASLFinal but received {:?}", auth)); } }; // authentication is only considered valid if this verification passes mac.verify_slice(&data.verifier).map_err(Error::protocol)?; Ok(()) } // nonce is a sequence of random printable bytes fn gen_nonce() -> String { let mut rng = rand::thread_rng(); let count = rng.gen_range(64..128); // printable = %x21-2B / %x2D-7E // ;; Printable ASCII except ",". // ;; Note that any "printable" is also // ;; a valid "value". let nonce: String = std::iter::repeat(()) .map(|()| { let mut c = rng.gen_range(0x21u8..0x7F); while c == 0x2C { c = rng.gen_range(0x21u8..0x7F); } c }) .take(count) .map(|c| c as char) .collect(); rng.gen_range(32..128); format!("{NONCE_ATTR}={nonce}") } // Hi(str, salt, i): async fn hi<'a>(s: &'a str, salt: &'a [u8], iter_count: u32) -> Result<[u8; 32], Error> { let mut mac = Hmac::::new_from_slice(s.as_bytes()).map_err(Error::protocol)?; mac.update(salt); mac.update(&1u32.to_be_bytes()); let mut u = mac.finalize_reset().into_bytes(); let mut hi = u; for i in 1..iter_count { mac.update(u.as_slice()); u = mac.finalize_reset().into_bytes(); hi = hi.iter().zip(u.iter()).map(|(&a, &b)| a ^ b).collect(); // For large iteration counts, this process can take a long time and block the event loop. // It was measured as taking ~50ms for 4096 iterations (the default) on a developer machine. // If we want to yield every 10-100us (as generally advised for tokio), then we can yield // every 5 iterations which should be every ~50us. if i % 5 == 0 { rt::yield_now().await; } } Ok(hi.into()) } ================================================ FILE: sqlx-postgres/src/connection/stream.rs ================================================ use std::collections::BTreeMap; use std::ops::{ControlFlow, Deref, DerefMut}; use std::str::FromStr; use futures_channel::mpsc::UnboundedSender; use futures_util::SinkExt; use log::Level; use sqlx_core::bytes::Buf; use crate::connection::tls::MaybeUpgradeTls; use crate::error::Error; use crate::message::{ BackendMessage, BackendMessageFormat, EncodeMessage, FrontendMessage, Notice, Notification, ParameterStatus, ReceivedMessage, }; use crate::net::{self, BufferedSocket, Socket}; use crate::{PgConnectOptions, PgDatabaseError, PgSeverity}; // the stream is a separate type from the connection to uphold the invariant where an instantiated // [PgConnection] is a **valid** connection to postgres // when a new connection is asked for, we work directly on the [PgStream] type until the // connection is fully established // in other words, `self` in any PgConnection method is a live connection to postgres that // is fully prepared to receive queries pub struct PgStream { // A trait object is okay here as the buffering amortizes the overhead of both the dynamic // function call as well as the syscall. inner: BufferedSocket>, // buffer of unreceived notification messages from `PUBLISH` // this is set when creating a PgListener and only written to if that listener is // re-used for query execution in-between receiving messages pub(crate) notifications: Option>, pub(crate) parameter_statuses: BTreeMap, pub(crate) server_version_num: Option, } impl PgStream { pub(super) async fn connect(options: &PgConnectOptions) -> Result { let socket_result = match options.fetch_socket() { Some(ref path) => net::connect_uds(path, MaybeUpgradeTls(options)).await?, None => net::connect_tcp(&options.host, options.port, MaybeUpgradeTls(options)).await?, }; let socket = socket_result?; Ok(Self { inner: BufferedSocket::new(socket), notifications: None, parameter_statuses: BTreeMap::default(), server_version_num: None, }) } #[inline(always)] pub(crate) fn write_msg(&mut self, message: impl FrontendMessage) -> Result<(), Error> { self.write(EncodeMessage(message)) } pub(crate) async fn send(&mut self, message: T) -> Result<(), Error> where T: FrontendMessage, { self.write_msg(message)?; self.flush().await?; Ok(()) } // Expect a specific type and format pub(crate) async fn recv_expect(&mut self) -> Result { self.recv().await?.decode() } pub(crate) async fn recv_unchecked(&mut self) -> Result { // NOTE: to not break everything, this should be cancel-safe; // DO NOT modify `buf` unless a full message has been read self.inner .try_read(|buf| { // all packets in postgres start with a 5-byte header // this header contains the message type and the total length of the message let Some(mut header) = buf.get(..5) else { return Ok(ControlFlow::Continue(5)); }; let format = BackendMessageFormat::try_from_u8(header.get_u8())?; let message_len = header.get_u32() as usize; let expected_len = message_len .checked_add(1) // this shouldn't really happen but is mostly a sanity check .ok_or_else(|| { err_protocol!("message_len + 1 overflows usize: {message_len}") })?; if buf.len() < expected_len { return Ok(ControlFlow::Continue(expected_len)); } // `buf` SHOULD NOT be modified ABOVE this line // pop off the format code since it's not counted in `message_len` buf.advance(1); // consume the message, including the length prefix let mut contents = buf.split_to(message_len).freeze(); // cut off the length prefix contents.advance(4); Ok(ControlFlow::Break(ReceivedMessage { format, contents })) }) .await } // Get the next message from the server // May wait for more data from the server pub(crate) async fn recv(&mut self) -> Result { loop { let message = self.recv_unchecked().await?; match message.format { BackendMessageFormat::ErrorResponse => { // An error returned from the database server. return Err(message.decode::()?.into()); } BackendMessageFormat::NotificationResponse => { if let Some(buffer) = &mut self.notifications { let notification: Notification = message.decode()?; let _ = buffer.send(notification).await; continue; } } BackendMessageFormat::ParameterStatus => { // informs the frontend about the current (initial) // setting of backend parameters let ParameterStatus { name, value } = message.decode()?; // TODO: handle `client_encoding`, `DateStyle` change match name.as_str() { "server_version" => { self.server_version_num = parse_server_version(&value); } _ => { self.parameter_statuses.insert(name, value); } } continue; } BackendMessageFormat::NoticeResponse => { // do we need this to be more configurable? // if you are reading this comment and think so, open an issue let notice: Notice = message.decode()?; let (log_level, tracing_level) = match notice.severity() { PgSeverity::Fatal | PgSeverity::Panic | PgSeverity::Error => { (Level::Error, tracing::Level::ERROR) } PgSeverity::Warning => (Level::Warn, tracing::Level::WARN), PgSeverity::Notice => (Level::Info, tracing::Level::INFO), PgSeverity::Debug => (Level::Debug, tracing::Level::DEBUG), PgSeverity::Info | PgSeverity::Log => (Level::Trace, tracing::Level::TRACE), }; let log_is_enabled = log::log_enabled!( target: "sqlx::postgres::notice", log_level ) || sqlx_core::private_tracing_dynamic_enabled!( target: "sqlx::postgres::notice", tracing_level ); if log_is_enabled { sqlx_core::private_tracing_dynamic_event!( target: "sqlx::postgres::notice", tracing_level, message = notice.message() ); } continue; } _ => {} } return Ok(message); } } } impl Deref for PgStream { type Target = BufferedSocket>; #[inline] fn deref(&self) -> &Self::Target { &self.inner } } impl DerefMut for PgStream { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } } // reference: // https://github.com/postgres/postgres/blob/6feebcb6b44631c3dc435e971bd80c2dd218a5ab/src/interfaces/libpq/fe-exec.c#L1030-L1065 fn parse_server_version(s: &str) -> Option { let mut parts = Vec::::with_capacity(3); let mut from = 0; let mut chs = s.char_indices().peekable(); while let Some((i, ch)) = chs.next() { match ch { '.' => { if let Ok(num) = u32::from_str(&s[from..i]) { parts.push(num); from = i + 1; } else { break; } } _ if ch.is_ascii_digit() => { if chs.peek().is_none() { if let Ok(num) = u32::from_str(&s[from..]) { parts.push(num); } break; } } _ => { if let Ok(num) = u32::from_str(&s[from..i]) { parts.push(num); } break; } }; } let version_num = match parts.as_slice() { [major, minor, rev] => (100 * major + minor) * 100 + rev, [major, minor] if *major >= 10 => 100 * 100 * major + minor, [major, minor] => (100 * major + minor) * 100, [major] => 100 * 100 * major, _ => return None, }; Some(version_num) } #[cfg(test)] mod tests { use super::parse_server_version; #[test] fn test_parse_server_version_num() { // old style assert_eq!(parse_server_version("9.6.1"), Some(90601)); // new style assert_eq!(parse_server_version("10.1"), Some(100001)); // old style without minor version assert_eq!(parse_server_version("9.6devel"), Some(90600)); // new style without minor version, e.g. */ assert_eq!(parse_server_version("10devel"), Some(100000)); assert_eq!(parse_server_version("13devel87"), Some(130000)); // unknown assert_eq!(parse_server_version("unknown"), None); } } ================================================ FILE: sqlx-postgres/src/connection/tls.rs ================================================ use crate::error::Error; use crate::net::tls::{self, TlsConfig}; use crate::net::{Socket, SocketIntoBox, WithSocket}; use crate::message::SslRequest; use crate::{PgConnectOptions, PgSslMode}; pub struct MaybeUpgradeTls<'a>(pub &'a PgConnectOptions); impl WithSocket for MaybeUpgradeTls<'_> { type Output = crate::Result>; async fn with_socket(self, socket: S) -> Self::Output { maybe_upgrade(socket, self.0).await } } async fn maybe_upgrade( mut socket: S, options: &PgConnectOptions, ) -> Result, Error> { // https://www.postgresql.org/docs/12/libpq-ssl.html#LIBPQ-SSL-SSLMODE-STATEMENTS match options.ssl_mode { // FIXME: Implement ALLOW PgSslMode::Allow | PgSslMode::Disable => return Ok(Box::new(socket)), PgSslMode::Prefer => { if !tls::available() { return Ok(Box::new(socket)); } // try upgrade, but its okay if we fail if !request_upgrade(&mut socket, options).await? { return Ok(Box::new(socket)); } } PgSslMode::Require | PgSslMode::VerifyFull | PgSslMode::VerifyCa => { tls::error_if_unavailable()?; if !request_upgrade(&mut socket, options).await? { // upgrade failed, die return Err(Error::Tls("server does not support TLS".into())); } } } let accept_invalid_certs = !matches!( options.ssl_mode, PgSslMode::VerifyCa | PgSslMode::VerifyFull ); let accept_invalid_hostnames = !matches!(options.ssl_mode, PgSslMode::VerifyFull); let config = TlsConfig { accept_invalid_certs, accept_invalid_hostnames, hostname: &options.host, root_cert_path: options.ssl_root_cert.as_ref(), client_cert_path: options.ssl_client_cert.as_ref(), client_key_path: options.ssl_client_key.as_ref(), }; tls::handshake(socket, config, SocketIntoBox).await } async fn request_upgrade( socket: &mut impl Socket, _options: &PgConnectOptions, ) -> Result { // https://www.postgresql.org/docs/current/protocol-flow.html#id-1.10.5.7.11 // To initiate an SSL-encrypted connection, the frontend initially sends an // SSLRequest message rather than a StartupMessage socket.write(SslRequest::BYTES).await?; // The server then responds with a single byte containing S or N, indicating that // it is willing or unwilling to perform SSL, respectively. let mut response = [0u8]; socket.read(&mut &mut response[..]).await?; match response[0] { b'S' => { // The server is ready and willing to accept an SSL connection Ok(true) } b'N' => { // The server is _unwilling_ to perform SSL Ok(false) } other => Err(err_protocol!( "unexpected response from SSLRequest: 0x{:02x}", other )), } } ================================================ FILE: sqlx-postgres/src/copy.rs ================================================ use std::borrow::Cow; use std::future::Future; use std::ops::{Deref, DerefMut}; use futures_core::stream::BoxStream; use sqlx_core::bytes::{BufMut, Bytes}; use crate::connection::PgConnection; use crate::error::{Error, Result}; use crate::ext::async_stream::TryAsyncStream; use crate::io::AsyncRead; use crate::message::{ BackendMessageFormat, CommandComplete, CopyData, CopyDone, CopyFail, CopyInResponse, CopyOutResponse, CopyResponseData, Query, ReadyForQuery, }; use crate::pool::{Pool, PoolConnection}; use crate::Postgres; impl PgConnection { /// Issue a `COPY FROM STDIN` statement and transition the connection to streaming data /// to Postgres. This is a more efficient way to import data into Postgres as compared to /// `INSERT` but requires one of a few specific data formats (text/CSV/binary). /// /// If `statement` is anything other than a `COPY ... FROM STDIN ...` command, an error is /// returned. /// /// Command examples and accepted formats for `COPY` data are shown here: /// /// /// ### Note /// [PgCopyIn::finish] or [PgCopyIn::abort] *must* be called when finished or the connection /// will return an error the next time it is used. pub async fn copy_in_raw(&mut self, statement: &str) -> Result> { PgCopyIn::begin(self, statement).await } /// Issue a `COPY TO STDOUT` statement and transition the connection to streaming data /// from Postgres. This is a more efficient way to export data from Postgres but /// arrives in chunks of one of a few data formats (text/CSV/binary). /// /// If `statement` is anything other than a `COPY ... TO STDOUT ...` command, /// an error is returned. /// /// Note that once this process has begun, unless you read the stream to completion, /// it can only be canceled in two ways: /// /// 1. by closing the connection, or: /// 2. by using another connection to kill the server process that is sending the data as shown /// [in this StackOverflow answer](https://stackoverflow.com/a/35319598). /// /// If you don't read the stream to completion, the next time the connection is used it will /// need to read and discard all the remaining queued data, which could take some time. /// /// Command examples and accepted formats for `COPY` data are shown here: /// #[allow(clippy::needless_lifetimes)] pub async fn copy_out_raw<'c>( &'c mut self, statement: &str, ) -> Result>> { pg_begin_copy_out(self, statement).await } } /// Implements methods for directly executing `COPY FROM/TO STDOUT` on a [`PgPool`][crate::PgPool]. /// /// This is a replacement for the inherent methods on `PgPool` which could not exist /// once the Postgres driver was moved out into its own crate. pub trait PgPoolCopyExt { /// Issue a `COPY FROM STDIN` statement and begin streaming data to Postgres. /// This is a more efficient way to import data into Postgres as compared to /// `INSERT` but requires one of a few specific data formats (text/CSV/binary). /// /// A single connection will be checked out for the duration. /// /// If `statement` is anything other than a `COPY ... FROM STDIN ...` command, an error is /// returned. /// /// Command examples and accepted formats for `COPY` data are shown here: /// /// /// ### Note /// [PgCopyIn::finish] or [PgCopyIn::abort] *must* be called when finished or the connection /// will return an error the next time it is used. fn copy_in_raw<'a>( &'a self, statement: &'a str, ) -> impl Future>>> + Send + 'a; /// Issue a `COPY TO STDOUT` statement and begin streaming data /// from Postgres. This is a more efficient way to export data from Postgres but /// arrives in chunks of one of a few data formats (text/CSV/binary). /// /// If `statement` is anything other than a `COPY ... TO STDOUT ...` command, /// an error is returned. /// /// Note that once this process has begun, unless you read the stream to completion, /// it can only be canceled in two ways: /// /// 1. by closing the connection, or: /// 2. by using another connection to kill the server process that is sending the data as shown /// [in this StackOverflow answer](https://stackoverflow.com/a/35319598). /// /// If you don't read the stream to completion, the next time the connection is used it will /// need to read and discard all the remaining queued data, which could take some time. /// /// Command examples and accepted formats for `COPY` data are shown here: /// fn copy_out_raw<'a>( &'a self, statement: &'a str, ) -> impl Future>>> + Send + 'a; } impl PgPoolCopyExt for Pool { async fn copy_in_raw<'a>( &'a self, statement: &'a str, ) -> Result>> { PgCopyIn::begin(self.acquire().await?, statement).await } async fn copy_out_raw<'a>( &'a self, statement: &'a str, ) -> Result>> { pg_begin_copy_out(self.acquire().await?, statement).await } } // (1 GiB - 1) - 1 - length prefix (4 bytes) pub const PG_COPY_MAX_DATA_LEN: usize = 0x3fffffff - 1 - 4; /// A connection in streaming `COPY FROM STDIN` mode. /// /// Created by [PgConnection::copy_in_raw] or [Pool::copy_out_raw]. /// /// ### Note /// [PgCopyIn::finish] or [PgCopyIn::abort] *must* be called when finished or the connection /// will return an error the next time it is used. #[must_use = "connection will error on next use if `.finish()` or `.abort()` is not called"] pub struct PgCopyIn> { conn: Option, response: CopyResponseData, } impl> PgCopyIn { async fn begin(mut conn: C, statement: &str) -> Result { conn.wait_until_ready().await?; conn.inner.stream.send(Query(statement)).await?; let response = match conn.inner.stream.recv_expect::().await { Ok(res) => res.0, Err(e) => { conn.inner.stream.recv().await?; return Err(e); } }; Ok(PgCopyIn { conn: Some(conn), response, }) } /// Returns `true` if Postgres is expecting data in text or CSV format. pub fn is_textual(&self) -> bool { self.response.format == 0 } /// Returns the number of columns expected in the input. pub fn num_columns(&self) -> usize { assert_eq!( self.response.num_columns.unsigned_abs() as usize, self.response.format_codes.len(), "num_columns does not match format_codes.len()" ); self.response.format_codes.len() } /// Check if a column is expecting data in text format (`true`) or binary format (`false`). /// /// ### Panics /// If `column` is out of range according to [`.num_columns()`][Self::num_columns]. pub fn column_is_textual(&self, column: usize) -> bool { self.response.format_codes[column] == 0 } /// Send a chunk of `COPY` data. /// /// The data is sent in chunks if it exceeds the maximum length of a `CopyData` message (1 GiB - 6 /// bytes) and may be partially sent if this call is cancelled. /// /// If you're copying data from an `AsyncRead`, maybe consider [Self::read_from] instead. pub async fn send(&mut self, data: impl Deref) -> Result<&mut Self> { for chunk in data.deref().chunks(PG_COPY_MAX_DATA_LEN) { self.conn .as_deref_mut() .expect("send_data: conn taken") .inner .stream .send(CopyData(chunk)) .await?; } Ok(self) } /// Copy data directly from `source` to the database without requiring an intermediate buffer. /// /// `source` will be read to the end. /// /// ### Note: Completion Step Required /// You must still call either [Self::finish] or [Self::abort] to complete the process. /// /// ### Note: Runtime Features /// This method uses the `AsyncRead` trait which is re-exported from either Tokio or `async-std` /// depending on which runtime feature is used. /// /// The runtime features _used_ to be mutually exclusive, but are no longer. /// If both `runtime-async-std` and `runtime-tokio` features are enabled, the Tokio version /// takes precedent. pub async fn read_from(&mut self, mut source: impl AsyncRead + Unpin) -> Result<&mut Self> { let conn: &mut PgConnection = self.conn.as_deref_mut().expect("copy_from: conn taken"); loop { let buf = conn.inner.stream.write_buffer_mut(); // Write the CopyData format code and reserve space for the length. // This may end up sending an empty `CopyData` packet if, after this point, // we get canceled or read 0 bytes, but that should be fine. buf.put_slice(b"d\0\0\0\x04"); let read = buf.read_from(&mut source).await?; if read == 0 { break; } // Write the length let read32 = i32::try_from(read) .map_err(|_| err_protocol!("number of bytes read exceeds 2^31 - 1: {}", read))?; (&mut buf.get_mut()[1..]).put_i32(read32 + 4); conn.inner.stream.flush().await?; } Ok(self) } /// Signal that the `COPY` process should be aborted and any data received should be discarded. /// /// The given message can be used for indicating the reason for the abort in the database logs. /// /// The server is expected to respond with an error, so only _unexpected_ errors are returned. pub async fn abort(mut self, msg: impl Into) -> Result<()> { let mut conn = self .conn .take() .expect("PgCopyIn::fail_with: conn taken illegally"); conn.inner.stream.send(CopyFail::new(msg)).await?; match conn.inner.stream.recv().await { Ok(msg) => Err(err_protocol!( "fail_with: expected ErrorResponse, got: {:?}", msg.format )), Err(Error::Database(e)) => { match e.code() { Some(Cow::Borrowed("57014")) => { // postgres abort received error code conn.inner.stream.recv_expect::().await?; Ok(()) } _ => Err(Error::Database(e)), } } Err(e) => Err(e), } } /// Signal that the `COPY` process is complete. /// /// The number of rows affected is returned. pub async fn finish(mut self) -> Result { let mut conn = self .conn .take() .expect("CopyWriter::finish: conn taken illegally"); conn.inner.stream.send(CopyDone).await?; let cc: CommandComplete = match conn.inner.stream.recv_expect().await { Ok(cc) => cc, Err(e) => { conn.inner.stream.recv().await?; return Err(e); } }; conn.inner.stream.recv_expect::().await?; Ok(cc.rows_affected()) } } impl> Drop for PgCopyIn { fn drop(&mut self) { if let Some(mut conn) = self.conn.take() { conn.inner .stream .write_msg(CopyFail::new( "PgCopyIn dropped without calling finish() or fail()", )) .expect("BUG: PgCopyIn abort message should not be too large"); } } } async fn pg_begin_copy_out<'c, C: DerefMut + Send + 'c>( mut conn: C, statement: &str, ) -> Result>> { conn.wait_until_ready().await?; conn.inner.stream.send(Query(statement)).await?; let _: CopyOutResponse = conn.inner.stream.recv_expect().await?; let stream: TryAsyncStream<'c, Bytes> = try_stream! { loop { match conn.inner.stream.recv().await { Err(e) => { conn.inner.stream.recv_expect::().await?; return Err(e); }, Ok(msg) => match msg.format { BackendMessageFormat::CopyData => r#yield!(msg.decode::>()?.0), BackendMessageFormat::CopyDone => { let _ = msg.decode::()?; conn.inner.stream.recv_expect::().await?; conn.inner.stream.recv_expect::().await?; return Ok(()) }, _ => return Err(err_protocol!("unexpected message format during copy out: {:?}", msg.format)) } } } }; Ok(Box::pin(stream)) } ================================================ FILE: sqlx-postgres/src/database.rs ================================================ use crate::arguments::PgArgumentBuffer; use crate::value::{PgValue, PgValueRef}; use crate::{ PgArguments, PgColumn, PgConnection, PgQueryResult, PgRow, PgStatement, PgTransactionManager, PgTypeInfo, }; pub(crate) use sqlx_core::database::{Database, HasStatementCache}; /// PostgreSQL database driver. #[derive(Debug)] pub struct Postgres; impl Database for Postgres { type Connection = PgConnection; type TransactionManager = PgTransactionManager; type Row = PgRow; type QueryResult = PgQueryResult; type Column = PgColumn; type TypeInfo = PgTypeInfo; type Value = PgValue; type ValueRef<'r> = PgValueRef<'r>; type Arguments = PgArguments; type ArgumentBuffer = PgArgumentBuffer; type Statement = PgStatement; const NAME: &'static str = "PostgreSQL"; const URL_SCHEMES: &'static [&'static str] = &["postgres", "postgresql"]; } impl HasStatementCache for Postgres {} ================================================ FILE: sqlx-postgres/src/error.rs ================================================ use std::error::Error as StdError; use std::fmt::{self, Debug, Display, Formatter}; use atoi::atoi; use smallvec::alloc::borrow::Cow; use sqlx_core::bytes::Bytes; pub(crate) use sqlx_core::error::*; use crate::message::{BackendMessage, BackendMessageFormat, Notice, PgSeverity}; /// An error returned from the PostgreSQL database. pub struct PgDatabaseError(pub(crate) Notice); // Error message fields are documented: // https://www.postgresql.org/docs/current/protocol-error-fields.html impl PgDatabaseError { #[inline] pub fn severity(&self) -> PgSeverity { self.0.severity() } /// The [SQLSTATE](https://www.postgresql.org/docs/current/errcodes-appendix.html) code for /// this error. #[inline] pub fn code(&self) -> &str { self.0.code() } /// The primary human-readable error message. This should be accurate but /// terse (typically one line). #[inline] pub fn message(&self) -> &str { self.0.message() } /// An optional secondary error message carrying more detail about the problem. /// Might run to multiple lines. #[inline] pub fn detail(&self) -> Option<&str> { self.0.get(b'D') } /// An optional suggestion what to do about the problem. This is intended to differ from /// `detail` in that it offers advice (potentially inappropriate) rather than hard facts. /// Might run to multiple lines. #[inline] pub fn hint(&self) -> Option<&str> { self.0.get(b'H') } /// Indicates an error cursor position as an index into the original query string; or, /// a position into an internally generated query. #[inline] pub fn position(&self) -> Option> { self.0 .get_raw(b'P') .and_then(atoi) .map(PgErrorPosition::Original) .or_else(|| { let position = self.0.get_raw(b'p').and_then(atoi)?; let query = self.0.get(b'q')?; Some(PgErrorPosition::Internal { position, query }) }) } /// An indication of the context in which the error occurred. Presently this includes a call /// stack traceback of active procedural language functions and internally-generated queries. /// The trace is one entry per line, most recent first. pub fn r#where(&self) -> Option<&str> { self.0.get(b'W') } /// If this error is with a specific database object, the /// name of the schema containing that object, if any. pub fn schema(&self) -> Option<&str> { self.0.get(b's') } /// If this error is with a specific table, the name of the table. pub fn table(&self) -> Option<&str> { self.0.get(b't') } /// If the error is with a specific table column, the name of the column. pub fn column(&self) -> Option<&str> { self.0.get(b'c') } /// If the error is with a specific data type, the name of the data type. pub fn data_type(&self) -> Option<&str> { self.0.get(b'd') } /// If the error is with a specific constraint, the name of the constraint. /// For this purpose, indexes are constraints, even if they weren't created /// with constraint syntax. pub fn constraint(&self) -> Option<&str> { self.0.get(b'n') } /// The file name of the source-code location where this error was reported. pub fn file(&self) -> Option<&str> { self.0.get(b'F') } /// The line number of the source-code location where this error was reported. pub fn line(&self) -> Option { self.0.get_raw(b'L').and_then(atoi) } /// The name of the source-code routine reporting this error. pub fn routine(&self) -> Option<&str> { self.0.get(b'R') } } #[derive(Debug, Eq, PartialEq)] pub enum PgErrorPosition<'a> { /// A position (in characters) into the original query. Original(usize), /// A position into the internally-generated query. Internal { /// The position in characters. position: usize, /// The text of a failed internally-generated command. This could be, for example, /// the SQL query issued by a PL/pgSQL function. query: &'a str, }, } impl Debug for PgDatabaseError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("PgDatabaseError") .field("severity", &self.severity()) .field("code", &self.code()) .field("message", &self.message()) .field("detail", &self.detail()) .field("hint", &self.hint()) .field("position", &self.position()) .field("where", &self.r#where()) .field("schema", &self.schema()) .field("table", &self.table()) .field("column", &self.column()) .field("data_type", &self.data_type()) .field("constraint", &self.constraint()) .field("file", &self.file()) .field("line", &self.line()) .field("routine", &self.routine()) .finish() } } impl Display for PgDatabaseError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str(self.message()) } } impl StdError for PgDatabaseError {} impl DatabaseError for PgDatabaseError { fn message(&self) -> &str { self.message() } fn code(&self) -> Option> { Some(Cow::Borrowed(self.code())) } #[doc(hidden)] fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static) { self } #[doc(hidden)] fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) { self } #[doc(hidden)] fn into_error(self: Box) -> BoxDynError { self } fn is_transient_in_connect_phase(&self) -> bool { // https://www.postgresql.org/docs/current/errcodes-appendix.html [ // too_many_connections // This may be returned if we just un-gracefully closed a connection, // give the database a chance to notice it and clean it up. "53300", // cannot_connect_now // Returned if the database is still starting up. "57P03", ] .contains(&self.code()) } fn constraint(&self) -> Option<&str> { self.constraint() } fn table(&self) -> Option<&str> { self.table() } fn kind(&self) -> ErrorKind { match self.code() { error_codes::UNIQUE_VIOLATION => ErrorKind::UniqueViolation, error_codes::FOREIGN_KEY_VIOLATION => ErrorKind::ForeignKeyViolation, error_codes::NOT_NULL_VIOLATION => ErrorKind::NotNullViolation, error_codes::CHECK_VIOLATION => ErrorKind::CheckViolation, error_codes::EXCLUSION_VIOLATION => ErrorKind::ExclusionViolation, _ => ErrorKind::Other, } } } // ErrorResponse is the same structure as NoticeResponse but a different format code. impl BackendMessage for PgDatabaseError { const FORMAT: BackendMessageFormat = BackendMessageFormat::ErrorResponse; #[inline(always)] fn decode_body(buf: Bytes) -> std::result::Result { Ok(Self(Notice::decode_body(buf)?)) } } /// For reference: pub(crate) mod error_codes { /// Caused when a unique or primary key is violated. pub const UNIQUE_VIOLATION: &str = "23505"; /// Caused when a foreign key is violated. pub const FOREIGN_KEY_VIOLATION: &str = "23503"; /// Caused when a column marked as NOT NULL received a null value. pub const NOT_NULL_VIOLATION: &str = "23502"; /// Caused when a check constraint is violated. pub const CHECK_VIOLATION: &str = "23514"; /// Caused when a exclude constraint is violated. pub const EXCLUSION_VIOLATION: &str = "23P01"; } ================================================ FILE: sqlx-postgres/src/io/buf_mut.rs ================================================ use crate::io::{PortalId, StatementId}; pub trait PgBufMutExt { fn put_length_prefixed(&mut self, f: F) -> Result<(), crate::Error> where F: FnOnce(&mut Vec) -> Result<(), crate::Error>; fn put_statement_name(&mut self, id: StatementId); fn put_portal_name(&mut self, id: PortalId); } impl PgBufMutExt for Vec { // writes a length-prefixed message, this is used when encoding nearly all messages as postgres // wants us to send the length of the often-variable-sized messages up front fn put_length_prefixed(&mut self, write_contents: F) -> Result<(), crate::Error> where F: FnOnce(&mut Vec) -> Result<(), crate::Error>, { // reserve space to write the prefixed length let offset = self.len(); self.extend(&[0; 4]); // write the main body of the message let write_result = write_contents(self); let size_result = write_result.and_then(|_| { let size = self.len() - offset; i32::try_from(size) .map_err(|_| err_protocol!("message size out of range for protocol: {size}")) }); match size_result { Ok(size) => { // now calculate the size of what we wrote and set the length value self[offset..(offset + 4)].copy_from_slice(&size.to_be_bytes()); Ok(()) } Err(e) => { // Put the buffer back to where it was. self.truncate(offset); Err(e) } } } // writes a statement name by ID #[inline] fn put_statement_name(&mut self, id: StatementId) { id.put_name_with_nul(self); } // writes a portal name by ID #[inline] fn put_portal_name(&mut self, id: PortalId) { id.put_name_with_nul(self); } } ================================================ FILE: sqlx-postgres/src/io/mod.rs ================================================ mod buf_mut; pub use buf_mut::PgBufMutExt; use std::fmt; use std::fmt::{Display, Formatter}; use std::num::{NonZeroU32, Saturating}; pub(crate) use sqlx_core::io::*; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub(crate) struct StatementId(IdInner); #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub(crate) struct PortalId(IdInner); #[derive(Debug, Copy, Clone, PartialEq, Eq)] struct IdInner(Option); #[allow(unused)] pub(crate) struct DisplayId { prefix: &'static str, id: NonZeroU32, } impl StatementId { #[allow(dead_code)] pub const UNNAMED: Self = Self(IdInner::UNNAMED); pub const NAMED_START: Self = Self(IdInner::NAMED_START); #[cfg(test)] pub const TEST_VAL: Self = Self(IdInner::TEST_VAL); const NAME_PREFIX: &'static str = "sqlx_s_"; pub fn next(&self) -> Self { Self(self.0.next()) } pub fn name_len(&self) -> Saturating { self.0.name_len(Self::NAME_PREFIX) } /// Get a type to format this statement ID with [`Display`]. /// /// Returns `None` if this is the unnamed statement. #[allow(unused)] #[inline(always)] pub fn display(&self) -> Option { self.0.display(Self::NAME_PREFIX) } pub fn put_name_with_nul(&self, buf: &mut Vec) { self.0.put_name_with_nul(Self::NAME_PREFIX, buf) } } impl Display for DisplayId { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}{}", self.prefix, self.id) } } #[allow(dead_code)] impl PortalId { // None selects the unnamed portal pub const UNNAMED: Self = PortalId(IdInner::UNNAMED); pub const NAMED_START: Self = PortalId(IdInner::NAMED_START); #[cfg(test)] pub const TEST_VAL: Self = Self(IdInner::TEST_VAL); const NAME_PREFIX: &'static str = "sqlx_p_"; /// If ID represents a named portal, return the next ID, wrapping on overflow. /// /// If this ID represents the unnamed portal, return the same. pub fn next(&self) -> Self { Self(self.0.next()) } /// Calculate the number of bytes that will be written by [`Self::put_name_with_nul()`]. pub fn name_len(&self) -> Saturating { self.0.name_len(Self::NAME_PREFIX) } pub fn put_name_with_nul(&self, buf: &mut Vec) { self.0.put_name_with_nul(Self::NAME_PREFIX, buf) } } impl IdInner { const UNNAMED: Self = Self(None); const NAMED_START: Self = Self(Some(NonZeroU32::MIN)); #[cfg(test)] pub const TEST_VAL: Self = Self(NonZeroU32::new(1234567890)); #[inline(always)] fn next(&self) -> Self { Self( self.0 .map(|id| id.checked_add(1).unwrap_or(NonZeroU32::MIN)), ) } #[allow(unused)] #[inline(always)] fn display(&self, prefix: &'static str) -> Option { self.0.map(|id| DisplayId { prefix, id }) } #[inline(always)] fn name_len(&self, name_prefix: &str) -> Saturating { let mut len = Saturating(0); if let Some(id) = self.0 { len += name_prefix.len(); // estimate the length of the ID in decimal // `.ilog10()` can't panic since the value is never zero len += id.get().ilog10() as usize; // add one to compensate for `ilog10()` rounding down. len += 1; } // count the NUL terminator len += 1; len } #[inline(always)] fn put_name_with_nul(&self, name_prefix: &str, buf: &mut Vec) { if let Some(id) = self.0 { buf.extend_from_slice(name_prefix.as_bytes()); buf.extend_from_slice(itoa::Buffer::new().format(id.get()).as_bytes()); } buf.push(0); } } #[test] fn statement_id_display_matches_encoding() { const EXPECTED_STR: &str = "sqlx_s_1234567890"; const EXPECTED_BYTES: &[u8] = b"sqlx_s_1234567890\0"; let mut bytes = Vec::new(); StatementId::TEST_VAL.put_name_with_nul(&mut bytes); assert_eq!(bytes, EXPECTED_BYTES); let str = StatementId::TEST_VAL.display().unwrap().to_string(); assert_eq!(str, EXPECTED_STR); } ================================================ FILE: sqlx-postgres/src/lib.rs ================================================ //! **PostgreSQL** database driver. #[macro_use] extern crate sqlx_core; use crate::executor::Executor; mod advisory_lock; mod arguments; mod bind_iter; mod column; mod connection; mod copy; mod database; mod error; mod io; mod listener; mod message; mod options; mod query_result; mod row; mod statement; mod transaction; mod type_checking; mod type_info; pub mod types; mod value; #[cfg(feature = "any")] // We are hiding the any module with its AnyConnectionBackend trait // so that IDEs don't show it in the autocompletion list // and end users don't accidentally use it. This can result in // nested transactions not behaving as expected. // For more information, see https://github.com/launchbadge/sqlx/pull/3254#issuecomment-2144043823 #[doc(hidden)] pub mod any; #[doc(hidden)] pub use copy::PG_COPY_MAX_DATA_LEN; #[cfg(feature = "migrate")] mod migrate; #[cfg(feature = "migrate")] mod testing; pub(crate) use sqlx_core::driver_prelude::*; pub use advisory_lock::{PgAdvisoryLock, PgAdvisoryLockGuard, PgAdvisoryLockKey}; pub use arguments::{PgArgumentBuffer, PgArguments}; pub use bind_iter::PgBindIterExt; pub use column::PgColumn; pub use connection::PgConnection; pub use copy::{PgCopyIn, PgPoolCopyExt}; pub use database::Postgres; pub use error::{PgDatabaseError, PgErrorPosition}; pub use listener::{PgListener, PgNotification}; pub use message::PgSeverity; pub use options::{PgConnectOptions, PgSslMode}; pub use query_result::PgQueryResult; pub use row::PgRow; pub use statement::PgStatement; pub use transaction::PgTransactionManager; pub use type_info::{PgTypeInfo, PgTypeKind}; pub use types::PgHasArrayType; pub use value::{PgValue, PgValueFormat, PgValueRef}; /// An alias for [`Pool`][crate::pool::Pool], specialized for Postgres. pub type PgPool = crate::pool::Pool; /// An alias for [`PoolOptions`][crate::pool::PoolOptions], specialized for Postgres. pub type PgPoolOptions = crate::pool::PoolOptions; /// An alias for [`Executor<'_, Database = Postgres>`][Executor]. pub trait PgExecutor<'c>: Executor<'c, Database = Postgres> {} impl<'c, T: Executor<'c, Database = Postgres>> PgExecutor<'c> for T {} /// An alias for [`Transaction`][crate::transaction::Transaction], specialized for Postgres. pub type PgTransaction<'c> = crate::transaction::Transaction<'c, Postgres>; impl_into_arguments_for_arguments!(PgArguments); impl_acquire!(Postgres, PgConnection); impl_column_index_for_row!(PgRow); impl_column_index_for_statement!(PgStatement); impl_encode_for_option!(Postgres); ================================================ FILE: sqlx-postgres/src/listener.rs ================================================ use std::fmt::{self, Debug}; use std::io; use std::str::from_utf8; use futures_channel::mpsc; use futures_core::future::BoxFuture; use futures_core::stream::{BoxStream, Stream}; use futures_util::{FutureExt, StreamExt, TryFutureExt, TryStreamExt}; use sqlx_core::acquire::Acquire; use sqlx_core::sql_str::{AssertSqlSafe, SqlStr}; use sqlx_core::transaction::Transaction; use sqlx_core::Either; use tracing::Instrument; use crate::error::Error; use crate::executor::{Execute, Executor}; use crate::message::{BackendMessageFormat, Notification}; use crate::pool::PoolOptions; use crate::pool::{Pool, PoolConnection}; use crate::{PgConnection, PgQueryResult, PgRow, PgStatement, PgTypeInfo, Postgres}; /// A stream of asynchronous notifications from Postgres. /// /// This listener will auto-reconnect. If the active /// connection being used ever dies, this listener will detect that event, create a /// new connection, will re-subscribe to all of the originally specified channels, and will resume /// operations as normal. pub struct PgListener { pool: Pool, connection: Option>, buffer_rx: mpsc::UnboundedReceiver, buffer_tx: Option>, channels: Vec, ignore_close_event: bool, eager_reconnect: bool, } /// An asynchronous notification from Postgres. pub struct PgNotification(Notification); impl PgListener { pub async fn connect(url: &str) -> Result { // Create a pool of 1 without timeouts (as they don't apply here) // We only use the pool to handle re-connections let pool = PoolOptions::::new() .max_connections(1) .max_lifetime(None) .idle_timeout(None) .connect(url) .await?; let mut this = Self::connect_with(&pool).await?; // We don't need to handle close events this.ignore_close_event = true; Ok(this) } pub async fn connect_with(pool: &Pool) -> Result { // Pull out an initial connection let mut connection = pool.acquire().await?; // Setup a notification buffer let (sender, receiver) = mpsc::unbounded(); connection.inner.stream.notifications = Some(sender); Ok(Self { pool: pool.clone(), connection: Some(connection), buffer_rx: receiver, buffer_tx: None, channels: Vec::new(), ignore_close_event: false, eager_reconnect: true, }) } /// Set whether or not to ignore [`Pool::close_event()`]. Defaults to `false`. /// /// By default, when [`Pool::close()`] is called on the pool this listener is using /// while [`Self::recv()`] or [`Self::try_recv()`] are waiting for a message, the wait is /// cancelled and `Err(PoolClosed)` is returned. /// /// This is because `Pool::close()` will wait until _all_ connections are returned and closed, /// including the one being used by this listener. /// /// Otherwise, `pool.close().await` would have to wait until `PgListener` encountered a /// need to acquire a new connection (timeout, error, etc.) and dropped the one it was /// currently holding, at which point `.recv()` or `.try_recv()` would return `Err(PoolClosed)` /// on the attempt to acquire a new connection anyway. /// /// However, if you want `PgListener` to ignore the close event and continue waiting for a /// message as long as it can, set this to `true`. /// /// Does nothing if this was constructed with [`PgListener::connect()`], as that creates an /// internal pool just for the new instance of `PgListener` which cannot be closed manually. pub fn ignore_pool_close_event(&mut self, val: bool) { self.ignore_close_event = val; } /// Set whether a lost connection in `try_recv()` should be re-established before it returns /// `Ok(None)`, or on the next call to `try_recv()`. /// /// By default, this is `true` and the connection is re-established before returning `Ok(None)`. /// /// If this is set to `false` then notifications will continue to be lost until the next call /// to `try_recv()`. If your recovery logic uses a different database connection then /// notifications that occur after it completes may be lost without any way to tell that they /// have been. pub fn eager_reconnect(&mut self, val: bool) { self.eager_reconnect = val; } /// Starts listening for notifications on a channel. /// The channel name is quoted here to ensure case sensitivity. pub async fn listen(&mut self, channel: &str) -> Result<(), Error> { self.connection() .await? .execute(AssertSqlSafe(format!(r#"LISTEN "{}""#, ident(channel)))) .await?; self.channels.push(channel.to_owned()); Ok(()) } /// Starts listening for notifications on all channels. pub async fn listen_all( &mut self, channels: impl IntoIterator, ) -> Result<(), Error> { let beg = self.channels.len(); self.channels.extend(channels.into_iter().map(|s| s.into())); let query = build_listen_all_query(&self.channels[beg..]); self.connection() .await? .execute(AssertSqlSafe(query)) .await?; Ok(()) } /// Stops listening for notifications on a channel. /// The channel name is quoted here to ensure case sensitivity. pub async fn unlisten(&mut self, channel: &str) -> Result<(), Error> { // use RAW connection and do NOT re-connect automatically, since this is not required for // UNLISTEN (we've disconnected anyways) if let Some(connection) = self.connection.as_mut() { connection .execute(AssertSqlSafe(format!(r#"UNLISTEN "{}""#, ident(channel)))) .await?; } if let Some(pos) = self.channels.iter().position(|s| s == channel) { self.channels.remove(pos); } Ok(()) } /// Stops listening for notifications on all channels. pub async fn unlisten_all(&mut self) -> Result<(), Error> { // use RAW connection and do NOT re-connect automatically, since this is not required for // UNLISTEN (we've disconnected anyways) if let Some(connection) = self.connection.as_mut() { connection.execute("UNLISTEN *").await?; } self.channels.clear(); Ok(()) } #[inline] async fn connect_if_needed(&mut self) -> Result<(), Error> { if self.connection.is_none() { let mut connection = self.pool.acquire().await?; connection.inner.stream.notifications = self.buffer_tx.take(); connection .execute(AssertSqlSafe(build_listen_all_query(&self.channels))) .await?; self.connection = Some(connection); } Ok(()) } #[inline] async fn connection(&mut self) -> Result<&mut PgConnection, Error> { // Ensure we have an active connection to work with. self.connect_if_needed().await?; Ok(self.connection.as_mut().unwrap()) } /// Receives the next notification available from any of the subscribed channels. /// /// If the connection to PostgreSQL is lost, it is automatically reconnected on the next /// call to `recv()`, and should be entirely transparent (as long as it was just an /// intermittent network failure or long-lived connection reaper). /// /// As notifications are transient, any received while the connection was lost, will not /// be returned. If you'd prefer the reconnection to be explicit and have a chance to /// do something before, please see [`try_recv`](Self::try_recv). /// /// # Example /// /// ```rust,no_run /// # use sqlx::postgres::PgListener; /// # /// # sqlx::__rt::test_block_on(async move { /// let mut listener = PgListener::connect("postgres:// ...").await?; /// loop { /// // ask for next notification, re-connecting (transparently) if needed /// let notification = listener.recv().await?; /// /// // handle notification, do something interesting /// } /// # Result::<(), sqlx::Error>::Ok(()) /// # }).unwrap(); /// ``` pub async fn recv(&mut self) -> Result { loop { if let Some(notification) = self.try_recv().await? { return Ok(notification); } } } /// Receives the next notification available from any of the subscribed channels. /// /// If the connection to PostgreSQL is lost, `None` is returned, and the connection is /// reconnected either immediately, or on the next call to `try_recv()`, depending on /// the value of [`eager_reconnect`]. /// /// # Example /// /// ```rust,no_run /// # use sqlx::postgres::PgListener; /// # /// # sqlx::__rt::test_block_on(async move { /// # let mut listener = PgListener::connect("postgres:// ...").await?; /// loop { /// // start handling notifications, connecting if needed /// while let Some(notification) = listener.try_recv().await? { /// // handle notification /// } /// /// // connection lost, do something interesting /// } /// # Result::<(), sqlx::Error>::Ok(()) /// # }).unwrap(); /// ``` /// /// [`eager_reconnect`]: PgListener::eager_reconnect pub async fn try_recv(&mut self) -> Result, Error> { // Flush the buffer first, if anything // This would only fill up if this listener is used as a connection if let Some(notification) = self.next_buffered() { return Ok(Some(notification)); } // Fetch our `CloseEvent` listener, if applicable. let mut close_event = (!self.ignore_close_event).then(|| self.pool.close_event()); loop { let next_message = self.connection().await?.inner.stream.recv_unchecked(); let res = if let Some(ref mut close_event) = close_event { // cancels the wait and returns `Err(PoolClosed)` if the pool is closed // before `next_message` returns, or if the pool was already closed close_event.do_until(next_message).await? } else { next_message.await }; let message = match res { Ok(message) => message, // The connection is dead, ensure that it is dropped, // update self state, and loop to try again. Err(Error::Io(err)) if matches!( err.kind(), io::ErrorKind::ConnectionAborted | io::ErrorKind::UnexpectedEof | // see ERRORS section in tcp(7) man page (https://man7.org/linux/man-pages/man7/tcp.7.html) io::ErrorKind::TimedOut | io::ErrorKind::BrokenPipe ) => { if let Some(mut conn) = self.connection.take() { self.buffer_tx = conn.inner.stream.notifications.take(); // Close the connection in a background task, so we can continue. conn.close_on_drop(); } if self.eager_reconnect { self.connect_if_needed().await?; } // lost connection return Ok(None); } // Forward other errors Err(error) => { return Err(error); } }; match message.format { // We've received an async notification, return it. BackendMessageFormat::NotificationResponse => { return Ok(Some(PgNotification(message.decode()?))); } // Mark the connection as ready for another query BackendMessageFormat::ReadyForQuery => { self.connection().await?.inner.pending_ready_for_query_count -= 1; } // Ignore unexpected messages _ => {} } } } /// Receives the next notification that already exists in the connection buffer, if any. /// /// This is similar to `try_recv`, except it will not wait if the connection has not yet received a notification. /// /// This is helpful if you want to retrieve all buffered notifications and process them in batches. pub fn next_buffered(&mut self) -> Option { if let Ok(Some(notification)) = self.buffer_rx.try_next() { Some(PgNotification(notification)) } else { None } } /// Consume this listener, returning a `Stream` of notifications. /// /// The backing connection will be automatically reconnected should it be lost. /// /// This has the same potential drawbacks as [`recv`](PgListener::recv). /// pub fn into_stream(mut self) -> impl Stream> + Unpin { Box::pin(try_stream! { loop { r#yield!(self.recv().await?); } }) } } impl Drop for PgListener { fn drop(&mut self) { if let Some(mut conn) = self.connection.take() { let fut = async move { let _ = conn.execute("UNLISTEN *").await; // inline the drop handler from `PoolConnection` so it doesn't try to spawn another task // otherwise, it may trigger a panic if this task is dropped because the runtime is going away: // https://github.com/launchbadge/sqlx/issues/1389 conn.return_to_pool().await; }; // Unregister any listeners before returning the connection to the pool. crate::rt::spawn(fut.in_current_span()); } } } impl<'c> Acquire<'c> for &'c mut PgListener { type Database = Postgres; type Connection = &'c mut PgConnection; fn acquire(self) -> BoxFuture<'c, Result> { self.connection().boxed() } fn begin(self) -> BoxFuture<'c, Result, Error>> { self.connection().and_then(|c| c.begin()).boxed() } } impl<'c> Executor<'c> for &'c mut PgListener { type Database = Postgres; fn fetch_many<'e, 'q, E>( self, query: E, ) -> BoxStream<'e, Result, Error>> where 'c: 'e, E: Execute<'q, Self::Database>, 'q: 'e, E: 'q, { futures_util::stream::once(async move { // need some basic type annotation to help the compiler a bit let res: Result<_, Error> = Ok(self.connection().await?.fetch_many(query)); res }) .try_flatten() .boxed() } fn fetch_optional<'e, 'q, E>(self, query: E) -> BoxFuture<'e, Result, Error>> where 'c: 'e, E: Execute<'q, Self::Database>, 'q: 'e, E: 'q, { async move { self.connection().await?.fetch_optional(query).await }.boxed() } fn prepare_with<'e>( self, query: SqlStr, parameters: &'e [PgTypeInfo], ) -> BoxFuture<'e, Result> where 'c: 'e, { async move { self.connection() .await? .prepare_with(query, parameters) .await } .boxed() } #[doc(hidden)] #[cfg(feature = "offline")] fn describe<'e>( self, query: SqlStr, ) -> BoxFuture<'e, Result, Error>> where 'c: 'e, { async move { self.connection().await?.describe(query).await }.boxed() } } impl PgNotification { /// The process ID of the notifying backend process. #[inline] pub fn process_id(&self) -> u32 { self.0.process_id } /// The channel that the notify has been raised on. This can be thought /// of as the message topic. #[inline] pub fn channel(&self) -> &str { from_utf8(&self.0.channel).unwrap() } /// The payload of the notification. An empty payload is received as an /// empty string. #[inline] pub fn payload(&self) -> &str { from_utf8(&self.0.payload).unwrap() } } impl Debug for PgListener { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PgListener").finish() } } impl Debug for PgNotification { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PgNotification") .field("process_id", &self.process_id()) .field("channel", &self.channel()) .field("payload", &self.payload()) .finish() } } fn ident(mut name: &str) -> String { // If the input string contains a NUL byte, we should truncate the // identifier. if let Some(index) = name.find('\0') { name = &name[..index]; } // Any double quotes must be escaped name.replace('"', "\"\"") } fn build_listen_all_query(channels: impl IntoIterator>) -> String { channels.into_iter().fold(String::new(), |mut acc, chan| { acc.push_str(r#"LISTEN ""#); acc.push_str(&ident(chan.as_ref())); acc.push_str(r#"";"#); acc }) } #[test] fn test_build_listen_all_query_with_single_channel() { let output = build_listen_all_query(["test"]); assert_eq!(output.as_str(), r#"LISTEN "test";"#); } #[test] fn test_build_listen_all_query_with_multiple_channels() { let output = build_listen_all_query(["channel.0", "channel.1"]); assert_eq!(output.as_str(), r#"LISTEN "channel.0";LISTEN "channel.1";"#); } ================================================ FILE: sqlx-postgres/src/message/authentication.rs ================================================ use std::str::from_utf8; use memchr::memchr; use sqlx_core::bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::ProtocolDecode; use crate::message::{BackendMessage, BackendMessageFormat}; use base64::prelude::{Engine as _, BASE64_STANDARD}; // On startup, the server sends an appropriate authentication request message, // to which the frontend must reply with an appropriate authentication // response message (such as a password). // For all authentication methods except GSSAPI, SSPI and SASL, there is at // most one request and one response. In some methods, no response at all is // needed from the frontend, and so no authentication request occurs. // For GSSAPI, SSPI and SASL, multiple exchanges of packets may // be needed to complete the authentication. // // #[derive(Debug)] pub enum Authentication { /// The authentication exchange is successfully completed. Ok, /// The frontend must now send a [PasswordMessage] containing the /// password in clear-text form. CleartextPassword, /// The frontend must now send a [PasswordMessage] containing the /// password (with user name) encrypted via MD5, then encrypted /// again using the 4-byte random salt. Md5Password(AuthenticationMd5Password), /// The frontend must now initiate a SASL negotiation, /// using one of the SASL mechanisms listed in the message. /// /// The frontend will send a [SaslInitialResponse] with the name /// of the selected mechanism, and the first part of the SASL /// data stream in response to this. /// /// If further messages are needed, the server will /// respond with [Authentication::SaslContinue]. Sasl(AuthenticationSasl), /// This message contains challenge data from the previous step of SASL negotiation. /// /// The frontend must respond with a [SaslResponse] message. SaslContinue(AuthenticationSaslContinue), /// SASL authentication has completed with additional mechanism-specific /// data for the client. /// /// The server will next send [Authentication::Ok] to /// indicate successful authentication. SaslFinal(AuthenticationSaslFinal), } impl BackendMessage for Authentication { const FORMAT: BackendMessageFormat = BackendMessageFormat::Authentication; fn decode_body(mut buf: Bytes) -> Result { Ok(match buf.get_u32() { 0 => Authentication::Ok, 3 => Authentication::CleartextPassword, 5 => { let mut salt = [0; 4]; buf.copy_to_slice(&mut salt); Authentication::Md5Password(AuthenticationMd5Password { salt }) } 10 => Authentication::Sasl(AuthenticationSasl(buf)), 11 => Authentication::SaslContinue(AuthenticationSaslContinue::decode(buf)?), 12 => Authentication::SaslFinal(AuthenticationSaslFinal::decode(buf)?), ty => { return Err(err_protocol!("unknown authentication method: {}", ty)); } }) } } /// Body of [Authentication::Md5Password]. #[derive(Debug)] pub struct AuthenticationMd5Password { pub salt: [u8; 4], } /// Body of [Authentication::Sasl]. #[derive(Debug)] pub struct AuthenticationSasl(Bytes); impl AuthenticationSasl { #[inline] pub fn mechanisms(&self) -> SaslMechanisms<'_> { SaslMechanisms(&self.0) } } /// An iterator over the SASL authentication mechanisms provided by the server. pub struct SaslMechanisms<'a>(&'a [u8]); impl<'a> Iterator for SaslMechanisms<'a> { type Item = &'a str; fn next(&mut self) -> Option { if !self.0.is_empty() && self.0[0] == b'\0' { return None; } let mechanism = memchr(b'\0', self.0).and_then(|nul| from_utf8(&self.0[..nul]).ok())?; self.0 = &self.0[(mechanism.len() + 1)..]; Some(mechanism) } } #[derive(Debug)] pub struct AuthenticationSaslContinue { pub salt: Vec, pub iterations: u32, pub nonce: String, pub message: String, } impl ProtocolDecode<'_> for AuthenticationSaslContinue { fn decode_with(buf: Bytes, _: ()) -> Result { let mut iterations: u32 = 4096; let mut salt = Vec::new(); let mut nonce = Bytes::new(); // [Example] // r=/z+giZiTxAH7r8sNAeHr7cvpqV3uo7G/bJBIJO3pjVM7t3ng,s=4UV68bIkC8f9/X8xH7aPhg==,i=4096 for item in buf.split(|b| *b == b',') { let key = item[0]; let value = &item[2..]; match key { b'r' => { nonce = buf.slice_ref(value); } b'i' => { iterations = atoi::atoi(value).unwrap_or(4096); } b's' => { salt = BASE64_STANDARD.decode(value).map_err(Error::protocol)?; } _ => {} } } Ok(Self { iterations, salt, nonce: from_utf8(&nonce).map_err(Error::protocol)?.to_owned(), message: from_utf8(&buf).map_err(Error::protocol)?.to_owned(), }) } } #[derive(Debug)] pub struct AuthenticationSaslFinal { pub verifier: Vec, } impl ProtocolDecode<'_> for AuthenticationSaslFinal { fn decode_with(buf: Bytes, _: ()) -> Result { let mut verifier = Vec::new(); for item in buf.split(|b| *b == b',') { let key = item[0]; let value = &item[2..]; if let b'v' = key { verifier = BASE64_STANDARD.decode(value).map_err(Error::protocol)?; } } Ok(Self { verifier }) } } ================================================ FILE: sqlx-postgres/src/message/backend_key_data.rs ================================================ use byteorder::{BigEndian, ByteOrder}; use sqlx_core::bytes::Bytes; use crate::error::Error; use crate::message::{BackendMessage, BackendMessageFormat}; /// Contains cancellation key data. The frontend must save these values if it /// wishes to be able to issue `CancelRequest` messages later. #[derive(Debug)] pub struct BackendKeyData { /// The process ID of this database. pub process_id: u32, /// The secret key of this database. pub secret_key: u32, } impl BackendMessage for BackendKeyData { const FORMAT: BackendMessageFormat = BackendMessageFormat::BackendKeyData; fn decode_body(buf: Bytes) -> Result { let process_id = BigEndian::read_u32(&buf); let secret_key = BigEndian::read_u32(&buf[4..]); Ok(Self { process_id, secret_key, }) } } #[test] fn test_decode_backend_key_data() { const DATA: &[u8] = b"\0\0'\xc6\x89R\xc5+"; let m = BackendKeyData::decode_body(DATA.into()).unwrap(); assert_eq!(m.process_id, 10182); assert_eq!(m.secret_key, 2303903019); } #[cfg(all(test, not(debug_assertions)))] #[bench] fn bench_decode_backend_key_data(b: &mut test::Bencher) { const DATA: &[u8] = b"\0\0'\xc6\x89R\xc5+"; b.iter(|| { BackendKeyData::decode_body(test::black_box(Bytes::from_static(DATA))).unwrap(); }); } ================================================ FILE: sqlx-postgres/src/message/bind.rs ================================================ use crate::io::{PgBufMutExt, PortalId, StatementId}; use crate::message::{FrontendMessage, FrontendMessageFormat}; use crate::PgValueFormat; use std::num::Saturating; /// /// /// ## Note: /// /// The integer values for number of bind parameters, number of parameter format codes, /// and number of result format codes all are interpreted as *unsigned*! #[derive(Debug)] pub struct Bind<'a> { /// The ID of the destination portal (`PortalId::UNNAMED` selects the unnamed portal). pub portal: PortalId, /// The id of the source prepared statement. pub statement: StatementId, /// The parameter format codes. Each must presently be zero (text) or one (binary). /// /// There can be zero to indicate that there are no parameters or that the parameters all use the /// default format (text); or one, in which case the specified format code is applied to all /// parameters; or it can equal the actual number of parameters. pub formats: &'a [PgValueFormat], // Note: interpreted as unsigned, as is `formats.len()` and `result_formats.len()` /// The number of parameters. /// /// May be different from `formats.len()` pub num_params: u16, /// The value of each parameter, in the indicated format. pub params: &'a [u8], /// The result-column format codes. Each must presently be zero (text) or one (binary). /// /// There can be zero to indicate that there are no result columns or that the /// result columns should all use the default format (text); or one, in which /// case the specified format code is applied to all result columns (if any); /// or it can equal the actual number of result columns of the query. pub result_formats: &'a [PgValueFormat], } impl FrontendMessage for Bind<'_> { const FORMAT: FrontendMessageFormat = FrontendMessageFormat::Bind; fn body_size_hint(&self) -> Saturating { let mut size = Saturating(0); size += self.portal.name_len(); size += self.statement.name_len(); // Parameter formats and length prefix size += 2; size += self.formats.len(); // `num_params` size += 2; size += self.params.len(); // Result formats and length prefix size += 2; size += self.result_formats.len(); size } fn encode_body(&self, buf: &mut Vec) -> Result<(), crate::Error> { buf.put_portal_name(self.portal); buf.put_statement_name(self.statement); // NOTE: the integer values for the number of parameters and format codes in this message // are all interpreted as *unsigned*! // // https://github.com/launchbadge/sqlx/issues/3464 let formats_len = u16::try_from(self.formats.len()).map_err(|_| { err_protocol!("too many parameter format codes ({})", self.formats.len()) })?; buf.extend(formats_len.to_be_bytes()); for &format in self.formats { buf.extend((format as i16).to_be_bytes()); } buf.extend(self.num_params.to_be_bytes()); buf.extend(self.params); let result_formats_len = u16::try_from(self.formats.len()) .map_err(|_| err_protocol!("too many result format codes ({})", self.formats.len()))?; buf.extend(result_formats_len.to_be_bytes()); for &format in self.result_formats { buf.extend((format as i16).to_be_bytes()); } Ok(()) } } // TODO: Unit Test Bind // TODO: Benchmark Bind ================================================ FILE: sqlx-postgres/src/message/close.rs ================================================ use crate::io::{PgBufMutExt, PortalId, StatementId}; use crate::message::{FrontendMessage, FrontendMessageFormat}; use std::num::Saturating; const CLOSE_PORTAL: u8 = b'P'; const CLOSE_STATEMENT: u8 = b'S'; #[derive(Debug)] #[allow(dead_code)] pub enum Close { Statement(StatementId), Portal(PortalId), } impl FrontendMessage for Close { const FORMAT: FrontendMessageFormat = FrontendMessageFormat::Close; fn body_size_hint(&self) -> Saturating { // Either `CLOSE_PORTAL` or `CLOSE_STATEMENT` let mut size = Saturating(1); match self { Close::Statement(id) => size += id.name_len(), Close::Portal(id) => size += id.name_len(), } size } fn encode_body(&self, buf: &mut Vec) -> Result<(), crate::Error> { match self { Close::Statement(id) => { buf.push(CLOSE_STATEMENT); buf.put_statement_name(*id); } Close::Portal(id) => { buf.push(CLOSE_PORTAL); buf.put_portal_name(*id); } } Ok(()) } } ================================================ FILE: sqlx-postgres/src/message/command_complete.rs ================================================ use atoi::atoi; use memchr::memrchr; use sqlx_core::bytes::Bytes; use crate::error::Error; use crate::message::{BackendMessage, BackendMessageFormat}; #[derive(Debug)] pub struct CommandComplete { /// The command tag. This is usually a single word that identifies which SQL command /// was completed. tag: Bytes, } impl BackendMessage for CommandComplete { const FORMAT: BackendMessageFormat = BackendMessageFormat::CommandComplete; fn decode_body(bytes: Bytes) -> Result { Ok(CommandComplete { tag: bytes }) } } impl CommandComplete { /// Returns the number of rows affected. /// If the command does not return rows (e.g., "CREATE TABLE"), returns 0. pub fn rows_affected(&self) -> u64 { // Look backwards for the first SPACE memrchr(b' ', &self.tag) // This is either a word or the number of rows affected .and_then(|i| atoi(&self.tag[(i + 1)..])) .unwrap_or(0) } } #[test] fn test_decode_command_complete_for_insert() { const DATA: &[u8] = b"INSERT 0 1214\0"; let cc = CommandComplete::decode_body(Bytes::from_static(DATA)).unwrap(); assert_eq!(cc.rows_affected(), 1214); } #[test] fn test_decode_command_complete_for_begin() { const DATA: &[u8] = b"BEGIN\0"; let cc = CommandComplete::decode_body(Bytes::from_static(DATA)).unwrap(); assert_eq!(cc.rows_affected(), 0); } #[test] fn test_decode_command_complete_for_update() { const DATA: &[u8] = b"UPDATE 5\0"; let cc = CommandComplete::decode_body(Bytes::from_static(DATA)).unwrap(); assert_eq!(cc.rows_affected(), 5); } #[cfg(all(test, not(debug_assertions)))] #[bench] fn bench_decode_command_complete(b: &mut test::Bencher) { const DATA: &[u8] = b"INSERT 0 1214\0"; b.iter(|| { let _ = CommandComplete::decode_body(test::black_box(Bytes::from_static(DATA))); }); } #[cfg(all(test, not(debug_assertions)))] #[bench] fn bench_decode_command_complete_rows_affected(b: &mut test::Bencher) { const DATA: &[u8] = b"INSERT 0 1214\0"; let data = CommandComplete::decode_body(Bytes::from_static(DATA)).unwrap(); b.iter(|| { let _rows = test::black_box(&data).rows_affected(); }); } ================================================ FILE: sqlx-postgres/src/message/copy.rs ================================================ use crate::error::Result; use crate::io::BufMutExt; use crate::message::{ BackendMessage, BackendMessageFormat, FrontendMessage, FrontendMessageFormat, }; use sqlx_core::bytes::{Buf, Bytes}; use sqlx_core::Error; use std::num::Saturating; use std::ops::Deref; /// The same structure is sent for both `CopyInResponse` and `CopyOutResponse` pub struct CopyResponseData { pub format: i8, pub num_columns: i16, pub format_codes: Vec, } pub struct CopyInResponse(pub CopyResponseData); #[allow(dead_code)] pub struct CopyOutResponse(pub CopyResponseData); pub struct CopyData(pub B); pub struct CopyFail { pub message: String, } pub struct CopyDone; impl CopyResponseData { #[inline] fn decode(mut buf: Bytes) -> Result { let format = buf.get_i8(); let num_columns = buf.get_i16(); let format_codes = (0..num_columns).map(|_| buf.get_i16()).collect(); Ok(CopyResponseData { format, num_columns, format_codes, }) } } impl BackendMessage for CopyInResponse { const FORMAT: BackendMessageFormat = BackendMessageFormat::CopyInResponse; #[inline(always)] fn decode_body(buf: Bytes) -> std::result::Result { Ok(Self(CopyResponseData::decode(buf)?)) } } impl BackendMessage for CopyOutResponse { const FORMAT: BackendMessageFormat = BackendMessageFormat::CopyOutResponse; #[inline(always)] fn decode_body(buf: Bytes) -> std::result::Result { Ok(Self(CopyResponseData::decode(buf)?)) } } impl BackendMessage for CopyData { const FORMAT: BackendMessageFormat = BackendMessageFormat::CopyData; #[inline(always)] fn decode_body(buf: Bytes) -> std::result::Result { Ok(Self(buf)) } } impl> FrontendMessage for CopyData { const FORMAT: FrontendMessageFormat = FrontendMessageFormat::CopyData; #[inline(always)] fn body_size_hint(&self) -> Saturating { Saturating(self.0.len()) } #[inline(always)] fn encode_body(&self, buf: &mut Vec) -> Result<(), Error> { buf.extend_from_slice(&self.0); Ok(()) } } impl FrontendMessage for CopyFail { const FORMAT: FrontendMessageFormat = FrontendMessageFormat::CopyFail; #[inline(always)] fn body_size_hint(&self) -> Saturating { Saturating(self.message.len()) } #[inline(always)] fn encode_body(&self, buf: &mut Vec) -> std::result::Result<(), Error> { buf.put_str_nul(&self.message); Ok(()) } } impl CopyFail { #[inline(always)] pub fn new(msg: impl Into) -> CopyFail { CopyFail { message: msg.into(), } } } impl FrontendMessage for CopyDone { const FORMAT: FrontendMessageFormat = FrontendMessageFormat::CopyDone; #[inline(always)] fn body_size_hint(&self) -> Saturating { Saturating(0) } #[inline(always)] fn encode_body(&self, _buf: &mut Vec) -> std::result::Result<(), Error> { Ok(()) } } impl BackendMessage for CopyDone { const FORMAT: BackendMessageFormat = BackendMessageFormat::CopyDone; #[inline(always)] fn decode_body(bytes: Bytes) -> std::result::Result { if !bytes.is_empty() { // Not fatal but may indicate a protocol change tracing::debug!( "Postgres backend returned non-empty message for CopyDone: \"{}\"", bytes.escape_ascii() ) } Ok(CopyDone) } } ================================================ FILE: sqlx-postgres/src/message/data_row.rs ================================================ use byteorder::{BigEndian, ByteOrder}; use sqlx_core::bytes::Bytes; use std::ops::Range; use crate::error::Error; use crate::message::{BackendMessage, BackendMessageFormat}; /// A row of data from the database. #[derive(Debug)] pub struct DataRow { pub(crate) storage: Bytes, /// Ranges into the stored row data. /// This uses `u32` instead of usize to reduce the size of this type. Values cannot be larger /// than `i32` in postgres. pub(crate) values: Vec>>, } impl DataRow { #[inline] pub(crate) fn get(&self, index: usize) -> Option<&'_ [u8]> { self.values[index] .as_ref() .map(|col| &self.storage[(col.start as usize)..(col.end as usize)]) } } impl BackendMessage for DataRow { const FORMAT: BackendMessageFormat = BackendMessageFormat::DataRow; fn decode_body(buf: Bytes) -> Result { if buf.len() < 2 { return Err(err_protocol!( "expected at least 2 bytes, got {}", buf.len() )); } let cnt = BigEndian::read_u16(&buf) as usize; let mut values = Vec::with_capacity(cnt); let mut offset: u32 = 2; for _ in 0..cnt { let value_start = offset .checked_add(4) .ok_or_else(|| err_protocol!("next value start out of range (offset: {offset})"))?; // widen both to a larger type for a safe comparison if (buf.len() as u64) < (value_start as u64) { return Err(err_protocol!( "expected 4 bytes at offset {offset}, got {}", (value_start as u64) - (buf.len() as u64) )); } // Length of the column value, in bytes (this count does not include itself). // Can be zero. As a special case, -1 indicates a NULL column value. // No value bytes follow in the NULL case. // // we know `offset` is within range of `buf.len()` from the above check #[allow(clippy::cast_possible_truncation)] let length = BigEndian::read_i32(&buf[(offset as usize)..]); if let Ok(length) = u32::try_from(length) { let value_end = value_start.checked_add(length).ok_or_else(|| { err_protocol!("value_start + length out of range ({offset} + {length})") })?; values.push(Some(value_start..value_end)); offset = value_end; } else { // Negative values signify NULL values.push(None); // `value_start` is actually the next value now. offset = value_start; } } Ok(Self { storage: buf, values, }) } } #[test] fn test_decode_data_row() { const DATA: &[u8] = b"\ \x00\x08\ \xff\xff\xff\xff\ \x00\x00\x00\x04\ \x00\x00\x00\n\ \xff\xff\xff\xff\ \x00\x00\x00\x04\ \x00\x00\x00\x14\ \xff\xff\xff\xff\ \x00\x00\x00\x04\ \x00\x00\x00(\ \xff\xff\xff\xff\ \x00\x00\x00\x04\ \x00\x00\x00P"; let row = DataRow::decode_body(DATA.into()).unwrap(); assert_eq!(row.values.len(), 8); assert!(row.get(0).is_none()); assert_eq!(row.get(1).unwrap(), &[0_u8, 0, 0, 10][..]); assert!(row.get(2).is_none()); assert_eq!(row.get(3).unwrap(), &[0_u8, 0, 0, 20][..]); assert!(row.get(4).is_none()); assert_eq!(row.get(5).unwrap(), &[0_u8, 0, 0, 40][..]); assert!(row.get(6).is_none()); assert_eq!(row.get(7).unwrap(), &[0_u8, 0, 0, 80][..]); } #[cfg(all(test, not(debug_assertions)))] #[bench] fn bench_data_row_get(b: &mut test::Bencher) { const DATA: &[u8] = b"\x00\x08\xff\xff\xff\xff\x00\x00\x00\x04\x00\x00\x00\n\xff\xff\xff\xff\x00\x00\x00\x04\x00\x00\x00\x14\xff\xff\xff\xff\x00\x00\x00\x04\x00\x00\x00(\xff\xff\xff\xff\x00\x00\x00\x04\x00\x00\x00P"; let row = DataRow::decode_body(test::black_box(Bytes::from_static(DATA))).unwrap(); b.iter(|| { let _value = test::black_box(&row).get(3); }); } #[cfg(all(test, not(debug_assertions)))] #[bench] fn bench_decode_data_row(b: &mut test::Bencher) { const DATA: &[u8] = b"\x00\x08\xff\xff\xff\xff\x00\x00\x00\x04\x00\x00\x00\n\xff\xff\xff\xff\x00\x00\x00\x04\x00\x00\x00\x14\xff\xff\xff\xff\x00\x00\x00\x04\x00\x00\x00(\xff\xff\xff\xff\x00\x00\x00\x04\x00\x00\x00P"; b.iter(|| { let _ = DataRow::decode_body(test::black_box(Bytes::from_static(DATA))); }); } ================================================ FILE: sqlx-postgres/src/message/describe.rs ================================================ use crate::io::{PgBufMutExt, PortalId, StatementId}; use crate::message::{FrontendMessage, FrontendMessageFormat}; use sqlx_core::Error; use std::num::Saturating; const DESCRIBE_PORTAL: u8 = b'P'; const DESCRIBE_STATEMENT: u8 = b'S'; /// Note: will emit both a RowDescription and a ParameterDescription message #[derive(Debug)] #[allow(dead_code)] pub enum Describe { Statement(StatementId), Portal(PortalId), } impl FrontendMessage for Describe { const FORMAT: FrontendMessageFormat = FrontendMessageFormat::Describe; fn body_size_hint(&self) -> Saturating { // Either `DESCRIBE_PORTAL` or `DESCRIBE_STATEMENT` let mut size = Saturating(1); match self { Describe::Statement(id) => size += id.name_len(), Describe::Portal(id) => size += id.name_len(), } size } fn encode_body(&self, buf: &mut Vec) -> Result<(), Error> { match self { // #[likely] Describe::Statement(id) => { buf.push(DESCRIBE_STATEMENT); buf.put_statement_name(*id); } Describe::Portal(id) => { buf.push(DESCRIBE_PORTAL); buf.put_portal_name(*id); } } Ok(()) } } #[cfg(test)] mod tests { use crate::message::FrontendMessage; use super::{Describe, PortalId, StatementId}; #[test] fn test_encode_describe_portal() { const EXPECTED: &[u8] = b"D\0\0\0\x17Psqlx_p_1234567890\0"; let mut buf = Vec::new(); let m = Describe::Portal(PortalId::TEST_VAL); m.encode_msg(&mut buf).unwrap(); assert_eq!(buf, EXPECTED); } #[test] fn test_encode_describe_unnamed_portal() { const EXPECTED: &[u8] = b"D\0\0\0\x06P\0"; let mut buf = Vec::new(); let m = Describe::Portal(PortalId::UNNAMED); m.encode_msg(&mut buf).unwrap(); assert_eq!(buf, EXPECTED); } #[test] fn test_encode_describe_statement() { const EXPECTED: &[u8] = b"D\0\0\0\x17Ssqlx_s_1234567890\0"; let mut buf = Vec::new(); let m = Describe::Statement(StatementId::TEST_VAL); m.encode_msg(&mut buf).unwrap(); assert_eq!(buf, EXPECTED); } #[test] fn test_encode_describe_unnamed_statement() { const EXPECTED: &[u8] = b"D\0\0\0\x06S\0"; let mut buf = Vec::new(); let m = Describe::Statement(StatementId::UNNAMED); m.encode_msg(&mut buf).unwrap(); assert_eq!(buf, EXPECTED); } } ================================================ FILE: sqlx-postgres/src/message/execute.rs ================================================ use std::num::Saturating; use sqlx_core::Error; use crate::io::{PgBufMutExt, PortalId}; use crate::message::{FrontendMessage, FrontendMessageFormat}; pub struct Execute { /// The id of the portal to execute. pub portal: PortalId, /// Maximum number of rows to return, if portal contains a query /// that returns rows (ignored otherwise). Zero denotes “no limit”. pub limit: u32, } impl FrontendMessage for Execute { const FORMAT: FrontendMessageFormat = FrontendMessageFormat::Execute; fn body_size_hint(&self) -> Saturating { let mut size = Saturating(0); size += self.portal.name_len(); size += 2; // limit size } fn encode_body(&self, buf: &mut Vec) -> Result<(), Error> { buf.put_portal_name(self.portal); buf.extend(&self.limit.to_be_bytes()); Ok(()) } } #[cfg(test)] mod tests { use crate::io::PortalId; use crate::message::FrontendMessage; use super::Execute; #[test] fn test_encode_execute_named_portal() { const EXPECTED: &[u8] = b"E\0\0\0\x1Asqlx_p_1234567890\0\0\0\0\x02"; let mut buf = Vec::new(); let m = Execute { portal: PortalId::TEST_VAL, limit: 2, }; m.encode_msg(&mut buf).unwrap(); assert_eq!(buf, EXPECTED); } #[test] fn test_encode_execute_unnamed_portal() { const EXPECTED: &[u8] = b"E\0\0\0\x09\0\x49\x96\x02\xD2"; let mut buf = Vec::new(); let m = Execute { portal: PortalId::UNNAMED, limit: 1234567890, }; m.encode_msg(&mut buf).unwrap(); assert_eq!(buf, EXPECTED); } } ================================================ FILE: sqlx-postgres/src/message/flush.rs ================================================ use crate::message::{FrontendMessage, FrontendMessageFormat}; use sqlx_core::Error; use std::num::Saturating; /// The Flush message does not cause any specific output to be generated, /// but forces the backend to deliver any data pending in its output buffers. /// /// A Flush must be sent after any extended-query command except Sync, if the /// frontend wishes to examine the results of that command before issuing more commands. #[derive(Debug)] pub struct Flush; impl FrontendMessage for Flush { const FORMAT: FrontendMessageFormat = FrontendMessageFormat::Flush; #[inline(always)] fn body_size_hint(&self) -> Saturating { Saturating(0) } #[inline(always)] fn encode_body(&self, _buf: &mut Vec) -> Result<(), Error> { Ok(()) } } ================================================ FILE: sqlx-postgres/src/message/mod.rs ================================================ use sqlx_core::bytes::Bytes; use std::num::Saturating; use crate::error::Error; use crate::io::PgBufMutExt; mod authentication; mod backend_key_data; mod bind; mod close; mod command_complete; mod copy; mod data_row; mod describe; mod execute; mod flush; mod notification; mod parameter_description; mod parameter_status; mod parse; mod parse_complete; mod password; mod query; mod ready_for_query; mod response; mod row_description; mod sasl; mod ssl_request; mod startup; mod sync; mod terminate; pub use authentication::{Authentication, AuthenticationSasl}; pub use backend_key_data::BackendKeyData; pub use bind::Bind; pub use close::Close; pub use command_complete::CommandComplete; pub use copy::{CopyData, CopyDone, CopyFail, CopyInResponse, CopyOutResponse, CopyResponseData}; pub use data_row::DataRow; pub use describe::Describe; pub use execute::Execute; #[allow(unused_imports)] pub use flush::Flush; pub use notification::Notification; pub use parameter_description::ParameterDescription; pub use parameter_status::ParameterStatus; pub use parse::Parse; pub use parse_complete::ParseComplete; pub use password::Password; pub use query::Query; pub use ready_for_query::{ReadyForQuery, TransactionStatus}; pub use response::{Notice, PgSeverity}; pub use row_description::RowDescription; pub use sasl::{SaslInitialResponse, SaslResponse}; use sqlx_core::io::ProtocolEncode; pub use ssl_request::SslRequest; pub use startup::Startup; pub use sync::Sync; pub use terminate::Terminate; // Note: we can't use the same enum for both frontend and backend message formats // because there are duplicated format codes between them. // // For example, `Close` (frontend) and `CommandComplete` (backend) both use format code `C`. // #[derive(Debug, PartialOrd, PartialEq)] #[repr(u8)] pub enum FrontendMessageFormat { Bind = b'B', Close = b'C', CopyData = b'd', CopyDone = b'c', CopyFail = b'f', Describe = b'D', Execute = b'E', Flush = b'H', Parse = b'P', /// This message format is polymorphic. It's used for: /// /// * Plain password responses /// * MD5 password responses /// * SASL responses /// * GSSAPI/SSPI responses PasswordPolymorphic = b'p', Query = b'Q', Sync = b'S', Terminate = b'X', } #[derive(Debug, PartialOrd, PartialEq)] #[repr(u8)] pub enum BackendMessageFormat { Authentication, BackendKeyData, BindComplete, CloseComplete, CommandComplete, CopyData, CopyDone, CopyInResponse, CopyOutResponse, DataRow, EmptyQueryResponse, ErrorResponse, NoData, NoticeResponse, NotificationResponse, ParameterDescription, ParameterStatus, ParseComplete, PortalSuspended, ReadyForQuery, RowDescription, } #[derive(Debug)] pub struct ReceivedMessage { pub format: BackendMessageFormat, pub contents: Bytes, } impl ReceivedMessage { #[inline] pub fn decode(self) -> Result where T: BackendMessage, { if T::FORMAT != self.format { return Err(err_protocol!( "Postgres protocol error: expected {:?}, got {:?}", T::FORMAT, self.format )); } T::decode_body(self.contents).map_err(|e| match e { Error::Protocol(s) => { err_protocol!("Postgres protocol error (reading {:?}): {s}", self.format) } other => other, }) } } impl BackendMessageFormat { pub fn try_from_u8(v: u8) -> Result { // https://www.postgresql.org/docs/current/protocol-message-formats.html Ok(match v { b'1' => BackendMessageFormat::ParseComplete, b'2' => BackendMessageFormat::BindComplete, b'3' => BackendMessageFormat::CloseComplete, b'C' => BackendMessageFormat::CommandComplete, b'd' => BackendMessageFormat::CopyData, b'c' => BackendMessageFormat::CopyDone, b'G' => BackendMessageFormat::CopyInResponse, b'H' => BackendMessageFormat::CopyOutResponse, b'D' => BackendMessageFormat::DataRow, b'E' => BackendMessageFormat::ErrorResponse, b'I' => BackendMessageFormat::EmptyQueryResponse, b'A' => BackendMessageFormat::NotificationResponse, b'K' => BackendMessageFormat::BackendKeyData, b'N' => BackendMessageFormat::NoticeResponse, b'R' => BackendMessageFormat::Authentication, b'S' => BackendMessageFormat::ParameterStatus, b'T' => BackendMessageFormat::RowDescription, b'Z' => BackendMessageFormat::ReadyForQuery, b'n' => BackendMessageFormat::NoData, b's' => BackendMessageFormat::PortalSuspended, b't' => BackendMessageFormat::ParameterDescription, _ => return Err(err_protocol!("unknown message type: {:?}", v as char)), }) } } pub(crate) trait FrontendMessage: Sized { /// The format prefix of this message. const FORMAT: FrontendMessageFormat; /// Return the amount of space, in bytes, to reserve in the buffer passed to [`Self::encode_body()`]. fn body_size_hint(&self) -> Saturating; /// Encode this type as a Frontend message in the Postgres protocol. /// /// The implementation should *not* include `Self::FORMAT` or the length prefix. fn encode_body(&self, buf: &mut Vec) -> Result<(), Error>; #[inline(always)] #[cfg_attr(not(test), allow(dead_code))] fn encode_msg(self, buf: &mut Vec) -> Result<(), Error> { EncodeMessage(self).encode(buf) } } pub(crate) trait BackendMessage: Sized { /// The expected message format. /// /// const FORMAT: BackendMessageFormat; /// Decode this type from a Backend message in the Postgres protocol. /// /// The format code and length prefix have already been read and are not at the start of `bytes`. fn decode_body(buf: Bytes) -> Result; } pub struct EncodeMessage(pub F); impl ProtocolEncode<'_, ()> for EncodeMessage { fn encode_with(&self, buf: &mut Vec, _context: ()) -> Result<(), Error> { let mut size_hint = self.0.body_size_hint(); // plus format code and length prefix size_hint += 5; // don't panic if `size_hint` is ridiculous buf.try_reserve(size_hint.0).map_err(|e| { err_protocol!( "Postgres protocol: error allocating {} bytes for encoding message {:?}: {e}", size_hint.0, F::FORMAT, ) })?; buf.push(F::FORMAT as u8); buf.put_length_prefixed(|buf| self.0.encode_body(buf)) } } ================================================ FILE: sqlx-postgres/src/message/notification.rs ================================================ use sqlx_core::bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::BufExt; use crate::message::{BackendMessage, BackendMessageFormat}; #[derive(Debug)] pub struct Notification { pub(crate) process_id: u32, pub(crate) channel: Bytes, pub(crate) payload: Bytes, } impl BackendMessage for Notification { const FORMAT: BackendMessageFormat = BackendMessageFormat::NotificationResponse; fn decode_body(mut buf: Bytes) -> Result { let process_id = buf.get_u32(); let channel = buf.get_bytes_nul()?; let payload = buf.get_bytes_nul()?; Ok(Self { process_id, channel, payload, }) } } #[test] fn test_decode_notification_response() { const NOTIFICATION_RESPONSE: &[u8] = b"\x34\x20\x10\x02TEST-CHANNEL\0THIS IS A TEST\0"; let message = Notification::decode_body(Bytes::from(NOTIFICATION_RESPONSE)).unwrap(); assert_eq!(message.process_id, 0x34201002); assert_eq!(&*message.channel, &b"TEST-CHANNEL"[..]); assert_eq!(&*message.payload, &b"THIS IS A TEST"[..]); } ================================================ FILE: sqlx-postgres/src/message/parameter_description.rs ================================================ use smallvec::SmallVec; use sqlx_core::bytes::{Buf, Bytes}; use crate::error::Error; use crate::message::{BackendMessage, BackendMessageFormat}; use crate::types::Oid; #[derive(Debug)] pub struct ParameterDescription { pub types: SmallVec<[Oid; 6]>, } impl BackendMessage for ParameterDescription { const FORMAT: BackendMessageFormat = BackendMessageFormat::ParameterDescription; fn decode_body(mut buf: Bytes) -> Result { // Note: this is correct, max parameters is 65535, not 32767 // https://github.com/launchbadge/sqlx/issues/3464 let cnt = buf.get_u16(); let mut types = SmallVec::with_capacity(cnt as usize); for _ in 0..cnt { types.push(Oid(buf.get_u32())); } Ok(Self { types }) } } #[test] fn test_decode_parameter_description() { const DATA: &[u8] = b"\x00\x02\x00\x00\x00\x00\x00\x00\x05\x00"; let m = ParameterDescription::decode_body(DATA.into()).unwrap(); assert_eq!(m.types.len(), 2); assert_eq!(m.types[0], Oid(0x0000_0000)); assert_eq!(m.types[1], Oid(0x0000_0500)); } #[test] fn test_decode_empty_parameter_description() { const DATA: &[u8] = b"\x00\x00"; let m = ParameterDescription::decode_body(DATA.into()).unwrap(); assert!(m.types.is_empty()); } #[cfg(all(test, not(debug_assertions)))] #[bench] fn bench_decode_parameter_description(b: &mut test::Bencher) { const DATA: &[u8] = b"\x00\x02\x00\x00\x00\x00\x00\x00\x05\x00"; b.iter(|| { ParameterDescription::decode_body(test::black_box(Bytes::from_static(DATA))).unwrap(); }); } ================================================ FILE: sqlx-postgres/src/message/parameter_status.rs ================================================ use sqlx_core::bytes::Bytes; use crate::error::Error; use crate::io::BufExt; use crate::message::{BackendMessage, BackendMessageFormat}; #[derive(Debug)] pub struct ParameterStatus { pub name: String, pub value: String, } impl BackendMessage for ParameterStatus { const FORMAT: BackendMessageFormat = BackendMessageFormat::ParameterStatus; fn decode_body(mut buf: Bytes) -> Result { let name = buf.get_str_nul()?; let value = buf.get_str_nul()?; Ok(Self { name, value }) } } #[test] fn test_decode_parameter_status() { const DATA: &[u8] = b"client_encoding\x00UTF8\x00"; let m = ParameterStatus::decode_body(DATA.into()).unwrap(); assert_eq!(&m.name, "client_encoding"); assert_eq!(&m.value, "UTF8") } #[test] fn test_decode_empty_parameter_status() { const DATA: &[u8] = b"\x00\x00"; let m = ParameterStatus::decode_body(DATA.into()).unwrap(); assert!(m.name.is_empty()); assert!(m.value.is_empty()); } #[cfg(all(test, not(debug_assertions)))] #[bench] fn bench_decode_parameter_status(b: &mut test::Bencher) { const DATA: &[u8] = b"client_encoding\x00UTF8\x00"; b.iter(|| { ParameterStatus::decode_body(test::black_box(Bytes::from_static(DATA))).unwrap(); }); } #[test] fn test_decode_parameter_status_response() { const PARAMETER_STATUS_RESPONSE: &[u8] = b"crdb_version\0CockroachDB CCL v21.1.0 (x86_64-unknown-linux-gnu, built 2021/05/17 13:49:40, go1.15.11)\0"; let message = ParameterStatus::decode_body(Bytes::from(PARAMETER_STATUS_RESPONSE)).unwrap(); assert_eq!(message.name, "crdb_version"); assert_eq!( message.value, "CockroachDB CCL v21.1.0 (x86_64-unknown-linux-gnu, built 2021/05/17 13:49:40, go1.15.11)" ); } ================================================ FILE: sqlx-postgres/src/message/parse.rs ================================================ use crate::io::BufMutExt; use crate::io::{PgBufMutExt, StatementId}; use crate::message::{FrontendMessage, FrontendMessageFormat}; use crate::types::Oid; use sqlx_core::Error; use std::num::Saturating; #[derive(Debug)] pub struct Parse<'a> { /// The ID of the destination prepared statement. pub statement: StatementId, /// The query string to be parsed. pub query: &'a str, /// The parameter data types specified (could be zero). Note that this is not an /// indication of the number of parameters that might appear in the query string, /// only the number that the frontend wants to pre-specify types for. pub param_types: &'a [Oid], } impl FrontendMessage for Parse<'_> { const FORMAT: FrontendMessageFormat = FrontendMessageFormat::Parse; fn body_size_hint(&self) -> Saturating { let mut size = Saturating(0); size += self.statement.name_len(); size += self.query.len(); size += 1; // NUL terminator size += 2; // param_types_len // `param_types` size += self.param_types.len().saturating_mul(4); size } fn encode_body(&self, buf: &mut Vec) -> Result<(), Error> { buf.put_statement_name(self.statement); buf.put_str_nul(self.query); // Note: actually interpreted as unsigned // https://github.com/launchbadge/sqlx/issues/3464 let param_types_len = u16::try_from(self.param_types.len()).map_err(|_| { err_protocol!( "param_types.len() too large for binary protocol: {}", self.param_types.len() ) })?; buf.extend(param_types_len.to_be_bytes()); for &oid in self.param_types { buf.extend(oid.0.to_be_bytes()); } Ok(()) } } #[test] fn test_encode_parse() { const EXPECTED: &[u8] = b"P\0\0\0\x26sqlx_s_1234567890\0SELECT $1\0\0\x01\0\0\0\x19"; let mut buf = Vec::new(); let m = Parse { statement: StatementId::TEST_VAL, query: "SELECT $1", param_types: &[Oid(25)], }; m.encode_msg(&mut buf).unwrap(); assert_eq!(buf, EXPECTED); } #[test] fn test_encode_parse_unnamed_statement() { const EXPECTED: &[u8] = b"P\0\0\0\x15\0SELECT $1\0\0\x01\0\0\0\x19"; let mut buf = Vec::new(); let m = Parse { statement: StatementId::UNNAMED, query: "SELECT $1", param_types: &[Oid(25)], }; m.encode_msg(&mut buf).unwrap(); assert_eq!(buf, EXPECTED); } ================================================ FILE: sqlx-postgres/src/message/parse_complete.rs ================================================ use crate::message::{BackendMessage, BackendMessageFormat}; use sqlx_core::bytes::Bytes; use sqlx_core::Error; pub struct ParseComplete; impl BackendMessage for ParseComplete { const FORMAT: BackendMessageFormat = BackendMessageFormat::ParseComplete; fn decode_body(_bytes: Bytes) -> Result { Ok(ParseComplete) } } ================================================ FILE: sqlx-postgres/src/message/password.rs ================================================ use crate::io::BufMutExt; use crate::message::{FrontendMessage, FrontendMessageFormat}; use md5::{Digest, Md5}; use sqlx_core::Error; use std::fmt::Write; use std::num::Saturating; #[derive(Debug)] pub enum Password<'a> { Cleartext(&'a str), Md5 { password: &'a str, username: &'a str, salt: [u8; 4], }, } impl FrontendMessage for Password<'_> { const FORMAT: FrontendMessageFormat = FrontendMessageFormat::PasswordPolymorphic; #[inline(always)] fn body_size_hint(&self) -> Saturating { let mut size = Saturating(0); match self { Password::Cleartext(password) => { // To avoid reporting the exact password length anywhere, // we deliberately give a bad estimate. // // This shouldn't affect performance in the long run. size += password .len() .saturating_add(1) // NUL terminator .checked_next_power_of_two() .unwrap_or(usize::MAX); } Password::Md5 { .. } => { // "md5<32 hex chars>\0" size += 36; } } size } fn encode_body(&self, buf: &mut Vec) -> Result<(), Error> { match self { Password::Cleartext(password) => { buf.put_str_nul(password); } Password::Md5 { username, password, salt, } => { // The actual `PasswordMessage` can be computed in SQL as // `concat('md5', md5(concat(md5(concat(password, username)), random-salt)))`. // Keep in mind the md5() function returns its result as a hex string. let mut hasher = Md5::new(); hasher.update(password); hasher.update(username); let mut output = String::with_capacity(35); let _ = write!(output, "{:x}", hasher.finalize_reset()); hasher.update(&output); hasher.update(salt); output.clear(); let _ = write!(output, "md5{:x}", hasher.finalize()); buf.put_str_nul(&output); } } Ok(()) } } #[cfg(test)] mod tests { use crate::message::FrontendMessage; use super::Password; #[test] fn test_encode_clear_password() { const EXPECTED: &[u8] = b"p\0\0\0\rpassword\0"; let mut buf = Vec::new(); let m = Password::Cleartext("password"); m.encode_msg(&mut buf).unwrap(); assert_eq!(buf, EXPECTED); } #[test] fn test_encode_md5_password() { const EXPECTED: &[u8] = b"p\0\0\0(md53e2c9d99d49b201ef867a36f3f9ed62c\0"; let mut buf = Vec::new(); let m = Password::Md5 { password: "password", username: "root", salt: [147, 24, 57, 152], }; m.encode_msg(&mut buf).unwrap(); assert_eq!(buf, EXPECTED); } #[cfg(all(test, not(debug_assertions)))] #[bench] fn bench_encode_clear_password(b: &mut test::Bencher) { use test::black_box; let mut buf = Vec::with_capacity(128); b.iter(|| { buf.clear(); black_box(Password::Cleartext("password")).encode_msg(&mut buf); }); } #[cfg(all(test, not(debug_assertions)))] #[bench] fn bench_encode_md5_password(b: &mut test::Bencher) { use test::black_box; let mut buf = Vec::with_capacity(128); b.iter(|| { buf.clear(); black_box(Password::Md5 { password: "password", username: "root", salt: [147, 24, 57, 152], }) .encode_msg(&mut buf); }); } } ================================================ FILE: sqlx-postgres/src/message/query.rs ================================================ use crate::io::BufMutExt; use crate::message::{FrontendMessage, FrontendMessageFormat}; use sqlx_core::Error; use std::num::Saturating; #[derive(Debug)] pub struct Query<'a>(pub &'a str); impl FrontendMessage for Query<'_> { const FORMAT: FrontendMessageFormat = FrontendMessageFormat::Query; fn body_size_hint(&self) -> Saturating { let mut size = Saturating(0); size += self.0.len(); size += 1; // NUL terminator size } fn encode_body(&self, buf: &mut Vec) -> Result<(), Error> { buf.put_str_nul(self.0); Ok(()) } } #[test] fn test_encode_query() { const EXPECTED: &[u8] = b"Q\0\0\0\x0DSELECT 1\0"; let mut buf = Vec::new(); let m = Query("SELECT 1"); m.encode_msg(&mut buf).unwrap(); assert_eq!(buf, EXPECTED); } ================================================ FILE: sqlx-postgres/src/message/ready_for_query.rs ================================================ use sqlx_core::bytes::Bytes; use crate::error::Error; use crate::message::{BackendMessage, BackendMessageFormat}; #[derive(Debug)] #[repr(u8)] pub enum TransactionStatus { /// Not in a transaction block. Idle = b'I', /// In a transaction block. Transaction = b'T', /// In a _failed_ transaction block. Queries will be rejected until block is ended. Error = b'E', } #[derive(Debug)] pub struct ReadyForQuery { pub transaction_status: TransactionStatus, } impl BackendMessage for ReadyForQuery { const FORMAT: BackendMessageFormat = BackendMessageFormat::ReadyForQuery; fn decode_body(buf: Bytes) -> Result { let status = match buf[0] { b'I' => TransactionStatus::Idle, b'T' => TransactionStatus::Transaction, b'E' => TransactionStatus::Error, status => { return Err(err_protocol!( "unknown transaction status: {:?}", status as char )); } }; Ok(Self { transaction_status: status, }) } } #[test] fn test_decode_ready_for_query() -> Result<(), Error> { const DATA: &[u8] = b"E"; let m = ReadyForQuery::decode_body(Bytes::from_static(DATA))?; assert!(matches!(m.transaction_status, TransactionStatus::Error)); Ok(()) } ================================================ FILE: sqlx-postgres/src/message/response.rs ================================================ use std::ops::Range; use std::str::from_utf8; use memchr::memchr; use sqlx_core::bytes::Bytes; use crate::error::Error; use crate::io::ProtocolDecode; use crate::message::{BackendMessage, BackendMessageFormat}; #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[repr(u8)] pub enum PgSeverity { Panic, Fatal, Error, Warning, Notice, Debug, Info, Log, } impl PgSeverity { #[inline] pub fn is_error(self) -> bool { matches!(self, Self::Panic | Self::Fatal | Self::Error) } } impl TryFrom<&str> for PgSeverity { type Error = Error; fn try_from(s: &str) -> Result { let result = match s { "PANIC" => PgSeverity::Panic, "FATAL" => PgSeverity::Fatal, "ERROR" => PgSeverity::Error, "WARNING" => PgSeverity::Warning, "NOTICE" => PgSeverity::Notice, "DEBUG" => PgSeverity::Debug, "INFO" => PgSeverity::Info, "LOG" => PgSeverity::Log, severity => { return Err(err_protocol!("unknown severity: {:?}", severity)); } }; Ok(result) } } #[derive(Debug)] pub struct Notice { storage: Bytes, severity: PgSeverity, message: Range, code: Range, } impl Notice { #[inline] pub fn severity(&self) -> PgSeverity { self.severity } #[inline] pub fn code(&self) -> &str { self.get_cached_str(self.code.clone()) } #[inline] pub fn message(&self) -> &str { self.get_cached_str(self.message.clone()) } // Field descriptions available here: // https://www.postgresql.org/docs/current/protocol-error-fields.html #[inline] pub fn get(&self, ty: u8) -> Option<&str> { self.get_raw(ty).and_then(|v| from_utf8(v).ok()) } pub fn get_raw(&self, ty: u8) -> Option<&[u8]> { self.fields() .filter(|(field, _)| *field == ty) .map(|(_, range)| &self.storage[range]) .next() } } impl Notice { #[inline] fn fields(&self) -> Fields<'_> { Fields { storage: &self.storage, offset: 0, } } #[inline] fn get_cached_str(&self, cache: Range) -> &str { // unwrap: this cannot fail at this stage from_utf8(&self.storage[cache]).unwrap() } } impl ProtocolDecode<'_> for Notice { fn decode_with(buf: Bytes, _: ()) -> Result { // In order to support PostgreSQL 9.5 and older we need to parse the localized S field. // Newer versions additionally come with the V field that is guaranteed to be in English. // We thus read both versions and prefer the unlocalized one if available. const DEFAULT_SEVERITY: PgSeverity = PgSeverity::Log; let mut severity_v = None; let mut severity_s = None; let mut message = 0..0; let mut code = 0..0; // we cache the three always present fields // this enables to keep the access time down for the fields most likely accessed let fields = Fields { storage: &buf, offset: 0, }; for (field, v) in fields { if !(message.is_empty() || code.is_empty()) { // stop iterating when we have the 3 fields we were looking for // we assume V (severity) was the first field as it should be break; } match field { b'S' => { severity_s = from_utf8(&buf[v.clone()]) // If the error string is not UTF-8, we have no hope of interpreting it, // localized or not. The `V` field would likely fail to parse as well. .map_err(|_| notice_protocol_err())? .try_into() // If we couldn't parse the severity here, it might just be localized. .ok(); } b'V' => { // Propagate errors here, because V is not localized and // thus we are missing a possible variant. severity_v = Some( from_utf8(&buf[v.clone()]) .map_err(|_| notice_protocol_err())? .try_into()?, ); } b'M' => { _ = from_utf8(&buf[v.clone()]).map_err(|_| notice_protocol_err())?; message = v; } b'C' => { _ = from_utf8(&buf[v.clone()]).map_err(|_| notice_protocol_err())?; code = v; } // If more fields are added, make sure to check that they are valid UTF-8, // otherwise the get_cached_str method will panic. _ => {} } } Ok(Self { severity: severity_v.or(severity_s).unwrap_or(DEFAULT_SEVERITY), message, code, storage: buf, }) } } impl BackendMessage for Notice { const FORMAT: BackendMessageFormat = BackendMessageFormat::NoticeResponse; fn decode_body(buf: Bytes) -> Result { // Keeping both impls for now Self::decode_with(buf, ()) } } /// An iterator over each field in the Error (or Notice) response. struct Fields<'a> { storage: &'a [u8], offset: usize, } impl Iterator for Fields<'_> { type Item = (u8, Range); fn next(&mut self) -> Option { // The fields in the response body are sequentially stored as [tag][string], // ending in a final, additional [nul] let ty = *self.storage.get(self.offset)?; if ty == 0 { return None; } // Consume the type byte self.offset = self.offset.checked_add(1)?; let start = self.offset; let len = memchr(b'\0', self.storage.get(start..)?)?; // Neither can overflow as they will always be `<= self.storage.len()`. let end = self.offset + len; self.offset = end + 1; Some((ty, start..end)) } } fn notice_protocol_err() -> Error { // https://github.com/launchbadge/sqlx/issues/1144 Error::Protocol( "Postgres returned a non-UTF-8 string for its error message. \ This is most likely due to an error that occurred during authentication and \ the default lc_messages locale is not binary-compatible with UTF-8. \ See the server logs for the error details." .into(), ) } #[test] fn test_decode_error_response() { const DATA: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, skipping\0Fextension.c\0L1656\0RCreateExtension\0\0"; let m = Notice::decode(Bytes::from_static(DATA)).unwrap(); assert_eq!( m.message(), "extension \"uuid-ossp\" already exists, skipping" ); assert!(matches!(m.severity(), PgSeverity::Notice)); assert_eq!(m.code(), "42710"); } #[cfg(all(test, not(debug_assertions)))] #[bench] fn bench_error_response_get_message(b: &mut test::Bencher) { const DATA: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, skipping\0Fextension.c\0L1656\0RCreateExtension\0\0"; let res = Notice::decode(test::black_box(Bytes::from_static(DATA))).unwrap(); b.iter(|| { let _ = test::black_box(&res).message(); }); } #[cfg(all(test, not(debug_assertions)))] #[bench] fn bench_decode_error_response(b: &mut test::Bencher) { const DATA: &[u8] = b"SNOTICE\0VNOTICE\0C42710\0Mextension \"uuid-ossp\" already exists, skipping\0Fextension.c\0L1656\0RCreateExtension\0\0"; b.iter(|| { let _ = Notice::decode(test::black_box(Bytes::from_static(DATA))); }); } ================================================ FILE: sqlx-postgres/src/message/row_description.rs ================================================ use sqlx_core::bytes::{Buf, Bytes}; use crate::error::Error; use crate::io::BufExt; use crate::message::{BackendMessage, BackendMessageFormat}; use crate::types::Oid; #[derive(Debug)] pub struct RowDescription { pub fields: Vec, } #[derive(Debug)] pub struct Field { /// The name of the field. pub name: String, /// If the field can be identified as a column of a specific table, the /// object ID of the table; otherwise zero. pub relation_id: Option, /// If the field can be identified as a column of a specific table, the attribute number of /// the column; otherwise zero. pub relation_attribute_no: Option, /// The object ID of the field's data type. pub data_type_id: Oid, /// The data type size (see pg_type.typlen). Note that negative values denote /// variable-width types. #[allow(dead_code)] pub data_type_size: i16, /// The type modifier (see pg_attribute.atttypmod). The meaning of the /// modifier is type-specific. #[allow(dead_code)] pub type_modifier: i32, /// The format code being used for the field. #[allow(dead_code)] pub format: i16, } impl BackendMessage for RowDescription { const FORMAT: BackendMessageFormat = BackendMessageFormat::RowDescription; fn decode_body(mut buf: Bytes) -> Result { if buf.len() < 2 { return Err(err_protocol!( "expected at least 2 bytes, got {}", buf.len() )); } let cnt = buf.get_u16(); let mut fields = Vec::with_capacity(cnt as usize); for _ in 0..cnt { let name = buf.get_str_nul()?.to_owned(); if buf.len() < 18 { return Err(err_protocol!( "expected at least 18 bytes after field name {name:?}, got {}", buf.len() )); } let relation_id = buf.get_u32(); let relation_attribute_no = buf.get_i16(); let data_type_id = Oid(buf.get_u32()); let data_type_size = buf.get_i16(); let type_modifier = buf.get_i32(); let format = buf.get_i16(); fields.push(Field { name, relation_id: if relation_id == 0 { None } else { Some(Oid(relation_id)) }, relation_attribute_no: if relation_attribute_no == 0 { None } else { Some(relation_attribute_no) }, data_type_id, data_type_size, type_modifier, format, }) } Ok(Self { fields }) } } // TODO: Unit Test RowDescription // TODO: Benchmark RowDescription ================================================ FILE: sqlx-postgres/src/message/sasl.rs ================================================ use crate::io::BufMutExt; use crate::message::{FrontendMessage, FrontendMessageFormat}; use sqlx_core::Error; use std::num::Saturating; pub struct SaslInitialResponse<'a> { pub response: &'a str, pub plus: bool, } impl SaslInitialResponse<'_> { #[inline(always)] fn selected_mechanism(&self) -> &'static str { if self.plus { "SCRAM-SHA-256-PLUS" } else { "SCRAM-SHA-256" } } } impl FrontendMessage for SaslInitialResponse<'_> { const FORMAT: FrontendMessageFormat = FrontendMessageFormat::PasswordPolymorphic; #[inline(always)] fn body_size_hint(&self) -> Saturating { let mut size = Saturating(0); size += self.selected_mechanism().len(); size += 1; // NUL terminator size += 4; // response_len size += self.response.len(); size } fn encode_body(&self, buf: &mut Vec) -> Result<(), Error> { // name of the SASL authentication mechanism that the client selected buf.put_str_nul(self.selected_mechanism()); let response_len = i32::try_from(self.response.len()).map_err(|_| { err_protocol!( "SASL Initial Response length too long for protocol: {}", self.response.len() ) })?; buf.extend_from_slice(&response_len.to_be_bytes()); buf.extend_from_slice(self.response.as_bytes()); Ok(()) } } pub struct SaslResponse<'a>(pub &'a str); impl FrontendMessage for SaslResponse<'_> { const FORMAT: FrontendMessageFormat = FrontendMessageFormat::PasswordPolymorphic; fn body_size_hint(&self) -> Saturating { Saturating(self.0.len()) } fn encode_body(&self, buf: &mut Vec) -> Result<(), Error> { buf.extend(self.0.as_bytes()); Ok(()) } } ================================================ FILE: sqlx-postgres/src/message/ssl_request.rs ================================================ use crate::io::ProtocolEncode; pub struct SslRequest; impl SslRequest { // https://www.postgresql.org/docs/current/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-SSLREQUEST pub const BYTES: &'static [u8] = b"\x00\x00\x00\x08\x04\xd2\x16\x2f"; } // Cannot impl FrontendMessage because it does not have a format code impl ProtocolEncode<'_> for SslRequest { #[inline(always)] fn encode_with(&self, buf: &mut Vec, _context: ()) -> Result<(), crate::Error> { buf.extend_from_slice(Self::BYTES); Ok(()) } } #[test] fn test_encode_ssl_request() { let mut buf = Vec::new(); // Int32(8) // Length of message contents in bytes, including self. buf.extend_from_slice(&8_u32.to_be_bytes()); // Int32(80877103) // The SSL request code. The value is chosen to contain 1234 in the most significant 16 bits, // and 5679 in the least significant 16 bits. // (To avoid confusion, this code must not be the same as any protocol version number.) buf.extend_from_slice(&(((1234 << 16) | 5679) as u32).to_be_bytes()); let mut encoded = Vec::new(); SslRequest.encode(&mut encoded).unwrap(); assert_eq!(buf, SslRequest::BYTES); assert_eq!(buf, encoded); } ================================================ FILE: sqlx-postgres/src/message/startup.rs ================================================ use crate::io::PgBufMutExt; use crate::io::{BufMutExt, ProtocolEncode}; // To begin a session, a frontend opens a connection to the server and sends a startup message. // This message includes the names of the user and of the database the user wants to connect to; // it also identifies the particular protocol version to be used. // Optionally, the startup message can include additional settings for run-time parameters. pub struct Startup<'a> { /// The database user name to connect as. Required; there is no default. pub username: Option<&'a str>, /// The database to connect to. Defaults to the user name. pub database: Option<&'a str>, /// Additional start-up params. /// pub params: &'a [(&'a str, &'a str)], } // Startup cannot impl FrontendMessage because it doesn't have a format code. impl ProtocolEncode<'_> for Startup<'_> { fn encode_with(&self, buf: &mut Vec, _context: ()) -> Result<(), crate::Error> { buf.reserve(120); buf.put_length_prefixed(|buf| { // The protocol version number. The most significant 16 bits are the // major version number (3 for the protocol described here). The least // significant 16 bits are the minor version number (0 // for the protocol described here) buf.extend(&196_608_i32.to_be_bytes()); if let Some(username) = self.username { // The database user name to connect as. encode_startup_param(buf, "user", username); } if let Some(database) = self.database { // The database to connect to. Defaults to the user name. encode_startup_param(buf, "database", database); } for (name, value) in self.params { encode_startup_param(buf, name, value); } // A zero byte is required as a terminator // after the last name/value pair. buf.push(0); Ok(()) }) } } #[inline] fn encode_startup_param(buf: &mut Vec, name: &str, value: &str) { buf.put_str_nul(name); buf.put_str_nul(value); } #[test] fn test_encode_startup() { const EXPECTED: &[u8] = b"\0\0\0)\0\x03\0\0user\0postgres\0database\0postgres\0\0"; let mut buf = Vec::new(); let m = Startup { username: Some("postgres"), database: Some("postgres"), params: &[], }; m.encode(&mut buf).unwrap(); assert_eq!(buf, EXPECTED); } #[cfg(all(test, not(debug_assertions)))] #[bench] fn bench_encode_startup(b: &mut test::Bencher) { use test::black_box; let mut buf = Vec::with_capacity(128); b.iter(|| { buf.clear(); black_box(Startup { username: Some("postgres"), database: Some("postgres"), params: &[], }) .encode(&mut buf); }); } ================================================ FILE: sqlx-postgres/src/message/sync.rs ================================================ use crate::message::{FrontendMessage, FrontendMessageFormat}; use sqlx_core::Error; use std::num::Saturating; #[derive(Debug)] pub struct Sync; impl FrontendMessage for Sync { const FORMAT: FrontendMessageFormat = FrontendMessageFormat::Sync; #[inline(always)] fn body_size_hint(&self) -> Saturating { Saturating(0) } #[inline(always)] fn encode_body(&self, _buf: &mut Vec) -> Result<(), Error> { Ok(()) } } ================================================ FILE: sqlx-postgres/src/message/terminate.rs ================================================ use crate::message::{FrontendMessage, FrontendMessageFormat}; use sqlx_core::Error; use std::num::Saturating; pub struct Terminate; impl FrontendMessage for Terminate { const FORMAT: FrontendMessageFormat = FrontendMessageFormat::Terminate; #[inline(always)] fn body_size_hint(&self) -> Saturating { Saturating(0) } #[inline(always)] fn encode_body(&self, _buf: &mut Vec) -> Result<(), Error> { Ok(()) } } ================================================ FILE: sqlx-postgres/src/migrate.rs ================================================ use std::str::FromStr; use std::time::Duration; use std::time::Instant; use futures_core::future::BoxFuture; pub(crate) use sqlx_core::migrate::MigrateError; pub(crate) use sqlx_core::migrate::{AppliedMigration, Migration}; pub(crate) use sqlx_core::migrate::{Migrate, MigrateDatabase}; use sqlx_core::sql_str::AssertSqlSafe; use crate::connection::{ConnectOptions, Connection}; use crate::error::Error; use crate::executor::Executor; use crate::query::query; use crate::query_as::query_as; use crate::query_scalar::query_scalar; use crate::{PgConnectOptions, PgConnection, Postgres}; fn parse_for_maintenance(url: &str) -> Result<(PgConnectOptions, String), Error> { let mut options = PgConnectOptions::from_str(url)?; // pull out the name of the database to create let database = options .database .as_deref() .unwrap_or(&options.username) .to_owned(); // switch us to the maintenance database // use `postgres` _unless_ the database is postgres, in which case, use `template1` // this matches the behavior of the `createdb` util options.database = if database == "postgres" { Some("template1".into()) } else { Some("postgres".into()) }; Ok((options, database)) } impl MigrateDatabase for Postgres { async fn create_database(url: &str) -> Result<(), Error> { let (options, database) = parse_for_maintenance(url)?; let mut conn = options.connect().await?; let _ = conn .execute(AssertSqlSafe(format!( "CREATE DATABASE \"{}\"", database.replace('"', "\"\"") ))) .await?; Ok(()) } async fn database_exists(url: &str) -> Result { let (options, database) = parse_for_maintenance(url)?; let mut conn = options.connect().await?; let exists: bool = query_scalar("select exists(SELECT 1 from pg_database WHERE datname = $1)") .bind(database) .fetch_one(&mut conn) .await?; Ok(exists) } async fn drop_database(url: &str) -> Result<(), Error> { let (options, database) = parse_for_maintenance(url)?; let mut conn = options.connect().await?; let _ = conn .execute(AssertSqlSafe(format!( "DROP DATABASE IF EXISTS \"{}\"", database.replace('"', "\"\"") ))) .await?; Ok(()) } async fn force_drop_database(url: &str) -> Result<(), Error> { let (options, database) = parse_for_maintenance(url)?; let mut conn = options.connect().await?; let row: (String,) = query_as("SELECT current_setting('server_version_num')") .fetch_one(&mut conn) .await?; let version = row.0.parse::().unwrap(); let pid_type = if version >= 90200 { "pid" } else { "procpid" }; conn.execute(AssertSqlSafe(format!( "SELECT pg_terminate_backend(pg_stat_activity.{pid_type}) FROM pg_stat_activity \ WHERE pg_stat_activity.datname = '{database}' AND {pid_type} <> pg_backend_pid()" ))) .await?; Self::drop_database(url).await } } impl Migrate for PgConnection { fn create_schema_if_not_exists<'e>( &'e mut self, schema_name: &'e str, ) -> BoxFuture<'e, Result<(), MigrateError>> { Box::pin(async move { // language=SQL self.execute(AssertSqlSafe(format!( r#"CREATE SCHEMA IF NOT EXISTS {schema_name};"# ))) .await?; Ok(()) }) } fn ensure_migrations_table<'e>( &'e mut self, table_name: &'e str, ) -> BoxFuture<'e, Result<(), MigrateError>> { Box::pin(async move { // language=SQL self.execute(AssertSqlSafe(format!( r#" CREATE TABLE IF NOT EXISTS {table_name} ( version BIGINT PRIMARY KEY, description TEXT NOT NULL, installed_on TIMESTAMPTZ NOT NULL DEFAULT now(), success BOOLEAN NOT NULL, checksum BYTEA NOT NULL, execution_time BIGINT NOT NULL ); "# ))) .await?; Ok(()) }) } fn dirty_version<'e>( &'e mut self, table_name: &'e str, ) -> BoxFuture<'e, Result, MigrateError>> { Box::pin(async move { // language=SQL let row: Option<(i64,)> = query_as(AssertSqlSafe(format!( "SELECT version FROM {table_name} WHERE success = false ORDER BY version LIMIT 1" ))) .fetch_optional(self) .await?; Ok(row.map(|r| r.0)) }) } fn list_applied_migrations<'e>( &'e mut self, table_name: &'e str, ) -> BoxFuture<'e, Result, MigrateError>> { Box::pin(async move { // language=SQL let rows: Vec<(i64, Vec)> = query_as(AssertSqlSafe(format!( "SELECT version, checksum FROM {table_name} ORDER BY version" ))) .fetch_all(self) .await?; let migrations = rows .into_iter() .map(|(version, checksum)| AppliedMigration { version, checksum: checksum.into(), }) .collect(); Ok(migrations) }) } fn lock(&mut self) -> BoxFuture<'_, Result<(), MigrateError>> { Box::pin(async move { let database_name = current_database(self).await?; let lock_id = generate_lock_id(&database_name); // create an application lock over the database // this function will not return until the lock is acquired // https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS // https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS-TABLE // language=SQL let _ = query("SELECT pg_advisory_lock($1)") .bind(lock_id) .execute(self) .await?; Ok(()) }) } fn unlock(&mut self) -> BoxFuture<'_, Result<(), MigrateError>> { Box::pin(async move { let database_name = current_database(self).await?; let lock_id = generate_lock_id(&database_name); // language=SQL let _ = query("SELECT pg_advisory_unlock($1)") .bind(lock_id) .execute(self) .await?; Ok(()) }) } fn apply<'e>( &'e mut self, table_name: &'e str, migration: &'e Migration, ) -> BoxFuture<'e, Result> { Box::pin(async move { let start = Instant::now(); // execute migration queries if migration.no_tx { execute_migration(self, table_name, migration).await?; } else { // Use a single transaction for the actual migration script and the essential bookkeeping so we never // execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966. // The `execution_time` however can only be measured for the whole transaction. This value _only_ exists for // data lineage and debugging reasons, so it is not super important if it is lost. So we initialize it to -1 // and update it once the actual transaction completed. let mut tx = self.begin().await?; execute_migration(&mut tx, table_name, migration).await?; tx.commit().await?; } // Update `elapsed_time`. // NOTE: The process may disconnect/die at this point, so the elapsed time value might be lost. We accept // this small risk since this value is not super important. let elapsed = start.elapsed(); // language=SQL #[allow(clippy::cast_possible_truncation)] let _ = query(AssertSqlSafe(format!( r#" UPDATE {table_name} SET execution_time = $1 WHERE version = $2 "# ))) .bind(elapsed.as_nanos() as i64) .bind(migration.version) .execute(self) .await?; Ok(elapsed) }) } fn revert<'e>( &'e mut self, table_name: &'e str, migration: &'e Migration, ) -> BoxFuture<'e, Result> { Box::pin(async move { let start = Instant::now(); // execute migration queries if migration.no_tx { revert_migration(self, table_name, migration).await?; } else { // Use a single transaction for the actual migration script and the essential bookkeeping so we never // execute migrations twice. See https://github.com/launchbadge/sqlx/issues/1966. let mut tx = self.begin().await?; revert_migration(&mut tx, table_name, migration).await?; tx.commit().await?; } let elapsed = start.elapsed(); Ok(elapsed) }) } } async fn execute_migration( conn: &mut PgConnection, table_name: &str, migration: &Migration, ) -> Result<(), MigrateError> { let _ = conn .execute(migration.sql.clone()) .await .map_err(|e| MigrateError::ExecuteMigration(e, migration.version))?; // language=SQL let _ = query(AssertSqlSafe(format!( r#" INSERT INTO {table_name} ( version, description, success, checksum, execution_time ) VALUES ( $1, $2, TRUE, $3, -1 ) "# ))) .bind(migration.version) .bind(&*migration.description) .bind(&*migration.checksum) .execute(conn) .await?; Ok(()) } async fn revert_migration( conn: &mut PgConnection, table_name: &str, migration: &Migration, ) -> Result<(), MigrateError> { let _ = conn .execute(migration.sql.clone()) .await .map_err(|e| MigrateError::ExecuteMigration(e, migration.version))?; // language=SQL let _ = query(AssertSqlSafe(format!( r#"DELETE FROM {table_name} WHERE version = $1"# ))) .bind(migration.version) .execute(conn) .await?; Ok(()) } async fn current_database(conn: &mut PgConnection) -> Result { // language=SQL Ok(query_scalar("SELECT current_database()") .fetch_one(conn) .await?) } // inspired from rails: https://github.com/rails/rails/blob/6e49cc77ab3d16c06e12f93158eaf3e507d4120e/activerecord/lib/active_record/migration.rb#L1308 fn generate_lock_id(database_name: &str) -> i64 { const CRC_IEEE: crc::Crc = crc::Crc::::new(&crc::CRC_32_ISO_HDLC); // 0x3d32ad9e chosen by fair dice roll 0x3d32ad9e * (CRC_IEEE.checksum(database_name.as_bytes()) as i64) } ================================================ FILE: sqlx-postgres/src/options/connect.rs ================================================ use crate::connection::ConnectOptions; use crate::error::Error; use crate::{PgConnectOptions, PgConnection}; use log::LevelFilter; use sqlx_core::Url; use std::future::Future; use std::time::Duration; impl ConnectOptions for PgConnectOptions { type Connection = PgConnection; fn from_url(url: &Url) -> Result { Self::parse_from_url(url) } fn to_url_lossy(&self) -> Url { self.build_url() } fn connect(&self) -> impl Future> + Send + '_ where Self::Connection: Sized, { PgConnection::establish(self) } fn log_statements(mut self, level: LevelFilter) -> Self { self.log_settings.log_statements(level); self } fn log_slow_statements(mut self, level: LevelFilter, duration: Duration) -> Self { self.log_settings.log_slow_statements(level, duration); self } } ================================================ FILE: sqlx-postgres/src/options/doc.md ================================================ Options and flags which can be used to configure a PostgreSQL connection. A value of `PgConnectOptions` can be parsed from a connection URL, as described by [libpq][libpq-connstring]. The general form for a connection URL is: ```text postgresql://[user[:password]@][host][:port][/dbname][?param1=value1&...] ``` The URL scheme designator can be either `postgresql://` or `postgres://`. Each of the URL parts is optional. For defaults, see the next section. This type also implements [`FromStr`][std::str::FromStr] so you can parse it from a string containing a connection URL and then further adjust options if necessary (see example below). Note that characters not allowed in URLs must be [percent-encoded]. # Parameters This API accepts many of the same parameters as [libpq][libpq-params]; if a parameter is not passed in via URL, it is populated by reading [environment variables][libpq-envars] or choosing customary defaults. | Parameter | Environment Variable | Default / Remarks | |--------------------|----------------------|-------------------------------------------------------------| | `user` | `PGUSER` | The `whoami` of the currently running process. | | `password` | `PGPASSWORD` | Read from [`passfile`], if it exists. | | [`passfile`] | `PGPASSFILE` | `~/.pgpass` or `%APPDATA%\postgresql\pgpass.conf` (Windows) | | `host` | `PGHOST` | See [Note: Default Host](#note-default-host). | | `hostaddr` | `PGHOSTADDR` | See [Note: Default Host](#note-default-host). | | `port` | `PGPORT` | `5432` | | `dbname` | `PGDATABASE` | Unset; defaults to the username server-side. | | `sslmode` | `PGSSLMODE` | `prefer`. See [`PgSslMode`] for details. | | `sslrootcert` | `PGSSLROOTCERT` | Unset. See [Note: SSL](#note-ssl). | | `sslcert` | `PGSSLCERT` | Unset. See [Note: SSL](#note-ssl). | | `sslkey` | `PGSSLKEY` | Unset. See [Note: SSL](#note-ssl). | | `options` | `PGOPTIONS` | Unset. | | `application_name` | `PGAPPNAME` | Unset. | [`passfile`] handling may be bypassed using [`PgConnectOptions::new_without_pgpass()`]. ## SQLx-Specific SQLx also parses some bespoke parameters. These are _not_ configurable by environment variable. Instead, the name is linked to the method to set the value. | Parameter | Default | |--------------------------------------------------------------|-------------------------------| | [`statement-cache-capacity`][Self::statement_cache_capacity] | `100` | # Example URLs ```text postgresql:// postgresql://:5433 postgresql://localhost postgresql://localhost:5433 postgresql://localhost/mydb postgresql://user@localhost postgresql://user:secret@localhost postgresql://user:correct%20horse%20battery%20staple@localhost postgresql://localhost?dbname=mydb&user=postgres&password=postgres ``` See also [Note: Unix Domain Sockets](#note-unix-domain-sockets) below. # Note: Default Host If the connection URL does not contain a hostname and `PGHOST` is not set, this constructor looks for an open Unix domain socket in one of a few standard locations (configured when Postgres is built): * `/var/run/postgresql/.s.PGSQL.{port}` (Debian) * `/private/tmp/.s.PGSQL.{port}` (macOS when installed through Homebrew) * `/tmp/.s.PGSQL.{port}` (default otherwise) This depends on the value of `port` being correct. If Postgres is using a port other than the default (`5432`), `port` must be set. If no Unix domain socket is found, `localhost` is assumed. Note: this description is updated on a best-effort basis. See `default_host()` in the same source file as this method for the current behavior. # Note: SSL ## Root Certs If `sslrootcert` is not set, the default root certificates used depends on Cargo features: * If `tls-native-tls` is enabled, the system root certificates are used. * If `tls-rustls-ring-native-roots` is enabled, the system root certificates are used. * Otherwise, TLS roots are populated using the [`webpki-roots`] crate. ## Environment Variables Unlike with `libpq`, the following environment variables may be _either_ a path to a file _or_ a string value containing a [PEM-encoded value][rfc7468]: * `PGSSLROOTCERT` * `PGSSLCERT` * `PGSSLKEY` If the string begins with the standard `-----BEGIN -----` header and ends with the standard `-----END -----` footer, it is parsed directly. This behavior is _only_ implemented for the environment variables, not the URL parameters. Note: passing the SSL private key via environment variable may be a security risk. # Note: Unix Domain Sockets If you want to connect to Postgres over a Unix domain socket, you can pass the path to the _directory_ containing the socket as the `host` parameter. The final path to the socket will be `{host}/.s.PGSQL.{port}` as is standard for Postgres. If you're passing the domain socket path as the host segment of the URL, forward slashes in the path must be [percent-encoded] (replacing `/` with `%2F`), e.g.: ```text postgres://%2Fvar%2Frun%2Fpostgresql/dbname Different port: postgres://%2Fvar%2Frun%2Fpostgresql:5433/dbname With username and password: postgres://user:password@%2Fvar%2Frun%2Fpostgresql/dbname With username and password, and different port: postgres://user:password@%2Fvar%2Frun%2Fpostgresql:5432/dbname ``` Instead, the hostname can be passed in the query segment of the URL, which does not require forward-slashes to be percent-encoded (however, [other characters are][percent-encoded]): ```text postgres:dbname?host=/var/run/postgresql Different port: postgres://:5433/dbname?host=/var/run/postgresql With username and password: postgres://user:password@/dbname?host=/var/run/postgresql With username and password, and different port: postgres://user:password@:5433/dbname?host=/var/run/postgresql ``` # Example ```rust,no_run use sqlx::{Connection, ConnectOptions}; use sqlx::postgres::{PgConnectOptions, PgConnection, PgPool, PgSslMode}; # async fn example() -> sqlx::Result<()> { // URL connection string let conn = PgConnection::connect("postgres://localhost/mydb").await?; // Manually-constructed options let conn = PgConnectOptions::new() .host("secret-host") .port(2525) .username("secret-user") .password("secret-password") .ssl_mode(PgSslMode::Require) .connect() .await?; // Modifying options parsed from a string let mut opts: PgConnectOptions = "postgres://localhost/mydb".parse()?; // Change the log verbosity level for queries. // Information about SQL queries is logged at `DEBUG` level by default. opts = opts.log_statements(log::LevelFilter::Trace); let pool = PgPool::connect_with(opts).await?; # Ok(()) # } ``` [percent-encoded]: https://developer.mozilla.org/en-US/docs/Glossary/Percent-encoding [`passfile`]: https://www.postgresql.org/docs/current/libpq-pgpass.html [libpq-connstring]: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING [libpq-params]: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS [libpq-envars]: https://www.postgresql.org/docs/current/libpq-envars.html [rfc7468]: https://datatracker.ietf.org/doc/html/rfc7468 [`webpki-roots`]: https://docs.rs/webpki-roots ================================================ FILE: sqlx-postgres/src/options/mod.rs ================================================ use std::borrow::Cow; use std::env::var; use std::fmt::{self, Display, Write}; use std::path::{Path, PathBuf}; pub use ssl_mode::PgSslMode; use crate::{connection::LogSettings, net::tls::CertificateInput}; mod connect; mod parse; mod pgpass; mod ssl_mode; #[doc = include_str!("doc.md")] #[derive(Debug, Clone)] pub struct PgConnectOptions { pub(crate) host: String, pub(crate) port: u16, pub(crate) socket: Option, pub(crate) username: String, pub(crate) password: Option, pub(crate) database: Option, pub(crate) ssl_mode: PgSslMode, pub(crate) ssl_root_cert: Option, pub(crate) ssl_client_cert: Option, pub(crate) ssl_client_key: Option, pub(crate) statement_cache_capacity: usize, pub(crate) application_name: Option, pub(crate) log_settings: LogSettings, pub(crate) extra_float_digits: Option>, pub(crate) options: Option, } impl Default for PgConnectOptions { fn default() -> Self { Self::new_without_pgpass().apply_pgpass() } } impl PgConnectOptions { /// Create a default set of connection options populated from the current environment. /// /// This behaves as if parsed from the connection string `postgres://` /// /// See the type-level documentation for details. pub fn new() -> Self { Self::new_without_pgpass().apply_pgpass() } /// Create a default set of connection options _without_ reading from `passfile`. /// /// Equivalent to [`PgConnectOptions::new()`] but `passfile` is ignored. /// /// See the type-level documentation for details. pub fn new_without_pgpass() -> Self { let port = var("PGPORT") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(5432); let host = var("PGHOSTADDR") .ok() .or_else(|| var("PGHOST").ok()) .unwrap_or_else(|| default_host(port)); let username = if let Ok(username) = var("PGUSER") { username } else if let Ok(username) = whoami::username() { username } else { // keep the same fallback as previous version "unknown".to_string() }; let database = var("PGDATABASE").ok(); PgConnectOptions { port, host, socket: None, username, password: var("PGPASSWORD").ok(), database, ssl_root_cert: var("PGSSLROOTCERT").ok().map(CertificateInput::from), ssl_client_cert: var("PGSSLCERT").ok().map(CertificateInput::from), // As of writing, the implementation of `From` only looks for // `-----BEGIN CERTIFICATE-----` and so will not attempt to parse // a PEM-encoded private key. ssl_client_key: var("PGSSLKEY").ok().map(CertificateInput::from), ssl_mode: var("PGSSLMODE") .ok() .and_then(|v| v.parse().ok()) .unwrap_or_default(), statement_cache_capacity: 100, application_name: var("PGAPPNAME").ok(), extra_float_digits: Some("2".into()), log_settings: Default::default(), options: var("PGOPTIONS").ok(), } } pub(crate) fn apply_pgpass(mut self) -> Self { if self.password.is_none() { self.password = pgpass::load_password( &self.host, self.port, &self.username, self.database.as_deref(), ); } self } /// Sets the name of the host to connect to. /// /// If a host name begins with a slash, it specifies /// Unix-domain communication rather than TCP/IP communication; the value is the name of /// the directory in which the socket file is stored. /// /// The default behavior when host is not specified, or is empty, /// is to connect to a Unix-domain socket /// /// # Example /// /// ```rust /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .host("localhost"); /// ``` pub fn host(mut self, host: &str) -> Self { host.clone_into(&mut self.host); self } /// Sets the port to connect to at the server host. /// /// The default port for PostgreSQL is `5432`. /// /// # Example /// /// ```rust /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .port(5432); /// ``` pub fn port(mut self, port: u16) -> Self { self.port = port; self } /// Sets a custom path to a directory containing a unix domain socket, /// switching the connection method from TCP to the corresponding socket. /// /// By default set to `None`. pub fn socket(mut self, path: impl AsRef) -> Self { self.socket = Some(path.as_ref().to_path_buf()); self } /// Sets the username to connect as. /// /// Defaults to be the same as the operating system name of /// the user running the application. /// /// # Example /// /// ```rust /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .username("postgres"); /// ``` pub fn username(mut self, username: &str) -> Self { username.clone_into(&mut self.username); self } /// Sets the password to use if the server demands password authentication. /// /// # Example /// /// ```rust /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .username("root") /// .password("safe-and-secure"); /// ``` pub fn password(mut self, password: &str) -> Self { self.password = Some(password.to_owned()); self } /// Sets the database name. Defaults to be the same as the user name. /// /// # Example /// /// ```rust /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .database("postgres"); /// ``` pub fn database(mut self, database: &str) -> Self { self.database = Some(database.to_owned()); self } /// Sets whether or with what priority a secure SSL TCP/IP connection will be negotiated /// with the server. /// /// By default, the SSL mode is [`Prefer`](PgSslMode::Prefer), and the client will /// first attempt an SSL connection but fallback to a non-SSL connection on failure. /// /// Ignored for Unix domain socket communication. /// /// # Example /// /// ```rust /// # use sqlx_postgres::{PgSslMode, PgConnectOptions}; /// let options = PgConnectOptions::new() /// .ssl_mode(PgSslMode::Require); /// ``` pub fn ssl_mode(mut self, mode: PgSslMode) -> Self { self.ssl_mode = mode; self } /// Sets the name of a file containing SSL certificate authority (CA) certificate(s). /// If the file exists, the server's certificate will be verified to be signed by /// one of these authorities. /// /// # Example /// /// ```rust /// # use sqlx_postgres::{PgSslMode, PgConnectOptions}; /// let options = PgConnectOptions::new() /// // Providing a CA certificate with less than VerifyCa is pointless /// .ssl_mode(PgSslMode::VerifyCa) /// .ssl_root_cert("./ca-certificate.crt"); /// ``` pub fn ssl_root_cert(mut self, cert: impl AsRef) -> Self { self.ssl_root_cert = Some(CertificateInput::File(cert.as_ref().to_path_buf())); self } /// Sets the name of a file containing SSL client certificate. /// /// # Example /// /// ```rust /// # use sqlx_postgres::{PgSslMode, PgConnectOptions}; /// let options = PgConnectOptions::new() /// // Providing a CA certificate with less than VerifyCa is pointless /// .ssl_mode(PgSslMode::VerifyCa) /// .ssl_client_cert("./client.crt"); /// ``` pub fn ssl_client_cert(mut self, cert: impl AsRef) -> Self { self.ssl_client_cert = Some(CertificateInput::File(cert.as_ref().to_path_buf())); self } /// Sets the SSL client certificate as a PEM-encoded byte slice. /// /// This should be an ASCII-encoded blob that starts with `-----BEGIN CERTIFICATE-----`. /// /// # Example /// Note: embedding SSL certificates and keys in the binary is not advised. /// This is for illustration purposes only. /// /// ```rust /// # use sqlx_postgres::{PgSslMode, PgConnectOptions}; /// /// const CERT: &[u8] = b"\ /// -----BEGIN CERTIFICATE----- /// /// -----END CERTIFICATE-----"; /// /// let options = PgConnectOptions::new() /// // Providing a CA certificate with less than VerifyCa is pointless /// .ssl_mode(PgSslMode::VerifyCa) /// .ssl_client_cert_from_pem(CERT); /// ``` pub fn ssl_client_cert_from_pem(mut self, cert: impl AsRef<[u8]>) -> Self { self.ssl_client_cert = Some(CertificateInput::Inline(cert.as_ref().to_vec())); self } /// Sets the name of a file containing SSL client key. /// /// # Example /// /// ```rust /// # use sqlx_postgres::{PgSslMode, PgConnectOptions}; /// let options = PgConnectOptions::new() /// // Providing a CA certificate with less than VerifyCa is pointless /// .ssl_mode(PgSslMode::VerifyCa) /// .ssl_client_key("./client.key"); /// ``` pub fn ssl_client_key(mut self, key: impl AsRef) -> Self { self.ssl_client_key = Some(CertificateInput::File(key.as_ref().to_path_buf())); self } /// Sets the SSL client key as a PEM-encoded byte slice. /// /// This should be an ASCII-encoded blob that starts with `-----BEGIN PRIVATE KEY-----`. /// /// # Example /// Note: embedding SSL certificates and keys in the binary is not advised. /// This is for illustration purposes only. /// /// ```rust /// # use sqlx_postgres::{PgSslMode, PgConnectOptions}; /// /// const KEY: &[u8] = b"\ /// -----BEGIN PRIVATE KEY----- /// /// -----END PRIVATE KEY-----"; /// /// let options = PgConnectOptions::new() /// // Providing a CA certificate with less than VerifyCa is pointless /// .ssl_mode(PgSslMode::VerifyCa) /// .ssl_client_key_from_pem(KEY); /// ``` pub fn ssl_client_key_from_pem(mut self, key: impl AsRef<[u8]>) -> Self { self.ssl_client_key = Some(CertificateInput::Inline(key.as_ref().to_vec())); self } /// Sets PEM encoded trusted SSL Certificate Authorities (CA). /// /// # Example /// /// ```rust /// # use sqlx_postgres::{PgSslMode, PgConnectOptions}; /// let options = PgConnectOptions::new() /// // Providing a CA certificate with less than VerifyCa is pointless /// .ssl_mode(PgSslMode::VerifyCa) /// .ssl_root_cert_from_pem(vec![]); /// ``` pub fn ssl_root_cert_from_pem(mut self, pem_certificate: Vec) -> Self { self.ssl_root_cert = Some(CertificateInput::Inline(pem_certificate)); self } /// Sets the capacity of the connection's statement cache in a number of stored /// distinct statements. Caching is handled using LRU, meaning when the /// amount of queries hits the defined limit, the oldest statement will get /// dropped. /// /// The default cache capacity is 100 statements. pub fn statement_cache_capacity(mut self, capacity: usize) -> Self { self.statement_cache_capacity = capacity; self } /// Sets the application name. Defaults to None /// /// # Example /// /// ```rust /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .application_name("my-app"); /// ``` pub fn application_name(mut self, application_name: &str) -> Self { self.application_name = Some(application_name.to_owned()); self } /// Sets or removes the `extra_float_digits` connection option. /// /// This changes the default precision of floating-point values returned in text mode (when /// not using prepared statements such as calling methods of [`Executor`] directly). /// /// Historically, Postgres would by default round floating-point values to 6 and 15 digits /// for `float4`/`REAL` (`f32`) and `float8`/`DOUBLE` (`f64`), respectively, which would mean /// that the returned value may not be exactly the same as its representation in Postgres. /// /// The nominal range for this value is `-15` to `3`, where negative values for this option /// cause floating-points to be rounded to that many fewer digits than normal (`-1` causes /// `float4` to be rounded to 5 digits instead of six, or 14 instead of 15 for `float8`), /// positive values cause Postgres to emit that many extra digits of precision over default /// (or simply use maximum precision in Postgres 12 and later), /// and 0 means keep the default behavior (or the "old" behavior described above /// as of Postgres 12). /// /// SQLx sets this value to 3 by default, which tells Postgres to return floating-point values /// at their maximum precision in the hope that the parsed value will be identical to its /// counterpart in Postgres. This is also the default in Postgres 12 and later anyway. /// /// However, older versions of Postgres and alternative implementations that talk the Postgres /// protocol may not support this option, or the full range of values. /// /// If you get an error like "unknown option `extra_float_digits`" when connecting, try /// setting this to `None` or consult the manual of your database for the allowed range /// of values. /// /// For more information, see: /// * [Postgres manual, 20.11.2: Client Connection Defaults; Locale and Formatting][20.11.2] /// * [Postgres manual, 8.1.3: Numeric Types; Floating-point Types][8.1.3] /// /// [`Executor`]: crate::executor::Executor /// [20.11.2]: https://www.postgresql.org/docs/current/runtime-config-client.html#RUNTIME-CONFIG-CLIENT-FORMAT /// [8.1.3]: https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-FLOAT /// /// ### Examples /// ```rust /// # use sqlx_postgres::PgConnectOptions; /// /// let mut options = PgConnectOptions::new() /// // for Redshift and Postgres 10 /// .extra_float_digits(2); /// /// let mut options = PgConnectOptions::new() /// // don't send the option at all (Postgres 9 and older) /// .extra_float_digits(None); /// ``` pub fn extra_float_digits(mut self, extra_float_digits: impl Into>) -> Self { self.extra_float_digits = extra_float_digits.into().map(|it| it.to_string().into()); self } /// Set additional startup options for the connection as a list of key-value pairs. /// /// Escapes the options’ backslash and space characters as per /// https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-OPTIONS /// /// # Example /// /// ```rust /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .options([("geqo", "off"), ("statement_timeout", "5min")]); /// ``` pub fn options(mut self, options: I) -> Self where K: Display, V: Display, I: IntoIterator, { // Do this in here so `options_str` is only set if we have an option to insert let options_str = self.options.get_or_insert_with(String::new); for (k, v) in options { if !options_str.is_empty() { options_str.push(' '); } options_str.push_str("-c "); write!(PgOptionsWriteEscaped(options_str), "{k}={v}").ok(); } self } /// We try using a socket if hostname starts with `/` or if socket parameter /// is specified. pub(crate) fn fetch_socket(&self) -> Option { match self.socket { Some(ref socket) => { let full_path = format!("{}/.s.PGSQL.{}", socket.display(), self.port); Some(full_path) } None if self.host.starts_with('/') => { let full_path = format!("{}/.s.PGSQL.{}", self.host, self.port); Some(full_path) } _ => None, } } } impl PgConnectOptions { /// Get the current host. /// /// # Example /// /// ```rust /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .host("127.0.0.1"); /// assert_eq!(options.get_host(), "127.0.0.1"); /// ``` pub fn get_host(&self) -> &str { &self.host } /// Get the server's port. /// /// # Example /// /// ```rust /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .port(6543); /// assert_eq!(options.get_port(), 6543); /// ``` pub fn get_port(&self) -> u16 { self.port } /// Get the socket path. /// /// # Example /// /// ```rust /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .socket("/tmp"); /// assert!(options.get_socket().is_some()); /// ``` pub fn get_socket(&self) -> Option<&PathBuf> { self.socket.as_ref() } /// Get the server's port. /// /// # Example /// /// ```rust /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .username("foo"); /// assert_eq!(options.get_username(), "foo"); /// ``` pub fn get_username(&self) -> &str { &self.username } /// Get the current database name. /// /// # Example /// /// ```rust /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .database("postgres"); /// assert!(options.get_database().is_some()); /// ``` pub fn get_database(&self) -> Option<&str> { self.database.as_deref() } /// Get the SSL mode. /// /// # Example /// /// ```rust /// # use sqlx_postgres::{PgConnectOptions, PgSslMode}; /// let options = PgConnectOptions::new(); /// assert!(matches!(options.get_ssl_mode(), PgSslMode::Prefer)); /// ``` pub fn get_ssl_mode(&self) -> PgSslMode { self.ssl_mode } /// Get the application name. /// /// # Example /// /// ```rust /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .application_name("service"); /// assert!(options.get_application_name().is_some()); /// ``` pub fn get_application_name(&self) -> Option<&str> { self.application_name.as_deref() } /// Get the options. /// /// # Example /// /// ```rust /// # use sqlx_postgres::PgConnectOptions; /// let options = PgConnectOptions::new() /// .options([("foo", "bar")]); /// assert!(options.get_options().is_some()); /// ``` pub fn get_options(&self) -> Option<&str> { self.options.as_deref() } } fn default_host(port: u16) -> String { // try to check for the existence of a unix socket and uses that let socket = format!(".s.PGSQL.{port}"); let candidates = [ "/var/run/postgresql", // Debian "/private/tmp", // OSX (homebrew) "/tmp", // Default ]; for candidate in &candidates { if Path::new(candidate).join(&socket).exists() { return candidate.to_string(); } } // fallback to localhost if no socket was found "localhost".to_owned() } /// Writer that escapes passed-in PostgreSQL options. /// /// Escapes backslashes and spaces with an additional backslash according to /// https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-OPTIONS #[derive(Debug)] struct PgOptionsWriteEscaped<'a>(&'a mut String); impl Write for PgOptionsWriteEscaped<'_> { fn write_str(&mut self, s: &str) -> fmt::Result { let mut span_start = 0; for (span_end, matched) in s.match_indices([' ', '\\']) { write!(self.0, r"{}\{matched}", &s[span_start..span_end])?; span_start = span_end + matched.len(); } // Write the rest of the string after the last match, or all of it if no matches self.0.push_str(&s[span_start..]); Ok(()) } fn write_char(&mut self, ch: char) -> fmt::Result { if matches!(ch, ' ' | '\\') { self.0.push('\\'); } self.0.push(ch); Ok(()) } } #[test] fn test_options_formatting() { let options = PgConnectOptions::new().options([("geqo", "off")]); assert_eq!(options.options, Some("-c geqo=off".to_string())); let options = options.options([("search_path", "sqlx")]); assert_eq!( options.options, Some("-c geqo=off -c search_path=sqlx".to_string()) ); let options = PgConnectOptions::new().options([("geqo", "off"), ("statement_timeout", "5min")]); assert_eq!( options.options, Some("-c geqo=off -c statement_timeout=5min".to_string()) ); // https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-OPTIONS let options = PgConnectOptions::new().options([("application_name", r"/back\slash/ and\ spaces")]); assert_eq!( options.options, Some(r"-c application_name=/back\\slash/\ and\\\ spaces".to_string()) ); let options = PgConnectOptions::new(); assert_eq!(options.options, None); } #[test] fn test_pg_write_escaped() { let mut buf = String::new(); let mut x = PgOptionsWriteEscaped(&mut buf); x.write_str("x").unwrap(); x.write_str("").unwrap(); x.write_char('\\').unwrap(); x.write_str("y \\").unwrap(); x.write_char(' ').unwrap(); x.write_char('z').unwrap(); assert_eq!(buf, r"x\\y\ \\\ z"); } ================================================ FILE: sqlx-postgres/src/options/parse.rs ================================================ use crate::error::Error; use crate::{PgConnectOptions, PgSslMode}; use sqlx_core::percent_encoding::{percent_decode_str, utf8_percent_encode, NON_ALPHANUMERIC}; use sqlx_core::Url; use std::net::IpAddr; use std::str::FromStr; impl PgConnectOptions { pub(crate) fn parse_from_url(url: &Url) -> Result { let mut options = Self::new_without_pgpass(); if let Some(host) = url.host_str() { let host_decoded = percent_decode_str(host); options = match host_decoded.clone().next() { Some(b'/') => options.socket(&*host_decoded.decode_utf8().map_err(Error::config)?), _ => options.host(host), } } if let Some(port) = url.port() { options = options.port(port); } let username = url.username(); if !username.is_empty() { options = options.username( &percent_decode_str(username) .decode_utf8() .map_err(Error::config)?, ); } if let Some(password) = url.password() { options = options.password( &percent_decode_str(password) .decode_utf8() .map_err(Error::config)?, ); } let path = url.path().trim_start_matches('/'); if !path.is_empty() { options = options.database( &percent_decode_str(path) .decode_utf8() .map_err(Error::config)?, ); } for (key, value) in url.query_pairs().into_iter() { match &*key { "sslmode" | "ssl-mode" => { options = options.ssl_mode(value.parse().map_err(Error::config)?); } "sslrootcert" | "ssl-root-cert" | "ssl-ca" => { options = options.ssl_root_cert(&*value); } "sslcert" | "ssl-cert" => options = options.ssl_client_cert(&*value), "sslkey" | "ssl-key" => options = options.ssl_client_key(&*value), "statement-cache-capacity" => { options = options.statement_cache_capacity(value.parse().map_err(Error::config)?); } "host" => { if value.starts_with('/') { options = options.socket(&*value); } else { options = options.host(&value); } } "hostaddr" => { value.parse::().map_err(Error::config)?; options = options.host(&value) } "port" => options = options.port(value.parse().map_err(Error::config)?), "dbname" => options = options.database(&value), "user" => options = options.username(&value), "password" => options = options.password(&value), "application_name" => options = options.application_name(&value), "options" => { if let Some(options) = options.options.as_mut() { options.push(' '); options.push_str(&value); } else { options.options = Some(value.to_string()); } } k if k.starts_with("options[") => { if let Some(key) = k.strip_prefix("options[").unwrap().strip_suffix(']') { options = options.options([(key, &*value)]); } } _ => tracing::warn!(%key, %value, "ignoring unrecognized connect parameter"), } } let options = options.apply_pgpass(); Ok(options) } pub(crate) fn build_url(&self) -> Url { let host = match &self.socket { Some(socket) => { utf8_percent_encode(&socket.to_string_lossy(), NON_ALPHANUMERIC).to_string() } None => self.host.to_owned(), }; let mut url = Url::parse(&format!( "postgres://{}@{}:{}", self.username, host, self.port )) .expect("BUG: generated un-parseable URL"); if let Some(password) = &self.password { let password = utf8_percent_encode(password, NON_ALPHANUMERIC).to_string(); let _ = url.set_password(Some(&password)); } if let Some(database) = &self.database { url.set_path(database); } let ssl_mode = match self.ssl_mode { PgSslMode::Allow => "allow", PgSslMode::Disable => "disable", PgSslMode::Prefer => "prefer", PgSslMode::Require => "require", PgSslMode::VerifyCa => "verify-ca", PgSslMode::VerifyFull => "verify-full", }; url.query_pairs_mut().append_pair("sslmode", ssl_mode); if let Some(ssl_root_cert) = &self.ssl_root_cert { url.query_pairs_mut() .append_pair("sslrootcert", &ssl_root_cert.to_string()); } if let Some(ssl_client_cert) = &self.ssl_client_cert { url.query_pairs_mut() .append_pair("sslcert", &ssl_client_cert.to_string()); } if let Some(ssl_client_key) = &self.ssl_client_key { url.query_pairs_mut() .append_pair("sslkey", &ssl_client_key.to_string()); } url.query_pairs_mut().append_pair( "statement-cache-capacity", &self.statement_cache_capacity.to_string(), ); url } } impl FromStr for PgConnectOptions { type Err = Error; fn from_str(s: &str) -> Result { let url: Url = s.parse().map_err(Error::config)?; Self::parse_from_url(&url) } } #[test] fn it_parses_socket_correctly_from_parameter() { let url = "postgres:///?host=/var/run/postgres/"; let opts = PgConnectOptions::from_str(url).unwrap(); assert_eq!(Some("/var/run/postgres/".into()), opts.socket); } #[test] fn it_parses_host_correctly_from_parameter() { let url = "postgres:///?host=google.database.com"; let opts = PgConnectOptions::from_str(url).unwrap(); assert_eq!(None, opts.socket); assert_eq!("google.database.com", &opts.host); } #[test] fn it_parses_hostaddr_correctly_from_parameter() { let url = "postgres:///?hostaddr=8.8.8.8"; let opts = PgConnectOptions::from_str(url).unwrap(); assert_eq!(None, opts.socket); assert_eq!("8.8.8.8", &opts.host); } #[test] fn it_parses_port_correctly_from_parameter() { let url = "postgres:///?port=1234"; let opts = PgConnectOptions::from_str(url).unwrap(); assert_eq!(None, opts.socket); assert_eq!(1234, opts.port); } #[test] fn it_parses_dbname_correctly_from_parameter() { let url = "postgres:///?dbname=some_db"; let opts = PgConnectOptions::from_str(url).unwrap(); assert_eq!(None, opts.socket); assert_eq!(Some("some_db"), opts.database.as_deref()); } #[test] fn it_parses_user_correctly_from_parameter() { let url = "postgres:///?user=some_user"; let opts = PgConnectOptions::from_str(url).unwrap(); assert_eq!(None, opts.socket); assert_eq!("some_user", opts.username); } #[test] fn it_parses_password_correctly_from_parameter() { let url = "postgres:///?password=some_pass"; let opts = PgConnectOptions::from_str(url).unwrap(); assert_eq!(None, opts.socket); assert_eq!(Some("some_pass"), opts.password.as_deref()); } #[test] fn it_parses_application_name_correctly_from_parameter() { let url = "postgres:///?application_name=some_name"; let opts = PgConnectOptions::from_str(url).unwrap(); assert_eq!(Some("some_name"), opts.application_name.as_deref()); } #[test] fn it_parses_username_with_at_sign_correctly() { let url = "postgres://user@hostname:password@hostname:5432/database"; let opts = PgConnectOptions::from_str(url).unwrap(); assert_eq!("user@hostname", &opts.username); } #[test] fn it_parses_password_with_non_ascii_chars_correctly() { let url = "postgres://username:p@ssw0rd@hostname:5432/database"; let opts = PgConnectOptions::from_str(url).unwrap(); assert_eq!(Some("p@ssw0rd".into()), opts.password); } #[test] fn it_parses_socket_correctly_percent_encoded() { let url = "postgres://%2Fvar%2Flib%2Fpostgres/database"; let opts = PgConnectOptions::from_str(url).unwrap(); assert_eq!(Some("/var/lib/postgres/".into()), opts.socket); } #[test] fn it_parses_socket_correctly_with_username_percent_encoded() { let url = "postgres://some_user@%2Fvar%2Flib%2Fpostgres/database"; let opts = PgConnectOptions::from_str(url).unwrap(); assert_eq!("some_user", opts.username); assert_eq!(Some("/var/lib/postgres/".into()), opts.socket); assert_eq!(Some("database"), opts.database.as_deref()); } #[test] fn it_parses_libpq_options_correctly() { let url = "postgres:///?options=-c%20synchronous_commit%3Doff%20--search_path%3Dpostgres"; let opts = PgConnectOptions::from_str(url).unwrap(); assert_eq!( Some("-c synchronous_commit=off --search_path=postgres".into()), opts.options ); } #[test] fn it_parses_sqlx_options_correctly() { let url = "postgres:///?options[synchronous_commit]=off&options[search_path]=postgres"; let opts = PgConnectOptions::from_str(url).unwrap(); assert_eq!( Some("-c synchronous_commit=off -c search_path=postgres".into()), opts.options ); } #[test] fn it_returns_the_parsed_url_when_socket() { let url = "postgres://username@%2Fvar%2Flib%2Fpostgres/database"; let opts = PgConnectOptions::from_str(url).unwrap(); let mut expected_url = Url::parse(url).unwrap(); // PgConnectOptions defaults let query_string = "sslmode=prefer&statement-cache-capacity=100"; let port = 5432; expected_url.set_query(Some(query_string)); let _ = expected_url.set_port(Some(port)); assert_eq!(expected_url, opts.build_url()); } #[test] fn it_returns_the_parsed_url_when_host() { let url = "postgres://username:p@ssw0rd@hostname:5432/database"; let opts = PgConnectOptions::from_str(url).unwrap(); let mut expected_url = Url::parse(url).unwrap(); // PgConnectOptions defaults let query_string = "sslmode=prefer&statement-cache-capacity=100"; expected_url.set_query(Some(query_string)); assert_eq!(expected_url, opts.build_url()); } #[test] fn built_url_can_be_parsed() { let url = "postgres://username:p@ssw0rd@hostname:5432/database"; let opts = PgConnectOptions::from_str(url).unwrap(); let parsed = PgConnectOptions::from_str(opts.build_url().as_ref()); assert!(parsed.is_ok()); } ================================================ FILE: sqlx-postgres/src/options/pgpass.rs ================================================ use std::borrow::Cow; use std::env::var_os; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::PathBuf; /// try to load a password from the various pgpass file locations pub fn load_password( host: &str, port: u16, username: &str, database: Option<&str>, ) -> Option { let custom_file = var_os("PGPASSFILE"); if let Some(file) = custom_file { if let Some(password) = load_password_from_file(PathBuf::from(file), host, port, username, database) { return Some(password); } } #[cfg(not(target_os = "windows"))] // home_dir fixed in 1.85 (rust-lang/rust#132515) and un-deprecated in 1.87 (rust-lang/rust#137327) #[allow(deprecated)] let default_file = std::env::home_dir().map(|path| path.join(".pgpass")); #[cfg(target_os = "windows")] let default_file = { use etcetera::BaseStrategy; etcetera::base_strategy::Windows::new() .ok() .map(|basedirs| basedirs.data_dir().join("postgres").join("pgpass.conf")) }; load_password_from_file(default_file?, host, port, username, database) } /// try to extract a password from a pgpass file fn load_password_from_file( path: PathBuf, host: &str, port: u16, username: &str, database: Option<&str>, ) -> Option { let file = File::open(&path) .map_err(|e| { match e.kind() { std::io::ErrorKind::NotFound => { tracing::debug!( path = %path.display(), "`.pgpass` file not found", ); } _ => { tracing::warn!( path = %path.display(), "Failed to open `.pgpass` file: {e:?}", ); } }; }) .ok()?; #[cfg(target_os = "linux")] { use std::os::unix::fs::PermissionsExt; // check file permissions on linux let metadata = file.metadata().ok()?; let permissions = metadata.permissions(); let mode = permissions.mode(); if mode & 0o77 != 0 { tracing::warn!( path = %path.display(), permissions = format!("{mode:o}"), "Ignoring path. Permissions are not strict enough", ); return None; } } let reader = BufReader::new(file); load_password_from_reader(reader, host, port, username, database) } fn load_password_from_reader( mut reader: impl BufRead, host: &str, port: u16, username: &str, database: Option<&str>, ) -> Option { let mut line = String::new(); // https://stackoverflow.com/a/55041833 fn trim_newline(s: &mut String) { if s.ends_with('\n') { s.pop(); if s.ends_with('\r') { s.pop(); } } } while let Ok(n) = reader.read_line(&mut line) { if n == 0 { break; } if line.starts_with('#') { // comment, do nothing } else { // try to load password from line trim_newline(&mut line); if let Some(password) = load_password_from_line(&line, host, port, username, database) { return Some(password); } } line.clear(); } None } /// try to check all fields & extract the password fn load_password_from_line( mut line: &str, host: &str, port: u16, username: &str, database: Option<&str>, ) -> Option { let whole_line = line; // Pgpass line ordering: hostname, port, database, username, password // See: https://www.postgresql.org/docs/9.3/libpq-pgpass.html match line.trim_start().chars().next() { None | Some('#') => None, _ => { matches_next_field(whole_line, &mut line, host)?; matches_next_field(whole_line, &mut line, &port.to_string())?; matches_next_field(whole_line, &mut line, database.unwrap_or_default())?; matches_next_field(whole_line, &mut line, username)?; Some(line.to_owned()) } } } /// check if the next field matches the provided value fn matches_next_field(whole_line: &str, line: &mut &str, value: &str) -> Option<()> { let field = find_next_field(line); match field { Some(field) => { if field == "*" || field == value { Some(()) } else { None } } None => { tracing::warn!(line = whole_line, "Malformed line in pgpass file"); None } } } /// extract the next value from a line in a pgpass file /// /// `line` will get updated to point behind the field and delimiter fn find_next_field<'a>(line: &mut &'a str) -> Option> { let mut escaping = false; let mut escaped_string = None; let mut last_added = 0; let char_indices = line.char_indices(); for (idx, c) in char_indices { if c == ':' && !escaping { let (field, rest) = line.split_at(idx); *line = &rest[1..]; if let Some(mut escaped_string) = escaped_string { escaped_string += &field[last_added..]; return Some(Cow::Owned(escaped_string)); } else { return Some(Cow::Borrowed(field)); } } else if c == '\\' { let s = escaped_string.get_or_insert_with(String::new); if escaping { s.push('\\'); } else { *s += &line[last_added..idx]; } escaping = !escaping; last_added = idx + 1; } else { escaping = false; } } None } #[cfg(test)] mod tests { use super::{find_next_field, load_password_from_line, load_password_from_reader}; use std::borrow::Cow; #[test] fn test_find_next_field() { fn test_case<'a>(mut input: &'a str, result: Option>, rest: &str) { assert_eq!(find_next_field(&mut input), result); assert_eq!(input, rest); } // normal field test_case("foo:bar:baz", Some(Cow::Borrowed("foo")), "bar:baz"); // \ escaped test_case( "foo\\\\:bar:baz", Some(Cow::Owned("foo\\".to_owned())), "bar:baz", ); // : escaped test_case( "foo\\::bar:baz", Some(Cow::Owned("foo:".to_owned())), "bar:baz", ); // unnecessary escape test_case( "foo\\a:bar:baz", Some(Cow::Owned("fooa".to_owned())), "bar:baz", ); // other text after escape test_case( "foo\\\\a:bar:baz", Some(Cow::Owned("foo\\a".to_owned())), "bar:baz", ); // double escape test_case( "foo\\\\\\\\a:bar:baz", Some(Cow::Owned("foo\\\\a".to_owned())), "bar:baz", ); // utf8 support test_case("🦀:bar:baz", Some(Cow::Borrowed("🦀")), "bar:baz"); // missing delimiter (eof) test_case("foo", None, "foo"); // missing delimiter after escape test_case("foo\\:", None, "foo\\:"); // missing delimiter after unused trailing escape test_case("foo\\", None, "foo\\"); } #[test] fn test_load_password_from_line() { // normal assert_eq!( load_password_from_line( "localhost:5432:bar:foo:baz", "localhost", 5432, "foo", Some("bar") ), Some("baz".to_owned()) ); // wildcard assert_eq!( load_password_from_line("*:5432:bar:foo:baz", "localhost", 5432, "foo", Some("bar")), Some("baz".to_owned()) ); // accept wildcard with missing db assert_eq!( load_password_from_line("localhost:5432:*:foo:baz", "localhost", 5432, "foo", None), Some("baz".to_owned()) ); // doesn't match assert_eq!( load_password_from_line( "thishost:5432:bar:foo:baz", "thathost", 5432, "foo", Some("bar") ), None ); // malformed entry assert_eq!( load_password_from_line( "localhost:5432:bar:foo", "localhost", 5432, "foo", Some("bar") ), None ); } #[test] fn test_load_password_from_reader() { let file = b"\ localhost:5432:bar:foo:baz\n\ # mixed line endings (also a comment!)\n\ *:5432:bar:foo:baz\r\n\ # trailing space, comment with CRLF! \r\n\ thishost:5432:bar:foo:baz \n\ # malformed line \n\ thathost:5432:foobar:foo\n\ # missing trailing newline\n\ localhost:5432:*:foo:baz "; // normal assert_eq!( load_password_from_reader(&mut &file[..], "localhost", 5432, "foo", Some("bar")), Some("baz".to_owned()) ); // wildcard assert_eq!( load_password_from_reader(&mut &file[..], "localhost", 5432, "foo", Some("foobar")), Some("baz".to_owned()) ); // accept wildcard with missing db assert_eq!( load_password_from_reader(&mut &file[..], "localhost", 5432, "foo", None), Some("baz".to_owned()) ); // doesn't match assert_eq!( load_password_from_reader(&mut &file[..], "thathost", 5432, "foo", Some("foobar")), None ); // malformed entry assert_eq!( load_password_from_reader(&mut &file[..], "thathost", 5432, "foo", Some("foobar")), None ); } } ================================================ FILE: sqlx-postgres/src/options/ssl_mode.rs ================================================ use crate::error::Error; use std::str::FromStr; /// Options for controlling the level of protection provided for PostgreSQL SSL connections. /// /// It is used by the [`ssl_mode`](super::PgConnectOptions::ssl_mode) method. #[derive(Debug, Clone, Copy, Default)] pub enum PgSslMode { /// Only try a non-SSL connection. Disable, /// First try a non-SSL connection; if that fails, try an SSL connection. Allow, /// First try an SSL connection; if that fails, try a non-SSL connection. /// /// This is the default if no other mode is specified. #[default] Prefer, /// Only try an SSL connection. If a root CA file is present, verify the connection /// in the same way as if `VerifyCa` was specified. Require, /// Only try an SSL connection, and verify that the server certificate is issued by a /// trusted certificate authority (CA). VerifyCa, /// Only try an SSL connection; verify that the server certificate is issued by a trusted /// CA and that the requested server host name matches that in the certificate. VerifyFull, } impl FromStr for PgSslMode { type Err = Error; fn from_str(s: &str) -> Result { Ok(match &*s.to_ascii_lowercase() { "disable" => PgSslMode::Disable, "allow" => PgSslMode::Allow, "prefer" => PgSslMode::Prefer, "require" => PgSslMode::Require, "verify-ca" => PgSslMode::VerifyCa, "verify-full" => PgSslMode::VerifyFull, _ => { return Err(Error::Configuration( format!("unknown value {s:?} for `ssl_mode`").into(), )); } }) } } ================================================ FILE: sqlx-postgres/src/query_result.rs ================================================ use std::iter::{Extend, IntoIterator}; #[derive(Debug, Default)] pub struct PgQueryResult { pub(super) rows_affected: u64, } impl PgQueryResult { pub fn rows_affected(&self) -> u64 { self.rows_affected } } impl Extend for PgQueryResult { fn extend>(&mut self, iter: T) { for elem in iter { self.rows_affected += elem.rows_affected; } } } #[cfg(feature = "any")] impl From for sqlx_core::any::AnyQueryResult { fn from(done: PgQueryResult) -> Self { sqlx_core::any::AnyQueryResult { rows_affected: done.rows_affected, last_insert_id: None, } } } ================================================ FILE: sqlx-postgres/src/row.rs ================================================ use crate::column::ColumnIndex; use crate::error::Error; use crate::message::DataRow; use crate::statement::PgStatementMetadata; use crate::value::PgValueFormat; use crate::{PgColumn, PgValueRef, Postgres}; use sqlx_core::row::debug_row; pub(crate) use sqlx_core::row::Row; use std::sync::Arc; /// Implementation of [`Row`] for PostgreSQL. pub struct PgRow { pub(crate) data: DataRow, pub(crate) format: PgValueFormat, pub(crate) metadata: Arc, } impl Row for PgRow { type Database = Postgres; fn columns(&self) -> &[PgColumn] { &self.metadata.columns } fn try_get_raw(&self, index: I) -> Result, Error> where I: ColumnIndex, { let index = index.index(self)?; let column = &self.metadata.columns[index]; let value = self.data.get(index); Ok(PgValueRef { format: self.format, row: Some(&self.data.storage), type_info: column.type_info.clone(), value, }) } } impl ColumnIndex for &'_ str { fn index(&self, row: &PgRow) -> Result { row.metadata .column_names .get(*self) .ok_or_else(|| Error::ColumnNotFound((*self).into())) .copied() } } impl std::fmt::Debug for PgRow { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { debug_row(self, f) } } ================================================ FILE: sqlx-postgres/src/statement.rs ================================================ use super::{PgColumn, PgTypeInfo}; use crate::column::ColumnIndex; use crate::error::Error; use crate::ext::ustr::UStr; use crate::{PgArguments, Postgres}; use std::sync::Arc; use sqlx_core::sql_str::SqlStr; pub(crate) use sqlx_core::statement::Statement; use sqlx_core::{Either, HashMap}; #[derive(Debug, Clone)] pub struct PgStatement { pub(crate) sql: SqlStr, pub(crate) metadata: Arc, } #[derive(Debug, Default)] pub(crate) struct PgStatementMetadata { pub(crate) columns: Vec, // This `Arc` is not redundant; it's used to avoid deep-copying this map for the `Any` backend. // See `sqlx-postgres/src/any.rs` pub(crate) column_names: Arc>, pub(crate) parameters: Vec, } impl Statement for PgStatement { type Database = Postgres; fn into_sql(self) -> SqlStr { self.sql } fn sql(&self) -> &SqlStr { &self.sql } fn parameters(&self) -> Option> { Some(Either::Left(&self.metadata.parameters)) } fn columns(&self) -> &[PgColumn] { &self.metadata.columns } impl_statement_query!(PgArguments); } impl ColumnIndex for &'_ str { fn index(&self, statement: &PgStatement) -> Result { statement .metadata .column_names .get(*self) .ok_or_else(|| Error::ColumnNotFound((*self).into())) .copied() } } // #[cfg(feature = "any")] // impl<'q> From> for crate::any::AnyStatement<'q> { // #[inline] // fn from(statement: PgStatement<'q>) -> Self { // crate::any::AnyStatement::<'q> { // columns: statement // .metadata // .columns // .iter() // .map(|col| col.clone().into()) // .collect(), // column_names: statement.metadata.column_names.clone(), // parameters: Some(Either::Left( // statement // .metadata // .parameters // .iter() // .map(|ty| ty.clone().into()) // .collect(), // )), // sql: statement.sql, // } // } // } ================================================ FILE: sqlx-postgres/src/testing/mod.rs ================================================ use std::future::Future; use std::ops::Deref; use std::str::FromStr; use std::sync::OnceLock; use std::time::Duration; use sqlx_core::connection::Connection; use sqlx_core::query_builder::QueryBuilder; use sqlx_core::query_scalar::query_scalar; use sqlx_core::sql_str::AssertSqlSafe; use crate::error::Error; use crate::executor::Executor; use crate::pool::{Pool, PoolOptions}; use crate::query::query; use crate::{PgConnectOptions, PgConnection, Postgres}; pub(crate) use sqlx_core::testing::*; // Using a blocking `OnceLock` here because the critical sections are short. static MASTER_POOL: OnceLock> = OnceLock::new(); // Automatically delete any databases created before the start of the test binary. impl TestSupport for Postgres { fn test_context( args: &TestArgs, ) -> impl Future, Error>> + Send + '_ { test_context(args) } async fn cleanup_test(db_name: &str) -> Result<(), Error> { let mut conn = MASTER_POOL .get() .expect("cleanup_test() invoked outside `#[sqlx::test]`") .acquire() .await?; do_cleanup(&mut conn, db_name).await } async fn cleanup_test_dbs() -> Result, Error> { let url = dotenvy::var("DATABASE_URL").expect("DATABASE_URL must be set"); let mut conn = PgConnection::connect(&url).await?; let delete_db_names: Vec = query_scalar("select db_name from _sqlx_test.databases") .fetch_all(&mut conn) .await?; if delete_db_names.is_empty() { return Ok(None); } let mut deleted_db_names = Vec::with_capacity(delete_db_names.len()); let mut builder = QueryBuilder::new("drop database if exists "); for db_name in &delete_db_names { builder.push(db_name); match builder.build().execute(&mut conn).await { Ok(_deleted) => { deleted_db_names.push(db_name); } // Assume a database error just means the DB is still in use. Err(Error::Database(dbe)) => { eprintln!("could not clean test database {db_name:?}: {dbe}") } // Bubble up other errors Err(e) => return Err(e), } builder.reset(); } query("delete from _sqlx_test.databases where db_name = any($1::text[])") .bind(&deleted_db_names) .execute(&mut conn) .await?; let _ = conn.close().await; Ok(Some(delete_db_names.len())) } async fn snapshot(_conn: &mut Self::Connection) -> Result, Error> { // TODO: I want to get the testing feature out the door so this will have to wait, // but I'm keeping the code around for now because I plan to come back to it. todo!() } } async fn test_context(args: &TestArgs) -> Result, Error> { let url = dotenvy::var("DATABASE_URL").expect("DATABASE_URL must be set"); let master_opts = PgConnectOptions::from_str(&url).expect("failed to parse DATABASE_URL"); let pool = PoolOptions::new() // Postgres' normal connection limit is 100 plus 3 superuser connections // We don't want to use the whole cap and there may be fuzziness here due to // concurrently running tests anyway. .max_connections(20) // Immediately close master connections. Tokio's I/O streams don't like hopping runtimes. .after_release(|_conn, _| Box::pin(async move { Ok(false) })) .connect_lazy_with(master_opts); let master_pool = match once_lock_try_insert_polyfill(&MASTER_POOL, pool) { Ok(inserted) => inserted, Err((existing, pool)) => { // Sanity checks. assert_eq!( existing.connect_options().host, pool.connect_options().host, "DATABASE_URL changed at runtime, host differs" ); assert_eq!( existing.connect_options().database, pool.connect_options().database, "DATABASE_URL changed at runtime, database differs" ); existing } }; let mut conn = master_pool.acquire().await?; // language=PostgreSQL conn.execute( // Explicit lock avoids this latent bug: https://stackoverflow.com/a/29908840 // I couldn't find a bug on the mailing list for `CREATE SCHEMA` specifically, // but a clearly related bug with `CREATE TABLE` has been known since 2007: // https://www.postgresql.org/message-id/200710222037.l9MKbCJZ098744%40wwwmaster.postgresql.org // magic constant 8318549251334697844 is just 8 ascii bytes 'sqlxtest'. r#" select pg_advisory_xact_lock(8318549251334697844); create schema if not exists _sqlx_test; create table if not exists _sqlx_test.databases ( db_name text primary key, test_path text not null, created_at timestamptz not null default now() ); create index if not exists databases_created_at on _sqlx_test.databases(created_at); create sequence if not exists _sqlx_test.database_ids; "#, ) .await?; let db_name = Postgres::db_name(args); do_cleanup(&mut conn, &db_name).await?; query( r#" insert into _sqlx_test.databases(db_name, test_path) values ($1, $2) "#, ) .bind(&db_name) .bind(args.test_path) .execute(&mut *conn) .await?; let create_command = format!("create database {db_name:?}"); debug_assert!(create_command.starts_with("create database \"")); conn.execute(AssertSqlSafe(create_command)).await?; Ok(TestContext { pool_opts: PoolOptions::new() // Don't allow a single test to take all the connections. // Most tests shouldn't require more than 5 connections concurrently, // or else they're likely doing too much in one test. .max_connections(5) // Close connections ASAP if left in the idle queue. .idle_timeout(Some(Duration::from_secs(1))) .parent(master_pool.clone()), connect_opts: master_pool .connect_options() .deref() .clone() .database(&db_name), db_name, }) } async fn do_cleanup(conn: &mut PgConnection, db_name: &str) -> Result<(), Error> { let delete_db_command = format!("drop database if exists {db_name:?};"); conn.execute(AssertSqlSafe(delete_db_command)).await?; query("delete from _sqlx_test.databases where db_name = $1::text") .bind(db_name) .execute(&mut *conn) .await?; Ok(()) } fn once_lock_try_insert_polyfill(this: &OnceLock, value: T) -> Result<&T, (&T, T)> { let mut value = Some(value); let res = this.get_or_init(|| value.take().unwrap()); match value { None => Ok(res), Some(value) => Err((res, value)), } } ================================================ FILE: sqlx-postgres/src/transaction.rs ================================================ use sqlx_core::database::Database; use sqlx_core::sql_str::SqlStr; use crate::error::Error; use crate::executor::Executor; use crate::{PgConnection, Postgres}; pub(crate) use sqlx_core::transaction::*; /// Implementation of [`TransactionManager`] for PostgreSQL. pub struct PgTransactionManager; impl TransactionManager for PgTransactionManager { type Database = Postgres; async fn begin(conn: &mut PgConnection, statement: Option) -> Result<(), Error> { let depth = conn.inner.transaction_depth; let statement = match statement { // custom `BEGIN` statements are not allowed if we're already in // a transaction (we need to issue a `SAVEPOINT` instead) Some(_) if depth > 0 => return Err(Error::InvalidSavePointStatement), Some(statement) => statement, None => begin_ansi_transaction_sql(depth), }; let rollback = Rollback::new(conn); rollback.conn.queue_simple_query(statement.as_str())?; rollback.conn.wait_until_ready().await?; if !rollback.conn.in_transaction() { return Err(Error::BeginFailed); } rollback.conn.inner.transaction_depth += 1; rollback.defuse(); Ok(()) } async fn commit(conn: &mut PgConnection) -> Result<(), Error> { if conn.inner.transaction_depth > 0 { conn.execute(commit_ansi_transaction_sql(conn.inner.transaction_depth)) .await?; conn.inner.transaction_depth -= 1; } Ok(()) } async fn rollback(conn: &mut PgConnection) -> Result<(), Error> { if conn.inner.transaction_depth > 0 { conn.execute(rollback_ansi_transaction_sql(conn.inner.transaction_depth)) .await?; conn.inner.transaction_depth -= 1; } Ok(()) } fn start_rollback(conn: &mut PgConnection) { if conn.inner.transaction_depth > 0 { conn.queue_simple_query( rollback_ansi_transaction_sql(conn.inner.transaction_depth).as_str(), ) .expect("BUG: Rollback query somehow too large for protocol"); conn.inner.transaction_depth -= 1; } } fn get_transaction_depth(conn: &::Connection) -> usize { conn.inner.transaction_depth } } struct Rollback<'c> { conn: &'c mut PgConnection, defuse: bool, } impl Drop for Rollback<'_> { fn drop(&mut self) { if !self.defuse { PgTransactionManager::start_rollback(self.conn) } } } impl<'c> Rollback<'c> { fn new(conn: &'c mut PgConnection) -> Self { Self { conn, defuse: false, } } fn defuse(mut self) { self.defuse = true; } } ================================================ FILE: sqlx-postgres/src/type_checking.rs ================================================ use crate::Postgres; // The paths used below will also be emitted by the macros so they have to match the final facade. #[allow(unused_imports, dead_code)] mod sqlx { pub use crate as postgres; pub use sqlx_core::*; } impl_type_checking!( Postgres { (), bool, String | &str, i8, i16, i32, i64, f32, f64, Vec | &[u8], sqlx::postgres::types::Oid, sqlx::postgres::types::PgInterval, sqlx::postgres::types::PgMoney, sqlx::postgres::types::PgLTree, sqlx::postgres::types::PgLQuery, sqlx::postgres::types::PgCube, sqlx::postgres::types::PgPoint, sqlx::postgres::types::PgLine, sqlx::postgres::types::PgLSeg, sqlx::postgres::types::PgBox, sqlx::postgres::types::PgPath, sqlx::postgres::types::PgPolygon, sqlx::postgres::types::PgCircle, #[cfg(feature = "uuid")] sqlx::types::Uuid, #[cfg(feature = "ipnetwork")] sqlx::types::ipnetwork::IpNetwork, #[cfg(feature = "ipnet")] sqlx::types::ipnet::IpNet, #[cfg(feature = "mac_address")] sqlx::types::mac_address::MacAddress, #[cfg(feature = "json")] sqlx::types::JsonValue, #[cfg(feature = "bit-vec")] sqlx::types::BitVec, sqlx::postgres::types::PgHstore, // Arrays Vec | &[bool], Vec | &[String], Vec> | &[Vec], Vec | &[i8], Vec | &[i16], Vec | &[i32], Vec | &[i64], Vec | &[f32], Vec | &[f64], Vec | &[sqlx::postgres::types::Oid], Vec | &[sqlx::postgres::types::PgMoney], Vec | &[sqlx::postgres::types::PgInterval], #[cfg(feature = "uuid")] Vec | &[sqlx::types::Uuid], #[cfg(feature = "ipnetwork")] Vec | &[sqlx::types::ipnetwork::IpNetwork], #[cfg(feature = "ipnet")] Vec | &[sqlx::types::ipnet::IpNet], #[cfg(feature = "mac_address")] Vec | &[sqlx::types::mac_address::MacAddress], #[cfg(feature = "json")] Vec | &[sqlx::types::JsonValue], Vec | &[sqlx::postgres::types::PgHstore], // Ranges sqlx::postgres::types::PgRange, sqlx::postgres::types::PgRange, // Range arrays Vec> | &[sqlx::postgres::types::PgRange], Vec> | &[sqlx::postgres::types::PgRange], }, ParamChecking::Strong, feature-types: info => info.__type_feature_gate(), // The expansion of the macro automatically applies the correct feature name // and checks `[macros.preferred-crates]` datetime-types: { chrono: { // Scalar types sqlx::types::chrono::NaiveTime, sqlx::types::chrono::NaiveDate, sqlx::types::chrono::NaiveDateTime, sqlx::types::chrono::DateTime | sqlx::types::chrono::DateTime<_>, sqlx::postgres::types::PgTimeTz, // Array types Vec | &[sqlx::types::chrono::NaiveTime], Vec | &[sqlx::types::chrono::NaiveDate], Vec | &[sqlx::types::chrono::NaiveDateTime], Vec> | &[sqlx::types::chrono::DateTime<_>], // Range types sqlx::postgres::types::PgRange, sqlx::postgres::types::PgRange, sqlx::postgres::types::PgRange> | sqlx::postgres::types::PgRange>, // Arrays of ranges Vec> | &[sqlx::postgres::types::PgRange], Vec> | &[sqlx::postgres::types::PgRange], Vec>> | &[sqlx::postgres::types::PgRange>], }, time: { // Scalar types sqlx::types::time::Time, sqlx::types::time::Date, sqlx::types::time::PrimitiveDateTime, sqlx::types::time::OffsetDateTime, sqlx::postgres::types::PgTimeTz, // Array types Vec | &[sqlx::types::time::Time], Vec | &[sqlx::types::time::Date], Vec | &[sqlx::types::time::PrimitiveDateTime], Vec | &[sqlx::types::time::OffsetDateTime], // Range types sqlx::postgres::types::PgRange, sqlx::postgres::types::PgRange, sqlx::postgres::types::PgRange, // Arrays of ranges Vec> | &[sqlx::postgres::types::PgRange], Vec> | &[sqlx::postgres::types::PgRange], Vec> | &[sqlx::postgres::types::PgRange], }, }, numeric-types: { bigdecimal: { sqlx::types::BigDecimal, Vec | &[sqlx::types::BigDecimal], sqlx::postgres::types::PgRange, Vec> | &[sqlx::postgres::types::PgRange], }, rust_decimal: { sqlx::types::Decimal, Vec | &[sqlx::types::Decimal], sqlx::postgres::types::PgRange, Vec> | &[sqlx::postgres::types::PgRange], }, }, ); ================================================ FILE: sqlx-postgres/src/type_info.rs ================================================ #![allow(dead_code)] use std::borrow::Cow; use std::fmt::{self, Display, Formatter}; use std::ops::Deref; use std::sync::Arc; use crate::ext::ustr::UStr; use crate::types::Oid; pub(crate) use sqlx_core::type_info::TypeInfo; /// Type information for a PostgreSQL type. /// /// ### Note: Implementation of `==` ([`PartialEq::eq()`]) /// Because `==` on [`TypeInfo`]s has been used throughout the SQLx API as a synonym for type compatibility, /// e.g. in the default impl of [`Type::compatible()`][sqlx_core::types::Type::compatible], /// some concessions have been made in the implementation. /// /// When comparing two `PgTypeInfo`s using the `==` operator ([`PartialEq::eq()`]), /// if one was constructed with [`Self::with_oid()`] and the other with [`Self::with_name()`] or /// [`Self::array_of()`], `==` will return `true`: /// /// ``` /// # use sqlx::postgres::{types::Oid, PgTypeInfo}; /// // Potentially surprising result, this assert will pass: /// assert_eq!(PgTypeInfo::with_oid(Oid(1)), PgTypeInfo::with_name("definitely_not_real")); /// ``` /// /// Since it is not possible in this case to prove the types are _not_ compatible (because /// both `PgTypeInfo`s need to be resolved by an active connection to know for sure) /// and type compatibility is mainly done as a sanity check anyway, /// it was deemed acceptable to fudge equality in this very specific case. /// /// This also applies when querying with the text protocol (not using prepared statements, /// e.g. [`sqlx::raw_sql()`][sqlx_core::raw_sql::raw_sql]), as the connection will be unable /// to look up the type info like it normally does when preparing a statement: it won't know /// what the OIDs of the output columns will be until it's in the middle of reading the result, /// and by that time it's too late. /// /// To compare types for exact equality, use [`Self::type_eq()`] instead. #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] pub struct PgTypeInfo(pub(crate) PgType); impl Deref for PgTypeInfo { type Target = PgType; fn deref(&self) -> &Self::Target { &self.0 } } #[derive(Debug, Clone)] #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] #[repr(u32)] pub enum PgType { Bool, Bytea, Char, Name, Int8, Int2, Int4, Text, Oid, Json, JsonArray, Point, Lseg, Path, Box, Polygon, Line, LineArray, Cidr, CidrArray, Float4, Float8, Unknown, Circle, CircleArray, Macaddr8, Macaddr8Array, Macaddr, Inet, BoolArray, ByteaArray, CharArray, NameArray, Int2Array, Int4Array, TextArray, BpcharArray, VarcharArray, Int8Array, PointArray, LsegArray, PathArray, BoxArray, Float4Array, Float8Array, PolygonArray, OidArray, MacaddrArray, InetArray, Bpchar, Varchar, Date, Time, Timestamp, TimestampArray, DateArray, TimeArray, Timestamptz, TimestamptzArray, Interval, IntervalArray, NumericArray, Timetz, TimetzArray, Bit, BitArray, Varbit, VarbitArray, Numeric, Record, RecordArray, Uuid, UuidArray, Jsonb, JsonbArray, Int4Range, Int4RangeArray, NumRange, NumRangeArray, TsRange, TsRangeArray, TstzRange, TstzRangeArray, DateRange, DateRangeArray, Int8Range, Int8RangeArray, Jsonpath, JsonpathArray, Money, MoneyArray, // https://www.postgresql.org/docs/9.3/datatype-pseudo.html Void, // A realized user-defined type. When a connection sees a DeclareXX variant it resolves // into this one before passing it along to `accepts` or inside of `Value` objects. Custom(Arc), // From [`PgTypeInfo::with_name`] DeclareWithName(UStr), // NOTE: Do we want to bring back type declaration by ID? It's notoriously fragile but // someone may have a user for it DeclareWithOid(Oid), DeclareArrayOf(Arc), } #[derive(Debug, Clone)] #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] pub struct PgCustomType { #[cfg_attr(feature = "offline", serde(skip))] pub(crate) oid: Oid, pub(crate) name: UStr, pub(crate) kind: PgTypeKind, } #[derive(Debug, Clone)] #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] pub enum PgTypeKind { Simple, Pseudo, Domain(PgTypeInfo), Composite(Arc<[(String, PgTypeInfo)]>), Array(PgTypeInfo), Enum(Arc<[String]>), Range(PgTypeInfo), } #[derive(Debug, Clone)] #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] pub struct PgArrayOf { pub(crate) elem_name: UStr, pub(crate) name: Box, } impl PgTypeInfo { /// Returns the corresponding `PgTypeInfo` if the OID is a built-in type and recognized by SQLx. pub(crate) fn try_from_oid(oid: Oid) -> Option { PgType::try_from_oid(oid).map(Self) } /// Returns the _kind_ (simple, array, enum, etc.) for this type. pub fn kind(&self) -> &PgTypeKind { self.0.kind() } /// Returns the OID for this type, if available. /// /// The OID may not be available if SQLx only knows the type by name. /// It will have to be resolved by a `PgConnection` at runtime which /// will yield a new and semantically distinct `TypeInfo` instance. /// /// This method does not perform any such lookup. /// /// ### Note /// With the exception of [the default `pg_type` catalog][pg_type], type OIDs are *not* stable in PostgreSQL. /// If a type is added by an extension, its OID will be assigned when the `CREATE EXTENSION` statement is executed, /// and so can change depending on what extensions are installed and in what order, as well as the exact /// version of PostgreSQL. /// /// [pg_type]: https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_type.dat pub fn oid(&self) -> Option { self.0.try_oid() } #[doc(hidden)] pub fn __type_feature_gate(&self) -> Option<&'static str> { if [ PgTypeInfo::DATE, PgTypeInfo::TIME, PgTypeInfo::TIMESTAMP, PgTypeInfo::TIMESTAMPTZ, PgTypeInfo::DATE_ARRAY, PgTypeInfo::TIME_ARRAY, PgTypeInfo::TIMESTAMP_ARRAY, PgTypeInfo::TIMESTAMPTZ_ARRAY, ] .contains(self) { Some("time") } else if [PgTypeInfo::UUID, PgTypeInfo::UUID_ARRAY].contains(self) { Some("uuid") } else if [ PgTypeInfo::JSON, PgTypeInfo::JSONB, PgTypeInfo::JSON_ARRAY, PgTypeInfo::JSONB_ARRAY, ] .contains(self) { Some("json") } else if [ PgTypeInfo::CIDR, PgTypeInfo::INET, PgTypeInfo::CIDR_ARRAY, PgTypeInfo::INET_ARRAY, ] .contains(self) { Some("ipnetwork") } else if [PgTypeInfo::MACADDR].contains(self) { Some("mac_address") } else if [PgTypeInfo::NUMERIC, PgTypeInfo::NUMERIC_ARRAY].contains(self) { Some("bigdecimal") } else { None } } /// Create a `PgTypeInfo` from a type name. /// /// The OID for the type will be fetched from Postgres on use of /// a value of this type. The fetched OID will be cached per-connection. /// /// ### Note: Type Names Prefixed with `_` /// In `pg_catalog.pg_type`, Postgres prefixes a type name with `_` to denote an array of that /// type, e.g. `int4[]` actually exists in `pg_type` as `_int4`. /// /// Previously, it was necessary in manual [`PgHasArrayType`][crate::PgHasArrayType] impls /// to return [`PgTypeInfo::with_name()`] with the type name prefixed with `_` to denote /// an array type, but this would not work with schema-qualified names. /// /// As of 0.8, [`PgTypeInfo::array_of()`] is used to declare an array type, /// and the Postgres driver is now able to properly resolve arrays of custom types, /// even in other schemas, which was not previously supported. /// /// It is highly recommended to migrate existing usages to [`PgTypeInfo::array_of()`] where /// applicable. /// /// However, to maintain compatibility, the driver now infers any type name prefixed with `_` /// to be an array of that type. This may introduce some breakages for types which use /// a `_` prefix but which are not arrays. /// /// As a workaround, type names with `_` as a prefix but which are not arrays should be wrapped /// in quotes, e.g.: /// ``` /// use sqlx::postgres::PgTypeInfo; /// use sqlx::{Type, TypeInfo}; /// /// /// `CREATE TYPE "_foo" AS ENUM ('Bar', 'Baz');` /// #[derive(sqlx::Type)] /// // Will prevent SQLx from inferring `_foo` as an array type. /// #[sqlx(type_name = r#""_foo""#)] /// enum Foo { /// Bar, /// Baz /// } /// /// assert_eq!(Foo::type_info().name(), r#""_foo""#); /// ``` pub const fn with_name(name: &'static str) -> Self { Self(PgType::DeclareWithName(UStr::Static(name))) } /// Create a `PgTypeInfo` of an array from the name of its element type. /// /// The array type OID will be fetched from Postgres on use of a value of this type. /// The fetched OID will be cached per-connection. pub fn array_of(elem_name: &'static str) -> Self { // to satisfy `name()` and `display_name()`, we need to construct strings to return Self(PgType::DeclareArrayOf(Arc::new(PgArrayOf { elem_name: elem_name.into(), name: format!("{elem_name}[]").into(), }))) } /// Create a `PgTypeInfo` from an OID. /// /// Note that the OID for a type is very dependent on the environment. If you only ever use /// one database or if this is an unhandled built-in type, you should be fine. Otherwise, /// you will be better served using [`Self::with_name()`]. /// /// ### Note: Interaction with `==` /// This constructor may give surprising results with `==`. /// /// See [the type-level docs][Self] for details. pub const fn with_oid(oid: Oid) -> Self { Self(PgType::DeclareWithOid(oid)) } /// Returns `true` if `self` can be compared exactly to `other`. /// /// Unlike `==`, this will return false if pub fn type_eq(&self, other: &Self) -> bool { self.eq_impl(other, false) } } // DEVELOPER PRO TIP: find builtin type OIDs easily by grepping this file // https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_type.dat // // If you have Postgres running locally you can also try // SELECT oid, typarray FROM pg_type where typname = '' impl PgType { /// Returns the corresponding `PgType` if the OID is a built-in type and recognized by SQLx. pub(crate) fn try_from_oid(oid: Oid) -> Option { Some(match oid.0 { 16 => PgType::Bool, 17 => PgType::Bytea, 18 => PgType::Char, 19 => PgType::Name, 20 => PgType::Int8, 21 => PgType::Int2, 23 => PgType::Int4, 25 => PgType::Text, 26 => PgType::Oid, 114 => PgType::Json, 199 => PgType::JsonArray, 600 => PgType::Point, 601 => PgType::Lseg, 602 => PgType::Path, 603 => PgType::Box, 604 => PgType::Polygon, 628 => PgType::Line, 629 => PgType::LineArray, 650 => PgType::Cidr, 651 => PgType::CidrArray, 700 => PgType::Float4, 701 => PgType::Float8, 705 => PgType::Unknown, 718 => PgType::Circle, 719 => PgType::CircleArray, 774 => PgType::Macaddr8, 775 => PgType::Macaddr8Array, 790 => PgType::Money, 791 => PgType::MoneyArray, 829 => PgType::Macaddr, 869 => PgType::Inet, 1000 => PgType::BoolArray, 1001 => PgType::ByteaArray, 1002 => PgType::CharArray, 1003 => PgType::NameArray, 1005 => PgType::Int2Array, 1007 => PgType::Int4Array, 1009 => PgType::TextArray, 1014 => PgType::BpcharArray, 1015 => PgType::VarcharArray, 1016 => PgType::Int8Array, 1017 => PgType::PointArray, 1018 => PgType::LsegArray, 1019 => PgType::PathArray, 1020 => PgType::BoxArray, 1021 => PgType::Float4Array, 1022 => PgType::Float8Array, 1027 => PgType::PolygonArray, 1028 => PgType::OidArray, 1040 => PgType::MacaddrArray, 1041 => PgType::InetArray, 1042 => PgType::Bpchar, 1043 => PgType::Varchar, 1082 => PgType::Date, 1083 => PgType::Time, 1114 => PgType::Timestamp, 1115 => PgType::TimestampArray, 1182 => PgType::DateArray, 1183 => PgType::TimeArray, 1184 => PgType::Timestamptz, 1185 => PgType::TimestamptzArray, 1186 => PgType::Interval, 1187 => PgType::IntervalArray, 1231 => PgType::NumericArray, 1266 => PgType::Timetz, 1270 => PgType::TimetzArray, 1560 => PgType::Bit, 1561 => PgType::BitArray, 1562 => PgType::Varbit, 1563 => PgType::VarbitArray, 1700 => PgType::Numeric, 2278 => PgType::Void, 2249 => PgType::Record, 2287 => PgType::RecordArray, 2950 => PgType::Uuid, 2951 => PgType::UuidArray, 3802 => PgType::Jsonb, 3807 => PgType::JsonbArray, 3904 => PgType::Int4Range, 3905 => PgType::Int4RangeArray, 3906 => PgType::NumRange, 3907 => PgType::NumRangeArray, 3908 => PgType::TsRange, 3909 => PgType::TsRangeArray, 3910 => PgType::TstzRange, 3911 => PgType::TstzRangeArray, 3912 => PgType::DateRange, 3913 => PgType::DateRangeArray, 3926 => PgType::Int8Range, 3927 => PgType::Int8RangeArray, 4072 => PgType::Jsonpath, 4073 => PgType::JsonpathArray, _ => { return None; } }) } pub(crate) fn oid(&self) -> Oid { match self.try_oid() { Some(oid) => oid, None => unreachable!("(bug) use of unresolved type declaration [oid]"), } } pub(crate) fn try_oid(&self) -> Option { Some(match self { PgType::Bool => Oid(16), PgType::Bytea => Oid(17), PgType::Char => Oid(18), PgType::Name => Oid(19), PgType::Int8 => Oid(20), PgType::Int2 => Oid(21), PgType::Int4 => Oid(23), PgType::Text => Oid(25), PgType::Oid => Oid(26), PgType::Json => Oid(114), PgType::JsonArray => Oid(199), PgType::Point => Oid(600), PgType::Lseg => Oid(601), PgType::Path => Oid(602), PgType::Box => Oid(603), PgType::Polygon => Oid(604), PgType::Line => Oid(628), PgType::LineArray => Oid(629), PgType::Cidr => Oid(650), PgType::CidrArray => Oid(651), PgType::Float4 => Oid(700), PgType::Float8 => Oid(701), PgType::Unknown => Oid(705), PgType::Circle => Oid(718), PgType::CircleArray => Oid(719), PgType::Macaddr8 => Oid(774), PgType::Macaddr8Array => Oid(775), PgType::Money => Oid(790), PgType::MoneyArray => Oid(791), PgType::Macaddr => Oid(829), PgType::Inet => Oid(869), PgType::BoolArray => Oid(1000), PgType::ByteaArray => Oid(1001), PgType::CharArray => Oid(1002), PgType::NameArray => Oid(1003), PgType::Int2Array => Oid(1005), PgType::Int4Array => Oid(1007), PgType::TextArray => Oid(1009), PgType::BpcharArray => Oid(1014), PgType::VarcharArray => Oid(1015), PgType::Int8Array => Oid(1016), PgType::PointArray => Oid(1017), PgType::LsegArray => Oid(1018), PgType::PathArray => Oid(1019), PgType::BoxArray => Oid(1020), PgType::Float4Array => Oid(1021), PgType::Float8Array => Oid(1022), PgType::PolygonArray => Oid(1027), PgType::OidArray => Oid(1028), PgType::MacaddrArray => Oid(1040), PgType::InetArray => Oid(1041), PgType::Bpchar => Oid(1042), PgType::Varchar => Oid(1043), PgType::Date => Oid(1082), PgType::Time => Oid(1083), PgType::Timestamp => Oid(1114), PgType::TimestampArray => Oid(1115), PgType::DateArray => Oid(1182), PgType::TimeArray => Oid(1183), PgType::Timestamptz => Oid(1184), PgType::TimestamptzArray => Oid(1185), PgType::Interval => Oid(1186), PgType::IntervalArray => Oid(1187), PgType::NumericArray => Oid(1231), PgType::Timetz => Oid(1266), PgType::TimetzArray => Oid(1270), PgType::Bit => Oid(1560), PgType::BitArray => Oid(1561), PgType::Varbit => Oid(1562), PgType::VarbitArray => Oid(1563), PgType::Numeric => Oid(1700), PgType::Void => Oid(2278), PgType::Record => Oid(2249), PgType::RecordArray => Oid(2287), PgType::Uuid => Oid(2950), PgType::UuidArray => Oid(2951), PgType::Jsonb => Oid(3802), PgType::JsonbArray => Oid(3807), PgType::Int4Range => Oid(3904), PgType::Int4RangeArray => Oid(3905), PgType::NumRange => Oid(3906), PgType::NumRangeArray => Oid(3907), PgType::TsRange => Oid(3908), PgType::TsRangeArray => Oid(3909), PgType::TstzRange => Oid(3910), PgType::TstzRangeArray => Oid(3911), PgType::DateRange => Oid(3912), PgType::DateRangeArray => Oid(3913), PgType::Int8Range => Oid(3926), PgType::Int8RangeArray => Oid(3927), PgType::Jsonpath => Oid(4072), PgType::JsonpathArray => Oid(4073), PgType::Custom(ty) => ty.oid, PgType::DeclareWithOid(oid) => *oid, PgType::DeclareWithName(_) => { return None; } PgType::DeclareArrayOf(_) => { return None; } }) } pub(crate) fn display_name(&self) -> &str { match self { PgType::Bool => "BOOL", PgType::Bytea => "BYTEA", PgType::Char => "\"CHAR\"", PgType::Name => "NAME", PgType::Int8 => "INT8", PgType::Int2 => "INT2", PgType::Int4 => "INT4", PgType::Text => "TEXT", PgType::Oid => "OID", PgType::Json => "JSON", PgType::JsonArray => "JSON[]", PgType::Point => "POINT", PgType::Lseg => "LSEG", PgType::Path => "PATH", PgType::Box => "BOX", PgType::Polygon => "POLYGON", PgType::Line => "LINE", PgType::LineArray => "LINE[]", PgType::Cidr => "CIDR", PgType::CidrArray => "CIDR[]", PgType::Float4 => "FLOAT4", PgType::Float8 => "FLOAT8", PgType::Unknown => "UNKNOWN", PgType::Circle => "CIRCLE", PgType::CircleArray => "CIRCLE[]", PgType::Macaddr8 => "MACADDR8", PgType::Macaddr8Array => "MACADDR8[]", PgType::Macaddr => "MACADDR", PgType::Inet => "INET", PgType::BoolArray => "BOOL[]", PgType::ByteaArray => "BYTEA[]", PgType::CharArray => "\"CHAR\"[]", PgType::NameArray => "NAME[]", PgType::Int2Array => "INT2[]", PgType::Int4Array => "INT4[]", PgType::TextArray => "TEXT[]", PgType::BpcharArray => "CHAR[]", PgType::VarcharArray => "VARCHAR[]", PgType::Int8Array => "INT8[]", PgType::PointArray => "POINT[]", PgType::LsegArray => "LSEG[]", PgType::PathArray => "PATH[]", PgType::BoxArray => "BOX[]", PgType::Float4Array => "FLOAT4[]", PgType::Float8Array => "FLOAT8[]", PgType::PolygonArray => "POLYGON[]", PgType::OidArray => "OID[]", PgType::MacaddrArray => "MACADDR[]", PgType::InetArray => "INET[]", PgType::Bpchar => "CHAR", PgType::Varchar => "VARCHAR", PgType::Date => "DATE", PgType::Time => "TIME", PgType::Timestamp => "TIMESTAMP", PgType::TimestampArray => "TIMESTAMP[]", PgType::DateArray => "DATE[]", PgType::TimeArray => "TIME[]", PgType::Timestamptz => "TIMESTAMPTZ", PgType::TimestamptzArray => "TIMESTAMPTZ[]", PgType::Interval => "INTERVAL", PgType::IntervalArray => "INTERVAL[]", PgType::NumericArray => "NUMERIC[]", PgType::Timetz => "TIMETZ", PgType::TimetzArray => "TIMETZ[]", PgType::Bit => "BIT", PgType::BitArray => "BIT[]", PgType::Varbit => "VARBIT", PgType::VarbitArray => "VARBIT[]", PgType::Numeric => "NUMERIC", PgType::Record => "RECORD", PgType::RecordArray => "RECORD[]", PgType::Uuid => "UUID", PgType::UuidArray => "UUID[]", PgType::Jsonb => "JSONB", PgType::JsonbArray => "JSONB[]", PgType::Int4Range => "INT4RANGE", PgType::Int4RangeArray => "INT4RANGE[]", PgType::NumRange => "NUMRANGE", PgType::NumRangeArray => "NUMRANGE[]", PgType::TsRange => "TSRANGE", PgType::TsRangeArray => "TSRANGE[]", PgType::TstzRange => "TSTZRANGE", PgType::TstzRangeArray => "TSTZRANGE[]", PgType::DateRange => "DATERANGE", PgType::DateRangeArray => "DATERANGE[]", PgType::Int8Range => "INT8RANGE", PgType::Int8RangeArray => "INT8RANGE[]", PgType::Jsonpath => "JSONPATH", PgType::JsonpathArray => "JSONPATH[]", PgType::Money => "MONEY", PgType::MoneyArray => "MONEY[]", PgType::Void => "VOID", PgType::Custom(ty) => &ty.name, PgType::DeclareWithOid(_) => "?", PgType::DeclareWithName(name) => name, PgType::DeclareArrayOf(array) => &array.name, } } pub(crate) fn name(&self) -> &str { match self { PgType::Bool => "bool", PgType::Bytea => "bytea", PgType::Char => "char", PgType::Name => "name", PgType::Int8 => "int8", PgType::Int2 => "int2", PgType::Int4 => "int4", PgType::Text => "text", PgType::Oid => "oid", PgType::Json => "json", PgType::JsonArray => "_json", PgType::Point => "point", PgType::Lseg => "lseg", PgType::Path => "path", PgType::Box => "box", PgType::Polygon => "polygon", PgType::Line => "line", PgType::LineArray => "_line", PgType::Cidr => "cidr", PgType::CidrArray => "_cidr", PgType::Float4 => "float4", PgType::Float8 => "float8", PgType::Unknown => "unknown", PgType::Circle => "circle", PgType::CircleArray => "_circle", PgType::Macaddr8 => "macaddr8", PgType::Macaddr8Array => "_macaddr8", PgType::Macaddr => "macaddr", PgType::Inet => "inet", PgType::BoolArray => "_bool", PgType::ByteaArray => "_bytea", PgType::CharArray => "_char", PgType::NameArray => "_name", PgType::Int2Array => "_int2", PgType::Int4Array => "_int4", PgType::TextArray => "_text", PgType::BpcharArray => "_bpchar", PgType::VarcharArray => "_varchar", PgType::Int8Array => "_int8", PgType::PointArray => "_point", PgType::LsegArray => "_lseg", PgType::PathArray => "_path", PgType::BoxArray => "_box", PgType::Float4Array => "_float4", PgType::Float8Array => "_float8", PgType::PolygonArray => "_polygon", PgType::OidArray => "_oid", PgType::MacaddrArray => "_macaddr", PgType::InetArray => "_inet", PgType::Bpchar => "bpchar", PgType::Varchar => "varchar", PgType::Date => "date", PgType::Time => "time", PgType::Timestamp => "timestamp", PgType::TimestampArray => "_timestamp", PgType::DateArray => "_date", PgType::TimeArray => "_time", PgType::Timestamptz => "timestamptz", PgType::TimestamptzArray => "_timestamptz", PgType::Interval => "interval", PgType::IntervalArray => "_interval", PgType::NumericArray => "_numeric", PgType::Timetz => "timetz", PgType::TimetzArray => "_timetz", PgType::Bit => "bit", PgType::BitArray => "_bit", PgType::Varbit => "varbit", PgType::VarbitArray => "_varbit", PgType::Numeric => "numeric", PgType::Record => "record", PgType::RecordArray => "_record", PgType::Uuid => "uuid", PgType::UuidArray => "_uuid", PgType::Jsonb => "jsonb", PgType::JsonbArray => "_jsonb", PgType::Int4Range => "int4range", PgType::Int4RangeArray => "_int4range", PgType::NumRange => "numrange", PgType::NumRangeArray => "_numrange", PgType::TsRange => "tsrange", PgType::TsRangeArray => "_tsrange", PgType::TstzRange => "tstzrange", PgType::TstzRangeArray => "_tstzrange", PgType::DateRange => "daterange", PgType::DateRangeArray => "_daterange", PgType::Int8Range => "int8range", PgType::Int8RangeArray => "_int8range", PgType::Jsonpath => "jsonpath", PgType::JsonpathArray => "_jsonpath", PgType::Money => "money", PgType::MoneyArray => "_money", PgType::Void => "void", PgType::Custom(ty) => &ty.name, PgType::DeclareWithOid(_) => "?", PgType::DeclareWithName(name) => name, PgType::DeclareArrayOf(array) => &array.name, } } pub(crate) fn kind(&self) -> &PgTypeKind { match self { PgType::Bool => &PgTypeKind::Simple, PgType::Bytea => &PgTypeKind::Simple, PgType::Char => &PgTypeKind::Simple, PgType::Name => &PgTypeKind::Simple, PgType::Int8 => &PgTypeKind::Simple, PgType::Int2 => &PgTypeKind::Simple, PgType::Int4 => &PgTypeKind::Simple, PgType::Text => &PgTypeKind::Simple, PgType::Oid => &PgTypeKind::Simple, PgType::Json => &PgTypeKind::Simple, PgType::JsonArray => &PgTypeKind::Array(PgTypeInfo(PgType::Json)), PgType::Point => &PgTypeKind::Simple, PgType::Lseg => &PgTypeKind::Simple, PgType::Path => &PgTypeKind::Simple, PgType::Box => &PgTypeKind::Simple, PgType::Polygon => &PgTypeKind::Simple, PgType::Line => &PgTypeKind::Simple, PgType::LineArray => &PgTypeKind::Array(PgTypeInfo(PgType::Line)), PgType::Cidr => &PgTypeKind::Simple, PgType::CidrArray => &PgTypeKind::Array(PgTypeInfo(PgType::Cidr)), PgType::Float4 => &PgTypeKind::Simple, PgType::Float8 => &PgTypeKind::Simple, PgType::Unknown => &PgTypeKind::Simple, PgType::Circle => &PgTypeKind::Simple, PgType::CircleArray => &PgTypeKind::Array(PgTypeInfo(PgType::Circle)), PgType::Macaddr8 => &PgTypeKind::Simple, PgType::Macaddr8Array => &PgTypeKind::Array(PgTypeInfo(PgType::Macaddr8)), PgType::Macaddr => &PgTypeKind::Simple, PgType::Inet => &PgTypeKind::Simple, PgType::BoolArray => &PgTypeKind::Array(PgTypeInfo(PgType::Bool)), PgType::ByteaArray => &PgTypeKind::Array(PgTypeInfo(PgType::Bytea)), PgType::CharArray => &PgTypeKind::Array(PgTypeInfo(PgType::Char)), PgType::NameArray => &PgTypeKind::Array(PgTypeInfo(PgType::Name)), PgType::Int2Array => &PgTypeKind::Array(PgTypeInfo(PgType::Int2)), PgType::Int4Array => &PgTypeKind::Array(PgTypeInfo(PgType::Int4)), PgType::TextArray => &PgTypeKind::Array(PgTypeInfo(PgType::Text)), PgType::BpcharArray => &PgTypeKind::Array(PgTypeInfo(PgType::Bpchar)), PgType::VarcharArray => &PgTypeKind::Array(PgTypeInfo(PgType::Varchar)), PgType::Int8Array => &PgTypeKind::Array(PgTypeInfo(PgType::Int8)), PgType::PointArray => &PgTypeKind::Array(PgTypeInfo(PgType::Point)), PgType::LsegArray => &PgTypeKind::Array(PgTypeInfo(PgType::Lseg)), PgType::PathArray => &PgTypeKind::Array(PgTypeInfo(PgType::Path)), PgType::BoxArray => &PgTypeKind::Array(PgTypeInfo(PgType::Box)), PgType::Float4Array => &PgTypeKind::Array(PgTypeInfo(PgType::Float4)), PgType::Float8Array => &PgTypeKind::Array(PgTypeInfo(PgType::Float8)), PgType::PolygonArray => &PgTypeKind::Array(PgTypeInfo(PgType::Polygon)), PgType::OidArray => &PgTypeKind::Array(PgTypeInfo(PgType::Oid)), PgType::MacaddrArray => &PgTypeKind::Array(PgTypeInfo(PgType::Macaddr)), PgType::InetArray => &PgTypeKind::Array(PgTypeInfo(PgType::Inet)), PgType::Bpchar => &PgTypeKind::Simple, PgType::Varchar => &PgTypeKind::Simple, PgType::Date => &PgTypeKind::Simple, PgType::Time => &PgTypeKind::Simple, PgType::Timestamp => &PgTypeKind::Simple, PgType::TimestampArray => &PgTypeKind::Array(PgTypeInfo(PgType::Timestamp)), PgType::DateArray => &PgTypeKind::Array(PgTypeInfo(PgType::Date)), PgType::TimeArray => &PgTypeKind::Array(PgTypeInfo(PgType::Time)), PgType::Timestamptz => &PgTypeKind::Simple, PgType::TimestamptzArray => &PgTypeKind::Array(PgTypeInfo(PgType::Timestamptz)), PgType::Interval => &PgTypeKind::Simple, PgType::IntervalArray => &PgTypeKind::Array(PgTypeInfo(PgType::Interval)), PgType::NumericArray => &PgTypeKind::Array(PgTypeInfo(PgType::Numeric)), PgType::Timetz => &PgTypeKind::Simple, PgType::TimetzArray => &PgTypeKind::Array(PgTypeInfo(PgType::Timetz)), PgType::Bit => &PgTypeKind::Simple, PgType::BitArray => &PgTypeKind::Array(PgTypeInfo(PgType::Bit)), PgType::Varbit => &PgTypeKind::Simple, PgType::VarbitArray => &PgTypeKind::Array(PgTypeInfo(PgType::Varbit)), PgType::Numeric => &PgTypeKind::Simple, PgType::Record => &PgTypeKind::Simple, PgType::RecordArray => &PgTypeKind::Array(PgTypeInfo(PgType::Record)), PgType::Uuid => &PgTypeKind::Simple, PgType::UuidArray => &PgTypeKind::Array(PgTypeInfo(PgType::Uuid)), PgType::Jsonb => &PgTypeKind::Simple, PgType::JsonbArray => &PgTypeKind::Array(PgTypeInfo(PgType::Jsonb)), PgType::Int4Range => &PgTypeKind::Range(PgTypeInfo::INT4), PgType::Int4RangeArray => &PgTypeKind::Array(PgTypeInfo(PgType::Int4Range)), PgType::NumRange => &PgTypeKind::Range(PgTypeInfo::NUMERIC), PgType::NumRangeArray => &PgTypeKind::Array(PgTypeInfo(PgType::NumRange)), PgType::TsRange => &PgTypeKind::Range(PgTypeInfo::TIMESTAMP), PgType::TsRangeArray => &PgTypeKind::Array(PgTypeInfo(PgType::TsRange)), PgType::TstzRange => &PgTypeKind::Range(PgTypeInfo::TIMESTAMPTZ), PgType::TstzRangeArray => &PgTypeKind::Array(PgTypeInfo(PgType::TstzRange)), PgType::DateRange => &PgTypeKind::Range(PgTypeInfo::DATE), PgType::DateRangeArray => &PgTypeKind::Array(PgTypeInfo(PgType::DateRange)), PgType::Int8Range => &PgTypeKind::Range(PgTypeInfo::INT8), PgType::Int8RangeArray => &PgTypeKind::Array(PgTypeInfo(PgType::Int8Range)), PgType::Jsonpath => &PgTypeKind::Simple, PgType::JsonpathArray => &PgTypeKind::Array(PgTypeInfo(PgType::Jsonpath)), PgType::Money => &PgTypeKind::Simple, PgType::MoneyArray => &PgTypeKind::Array(PgTypeInfo(PgType::Money)), PgType::Void => &PgTypeKind::Pseudo, PgType::Custom(ty) => &ty.kind, PgType::DeclareWithOid(oid) => { unreachable!("(bug) use of unresolved type declaration [oid={}]", oid.0); } PgType::DeclareWithName(name) => { unreachable!("(bug) use of unresolved type declaration [name={name}]"); } PgType::DeclareArrayOf(array) => { unreachable!( "(bug) use of unresolved type declaration [array of={}]", array.elem_name ); } } } /// If `self` is an array type, return the type info for its element. pub(crate) fn try_array_element(&self) -> Option> { // We explicitly match on all the `None` cases to ensure an exhaustive match. match self { PgType::Bool => None, PgType::BoolArray => Some(Cow::Owned(PgTypeInfo(PgType::Bool))), PgType::Bytea => None, PgType::ByteaArray => Some(Cow::Owned(PgTypeInfo(PgType::Bytea))), PgType::Char => None, PgType::CharArray => Some(Cow::Owned(PgTypeInfo(PgType::Char))), PgType::Name => None, PgType::NameArray => Some(Cow::Owned(PgTypeInfo(PgType::Name))), PgType::Int8 => None, PgType::Int8Array => Some(Cow::Owned(PgTypeInfo(PgType::Int8))), PgType::Int2 => None, PgType::Int2Array => Some(Cow::Owned(PgTypeInfo(PgType::Int2))), PgType::Int4 => None, PgType::Int4Array => Some(Cow::Owned(PgTypeInfo(PgType::Int4))), PgType::Text => None, PgType::TextArray => Some(Cow::Owned(PgTypeInfo(PgType::Text))), PgType::Oid => None, PgType::OidArray => Some(Cow::Owned(PgTypeInfo(PgType::Oid))), PgType::Json => None, PgType::JsonArray => Some(Cow::Owned(PgTypeInfo(PgType::Json))), PgType::Point => None, PgType::PointArray => Some(Cow::Owned(PgTypeInfo(PgType::Point))), PgType::Lseg => None, PgType::LsegArray => Some(Cow::Owned(PgTypeInfo(PgType::Lseg))), PgType::Path => None, PgType::PathArray => Some(Cow::Owned(PgTypeInfo(PgType::Path))), PgType::Box => None, PgType::BoxArray => Some(Cow::Owned(PgTypeInfo(PgType::Box))), PgType::Polygon => None, PgType::PolygonArray => Some(Cow::Owned(PgTypeInfo(PgType::Polygon))), PgType::Line => None, PgType::LineArray => Some(Cow::Owned(PgTypeInfo(PgType::Line))), PgType::Cidr => None, PgType::CidrArray => Some(Cow::Owned(PgTypeInfo(PgType::Cidr))), PgType::Float4 => None, PgType::Float4Array => Some(Cow::Owned(PgTypeInfo(PgType::Float4))), PgType::Float8 => None, PgType::Float8Array => Some(Cow::Owned(PgTypeInfo(PgType::Float8))), PgType::Circle => None, PgType::CircleArray => Some(Cow::Owned(PgTypeInfo(PgType::Circle))), PgType::Macaddr8 => None, PgType::Macaddr8Array => Some(Cow::Owned(PgTypeInfo(PgType::Macaddr8))), PgType::Money => None, PgType::MoneyArray => Some(Cow::Owned(PgTypeInfo(PgType::Money))), PgType::Macaddr => None, PgType::MacaddrArray => Some(Cow::Owned(PgTypeInfo(PgType::Macaddr))), PgType::Inet => None, PgType::InetArray => Some(Cow::Owned(PgTypeInfo(PgType::Inet))), PgType::Bpchar => None, PgType::BpcharArray => Some(Cow::Owned(PgTypeInfo(PgType::Bpchar))), PgType::Varchar => None, PgType::VarcharArray => Some(Cow::Owned(PgTypeInfo(PgType::Varchar))), PgType::Date => None, PgType::DateArray => Some(Cow::Owned(PgTypeInfo(PgType::Date))), PgType::Time => None, PgType::TimeArray => Some(Cow::Owned(PgTypeInfo(PgType::Time))), PgType::Timestamp => None, PgType::TimestampArray => Some(Cow::Owned(PgTypeInfo(PgType::Timestamp))), PgType::Timestamptz => None, PgType::TimestamptzArray => Some(Cow::Owned(PgTypeInfo(PgType::Timestamptz))), PgType::Interval => None, PgType::IntervalArray => Some(Cow::Owned(PgTypeInfo(PgType::Interval))), PgType::Timetz => None, PgType::TimetzArray => Some(Cow::Owned(PgTypeInfo(PgType::Timetz))), PgType::Bit => None, PgType::BitArray => Some(Cow::Owned(PgTypeInfo(PgType::Bit))), PgType::Varbit => None, PgType::VarbitArray => Some(Cow::Owned(PgTypeInfo(PgType::Varbit))), PgType::Numeric => None, PgType::NumericArray => Some(Cow::Owned(PgTypeInfo(PgType::Numeric))), PgType::Record => None, PgType::RecordArray => Some(Cow::Owned(PgTypeInfo(PgType::Record))), PgType::Uuid => None, PgType::UuidArray => Some(Cow::Owned(PgTypeInfo(PgType::Uuid))), PgType::Jsonb => None, PgType::JsonbArray => Some(Cow::Owned(PgTypeInfo(PgType::Jsonb))), PgType::Int4Range => None, PgType::Int4RangeArray => Some(Cow::Owned(PgTypeInfo(PgType::Int4Range))), PgType::NumRange => None, PgType::NumRangeArray => Some(Cow::Owned(PgTypeInfo(PgType::NumRange))), PgType::TsRange => None, PgType::TsRangeArray => Some(Cow::Owned(PgTypeInfo(PgType::TsRange))), PgType::TstzRange => None, PgType::TstzRangeArray => Some(Cow::Owned(PgTypeInfo(PgType::TstzRange))), PgType::DateRange => None, PgType::DateRangeArray => Some(Cow::Owned(PgTypeInfo(PgType::DateRange))), PgType::Int8Range => None, PgType::Int8RangeArray => Some(Cow::Owned(PgTypeInfo(PgType::Int8Range))), PgType::Jsonpath => None, PgType::JsonpathArray => Some(Cow::Owned(PgTypeInfo(PgType::Jsonpath))), // There is no `UnknownArray` PgType::Unknown => None, // There is no `VoidArray` PgType::Void => None, PgType::Custom(ty) => match &ty.kind { PgTypeKind::Simple => None, PgTypeKind::Pseudo => None, PgTypeKind::Domain(_) => None, PgTypeKind::Composite(_) => None, PgTypeKind::Array(ref elem_type_info) => Some(Cow::Borrowed(elem_type_info)), PgTypeKind::Enum(_) => None, PgTypeKind::Range(_) => None, }, PgType::DeclareWithOid(_) => None, PgType::DeclareWithName(name) => { // LEGACY: infer the array element name from a `_` prefix UStr::strip_prefix(name, "_") .map(|elem| Cow::Owned(PgTypeInfo(PgType::DeclareWithName(elem)))) } PgType::DeclareArrayOf(array) => Some(Cow::Owned(PgTypeInfo(PgType::DeclareWithName( array.elem_name.clone(), )))), } } /// Returns `true` if this type cannot be matched by name. fn is_declare_with_oid(&self) -> bool { matches!(self, Self::DeclareWithOid(_)) } /// Compare two `PgType`s, first by OID, then by array element, then by name. /// /// If `soft_eq` is true and `self` or `other` is `DeclareWithOid` but not both, return `true` /// before checking names. fn eq_impl(&self, other: &Self, soft_eq: bool) -> bool { if let (Some(a), Some(b)) = (self.try_base_oid(), other.try_base_oid()) { // If there are OIDs available, use OIDs to perform a direct match return a == b; } if soft_eq && (self.is_declare_with_oid() || other.is_declare_with_oid()) { // If we get to this point, one instance is `DeclareWithOid()` and the other is // `DeclareArrayOf()` or `DeclareWithName()`, which means we can't compare the two. // // Since this is only likely to occur when using the text protocol where we can't // resolve type names before executing a query, we can just opt out of typechecking. return true; } if let (Some(elem_a), Some(elem_b)) = (self.try_array_element(), other.try_array_element()) { return elem_a == elem_b; } // Otherwise, perform a match on the name name_eq(self.name(), other.name()) } // Tries to return the OID of the type, returns the OID of the base_type for domain types #[inline(always)] fn try_base_oid(&self) -> Option { match self { PgType::Custom(custom) => match &custom.kind { PgTypeKind::Domain(domain) => domain.try_oid(), _ => Some(custom.oid), }, ty => ty.try_oid(), } } } impl TypeInfo for PgTypeInfo { fn name(&self) -> &str { self.0.display_name() } fn is_null(&self) -> bool { false } fn is_void(&self) -> bool { matches!(self.0, PgType::Void) } fn type_compatible(&self, other: &Self) -> bool where Self: Sized, { self == other } } impl PartialEq for PgCustomType { fn eq(&self, other: &PgCustomType) -> bool { other.oid == self.oid } } impl PgTypeInfo { // boolean, state of true or false pub(crate) const BOOL: Self = Self(PgType::Bool); pub(crate) const BOOL_ARRAY: Self = Self(PgType::BoolArray); // binary data types, variable-length binary string pub(crate) const BYTEA: Self = Self(PgType::Bytea); pub(crate) const BYTEA_ARRAY: Self = Self(PgType::ByteaArray); // uuid pub(crate) const UUID: Self = Self(PgType::Uuid); pub(crate) const UUID_ARRAY: Self = Self(PgType::UuidArray); // record pub(crate) const RECORD: Self = Self(PgType::Record); pub(crate) const RECORD_ARRAY: Self = Self(PgType::RecordArray); // // JSON types // https://www.postgresql.org/docs/current/datatype-json.html // pub(crate) const JSON: Self = Self(PgType::Json); pub(crate) const JSON_ARRAY: Self = Self(PgType::JsonArray); pub(crate) const JSONB: Self = Self(PgType::Jsonb); pub(crate) const JSONB_ARRAY: Self = Self(PgType::JsonbArray); pub(crate) const JSONPATH: Self = Self(PgType::Jsonpath); pub(crate) const JSONPATH_ARRAY: Self = Self(PgType::JsonpathArray); // // network address types // https://www.postgresql.org/docs/current/datatype-net-types.html // pub(crate) const CIDR: Self = Self(PgType::Cidr); pub(crate) const CIDR_ARRAY: Self = Self(PgType::CidrArray); pub(crate) const INET: Self = Self(PgType::Inet); pub(crate) const INET_ARRAY: Self = Self(PgType::InetArray); pub(crate) const MACADDR: Self = Self(PgType::Macaddr); pub(crate) const MACADDR_ARRAY: Self = Self(PgType::MacaddrArray); pub(crate) const MACADDR8: Self = Self(PgType::Macaddr8); pub(crate) const MACADDR8_ARRAY: Self = Self(PgType::Macaddr8Array); // // character types // https://www.postgresql.org/docs/current/datatype-character.html // // internal type for object names pub(crate) const NAME: Self = Self(PgType::Name); pub(crate) const NAME_ARRAY: Self = Self(PgType::NameArray); // character type, fixed-length, blank-padded pub(crate) const BPCHAR: Self = Self(PgType::Bpchar); pub(crate) const BPCHAR_ARRAY: Self = Self(PgType::BpcharArray); // character type, variable-length with limit pub(crate) const VARCHAR: Self = Self(PgType::Varchar); pub(crate) const VARCHAR_ARRAY: Self = Self(PgType::VarcharArray); // character type, variable-length pub(crate) const TEXT: Self = Self(PgType::Text); pub(crate) const TEXT_ARRAY: Self = Self(PgType::TextArray); // unknown type, transmitted as text pub(crate) const UNKNOWN: Self = Self(PgType::Unknown); // // numeric types // https://www.postgresql.org/docs/current/datatype-numeric.html // // single-byte internal type pub(crate) const CHAR: Self = Self(PgType::Char); pub(crate) const CHAR_ARRAY: Self = Self(PgType::CharArray); // internal type for type ids pub(crate) const OID: Self = Self(PgType::Oid); pub(crate) const OID_ARRAY: Self = Self(PgType::OidArray); // small-range integer; -32768 to +32767 pub(crate) const INT2: Self = Self(PgType::Int2); pub(crate) const INT2_ARRAY: Self = Self(PgType::Int2Array); // typical choice for integer; -2147483648 to +2147483647 pub(crate) const INT4: Self = Self(PgType::Int4); pub(crate) const INT4_ARRAY: Self = Self(PgType::Int4Array); // large-range integer; -9223372036854775808 to +9223372036854775807 pub(crate) const INT8: Self = Self(PgType::Int8); pub(crate) const INT8_ARRAY: Self = Self(PgType::Int8Array); // variable-precision, inexact, 6 decimal digits precision pub(crate) const FLOAT4: Self = Self(PgType::Float4); pub(crate) const FLOAT4_ARRAY: Self = Self(PgType::Float4Array); // variable-precision, inexact, 15 decimal digits precision pub(crate) const FLOAT8: Self = Self(PgType::Float8); pub(crate) const FLOAT8_ARRAY: Self = Self(PgType::Float8Array); // user-specified precision, exact pub(crate) const NUMERIC: Self = Self(PgType::Numeric); pub(crate) const NUMERIC_ARRAY: Self = Self(PgType::NumericArray); // user-specified precision, exact pub(crate) const MONEY: Self = Self(PgType::Money); pub(crate) const MONEY_ARRAY: Self = Self(PgType::MoneyArray); // // date/time types // https://www.postgresql.org/docs/current/datatype-datetime.html // // both date and time (no time zone) pub(crate) const TIMESTAMP: Self = Self(PgType::Timestamp); pub(crate) const TIMESTAMP_ARRAY: Self = Self(PgType::TimestampArray); // both date and time (with time zone) pub(crate) const TIMESTAMPTZ: Self = Self(PgType::Timestamptz); pub(crate) const TIMESTAMPTZ_ARRAY: Self = Self(PgType::TimestamptzArray); // date (no time of day) pub(crate) const DATE: Self = Self(PgType::Date); pub(crate) const DATE_ARRAY: Self = Self(PgType::DateArray); // time of day (no date) pub(crate) const TIME: Self = Self(PgType::Time); pub(crate) const TIME_ARRAY: Self = Self(PgType::TimeArray); // time of day (no date), with time zone pub(crate) const TIMETZ: Self = Self(PgType::Timetz); pub(crate) const TIMETZ_ARRAY: Self = Self(PgType::TimetzArray); // time interval pub(crate) const INTERVAL: Self = Self(PgType::Interval); pub(crate) const INTERVAL_ARRAY: Self = Self(PgType::IntervalArray); // // geometric types // https://www.postgresql.org/docs/current/datatype-geometric.html // // point on a plane pub(crate) const POINT: Self = Self(PgType::Point); pub(crate) const POINT_ARRAY: Self = Self(PgType::PointArray); // infinite line pub(crate) const LINE: Self = Self(PgType::Line); pub(crate) const LINE_ARRAY: Self = Self(PgType::LineArray); // finite line segment pub(crate) const LSEG: Self = Self(PgType::Lseg); pub(crate) const LSEG_ARRAY: Self = Self(PgType::LsegArray); // rectangular box pub(crate) const BOX: Self = Self(PgType::Box); pub(crate) const BOX_ARRAY: Self = Self(PgType::BoxArray); // open or closed path pub(crate) const PATH: Self = Self(PgType::Path); pub(crate) const PATH_ARRAY: Self = Self(PgType::PathArray); // polygon pub(crate) const POLYGON: Self = Self(PgType::Polygon); pub(crate) const POLYGON_ARRAY: Self = Self(PgType::PolygonArray); // circle pub(crate) const CIRCLE: Self = Self(PgType::Circle); pub(crate) const CIRCLE_ARRAY: Self = Self(PgType::CircleArray); // // bit string types // https://www.postgresql.org/docs/current/datatype-bit.html // pub(crate) const BIT: Self = Self(PgType::Bit); pub(crate) const BIT_ARRAY: Self = Self(PgType::BitArray); pub(crate) const VARBIT: Self = Self(PgType::Varbit); pub(crate) const VARBIT_ARRAY: Self = Self(PgType::VarbitArray); // // range types // https://www.postgresql.org/docs/current/rangetypes.html // pub(crate) const INT4_RANGE: Self = Self(PgType::Int4Range); pub(crate) const INT4_RANGE_ARRAY: Self = Self(PgType::Int4RangeArray); pub(crate) const NUM_RANGE: Self = Self(PgType::NumRange); pub(crate) const NUM_RANGE_ARRAY: Self = Self(PgType::NumRangeArray); pub(crate) const TS_RANGE: Self = Self(PgType::TsRange); pub(crate) const TS_RANGE_ARRAY: Self = Self(PgType::TsRangeArray); pub(crate) const TSTZ_RANGE: Self = Self(PgType::TstzRange); pub(crate) const TSTZ_RANGE_ARRAY: Self = Self(PgType::TstzRangeArray); pub(crate) const DATE_RANGE: Self = Self(PgType::DateRange); pub(crate) const DATE_RANGE_ARRAY: Self = Self(PgType::DateRangeArray); pub(crate) const INT8_RANGE: Self = Self(PgType::Int8Range); pub(crate) const INT8_RANGE_ARRAY: Self = Self(PgType::Int8RangeArray); // // pseudo types // https://www.postgresql.org/docs/9.3/datatype-pseudo.html // pub(crate) const VOID: Self = Self(PgType::Void); } impl Display for PgTypeInfo { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.pad(self.name()) } } impl PartialEq for PgType { fn eq(&self, other: &PgType) -> bool { self.eq_impl(other, true) } } /// Check type names for equality, respecting Postgres' case sensitivity rules for identifiers. /// /// https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS fn name_eq(name1: &str, name2: &str) -> bool { // Cop-out of processing Unicode escapes by just using string equality. if name1.starts_with("U&") { // If `name2` doesn't start with `U&` this will automatically be `false`. return name1 == name2; } let mut chars1 = identifier_chars(name1); let mut chars2 = identifier_chars(name2); while let (Some(a), Some(b)) = (chars1.next(), chars2.next()) { if !a.eq(&b) { return false; } } chars1.next().is_none() && chars2.next().is_none() } struct IdentifierChar { ch: char, case_sensitive: bool, } impl IdentifierChar { fn eq(&self, other: &Self) -> bool { if self.case_sensitive || other.case_sensitive { self.ch == other.ch } else { self.ch.eq_ignore_ascii_case(&other.ch) } } } /// Return an iterator over all significant characters of an identifier. /// /// Ignores non-escaped quotation marks. fn identifier_chars(ident: &str) -> impl Iterator + '_ { let mut case_sensitive = false; let mut last_char_quote = false; ident.chars().filter_map(move |ch| { if ch == '"' { if last_char_quote { last_char_quote = false; } else { last_char_quote = true; return None; } } else if last_char_quote { last_char_quote = false; case_sensitive = !case_sensitive; } Some(IdentifierChar { ch, case_sensitive }) }) } #[test] fn test_name_eq() { let test_values = [ ("foo", "foo", true), ("foo", "Foo", true), ("foo", "FOO", true), ("foo", r#""foo""#, true), ("foo", r#""Foo""#, false), ("foo", "foo.foo", false), ("foo.foo", "foo.foo", true), ("foo.foo", "foo.Foo", true), ("foo.foo", "foo.FOO", true), ("foo.foo", "Foo.foo", true), ("foo.foo", "Foo.Foo", true), ("foo.foo", "FOO.FOO", true), ("foo.foo", "foo", false), ("foo.foo", r#"foo."foo""#, true), ("foo.foo", r#"foo."Foo""#, false), ("foo.foo", r#"foo."FOO""#, false), ]; for (left, right, eq) in test_values { assert_eq!( name_eq(left, right), eq, "failed check for name_eq({left:?}, {right:?})" ); assert_eq!( name_eq(right, left), eq, "failed check for name_eq({right:?}, {left:?})" ); } } ================================================ FILE: sqlx-postgres/src/types/array.rs ================================================ use sqlx_core::bytes::Buf; use sqlx_core::types::Text; use std::borrow::Cow; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Oid; use crate::types::Type; use crate::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; /// Provides information necessary to encode and decode Postgres arrays as compatible Rust types. /// /// Implementing this trait for some type `T` enables relevant `Type`,`Encode` and `Decode` impls /// for `Vec`, `&[T]` (slices), `[T; N]` (arrays), etc. /// /// ### Note: `#[derive(sqlx::Type)]` /// If you have the `postgres` feature enabled, `#[derive(sqlx::Type)]` will also generate /// an impl of this trait for your type if your wrapper is marked `#[sqlx(transparent)]`: /// /// ```rust,ignore /// #[derive(sqlx::Type)] /// #[sqlx(transparent)] /// struct UserId(i64); /// /// let user_ids: Vec = sqlx::query_scalar("select '{ 123, 456 }'::int8[]") /// .fetch(&mut pg_connection) /// .await?; /// ``` /// /// However, this may cause an error if the type being wrapped does not implement `PgHasArrayType`, /// e.g. `Vec` itself, because we don't currently support multidimensional arrays: /// /// ```rust,ignore /// #[derive(sqlx::Type)] // ERROR: `Vec` does not implement `PgHasArrayType` /// #[sqlx(transparent)] /// struct UserIds(Vec); /// ``` /// /// To remedy this, add `#[sqlx(no_pg_array)]`, which disables the generation /// of the `PgHasArrayType` impl: /// /// ```rust,ignore /// #[derive(sqlx::Type)] /// #[sqlx(transparent, no_pg_array)] /// struct UserIds(Vec); /// ``` /// /// See [the documentation of `Type`][Type] for more details. pub trait PgHasArrayType { fn array_type_info() -> PgTypeInfo; fn array_compatible(ty: &PgTypeInfo) -> bool { *ty == Self::array_type_info() } } impl PgHasArrayType for &T where T: PgHasArrayType, { fn array_type_info() -> PgTypeInfo { T::array_type_info() } fn array_compatible(ty: &PgTypeInfo) -> bool { T::array_compatible(ty) } } impl PgHasArrayType for Option where T: PgHasArrayType, { fn array_type_info() -> PgTypeInfo { T::array_type_info() } fn array_compatible(ty: &PgTypeInfo) -> bool { T::array_compatible(ty) } } impl PgHasArrayType for Text { fn array_type_info() -> PgTypeInfo { String::array_type_info() } fn array_compatible(ty: &PgTypeInfo) -> bool { String::array_compatible(ty) } } impl Type for [T] where T: PgHasArrayType, { fn type_info() -> PgTypeInfo { T::array_type_info() } fn compatible(ty: &PgTypeInfo) -> bool { T::array_compatible(ty) } } impl Type for Vec where T: PgHasArrayType, { fn type_info() -> PgTypeInfo { T::array_type_info() } fn compatible(ty: &PgTypeInfo) -> bool { T::array_compatible(ty) } } impl Type for [T; N] where T: PgHasArrayType, { fn type_info() -> PgTypeInfo { T::array_type_info() } fn compatible(ty: &PgTypeInfo) -> bool { T::array_compatible(ty) } } impl<'q, T> Encode<'q, Postgres> for Vec where for<'a> &'a [T]: Encode<'q, Postgres>, T: Encode<'q, Postgres>, { #[inline] fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { self.as_slice().encode_by_ref(buf) } } impl<'q, T, const N: usize> Encode<'q, Postgres> for [T; N] where for<'a> &'a [T]: Encode<'q, Postgres>, T: Encode<'q, Postgres>, { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { self.as_slice().encode_by_ref(buf) } } impl<'q, T> Encode<'q, Postgres> for &'_ [T] where T: Encode<'q, Postgres> + Type, { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { // do the length check early to avoid doing unnecessary work i32::try_from(self.len()).map_err(|_| { format!( "encoded array length is too large for Postgres: {}", self.len() ) })?; crate::PgBindIterExt::bind_iter(self.iter()).encode(buf) } } impl<'r, T, const N: usize> Decode<'r, Postgres> for [T; N] where T: for<'a> Decode<'a, Postgres> + Type, { fn decode(value: PgValueRef<'r>) -> Result { // This could be done more efficiently by refactoring the Vec decoding below so that it can // be used for arrays and Vec. let vec: Vec = Decode::decode(value)?; let array: [T; N] = vec.try_into().map_err(|_| "wrong number of elements")?; Ok(array) } } impl<'r, T> Decode<'r, Postgres> for Vec where T: for<'a> Decode<'a, Postgres> + Type, { fn decode(value: PgValueRef<'r>) -> Result { let format = value.format(); match format { PgValueFormat::Binary => { // https://github.com/postgres/postgres/blob/a995b371ae29de2d38c4b7881cf414b1560e9746/src/backend/utils/adt/arrayfuncs.c#L1548 let mut buf = value.as_bytes()?; // number of dimensions in the array let ndim = buf.get_i32(); if ndim == 0 { // zero dimensions is an empty array return Ok(Vec::new()); } if ndim != 1 { return Err(format!("encountered an array of {ndim} dimensions; only one-dimensional arrays are supported").into()); } // appears to have been used in the past to communicate potential NULLS // but reading source code back through our historically supported // postgres versions (9.5+) this is never used for anything let _flags = buf.get_i32(); // the OID of the element let element_type_oid = Oid(buf.get_u32()); let element_type_info: PgTypeInfo = PgTypeInfo::try_from_oid(element_type_oid) .or_else(|| value.type_info.try_array_element().map(Cow::into_owned)) .ok_or_else(|| { BoxDynError::from(format!( "failed to resolve array element type for oid {}", element_type_oid.0 )) })?; // length of the array axis let len = buf.get_i32(); let len = usize::try_from(len) .map_err(|_| format!("overflow converting array len ({len}) to usize"))?; // the lower bound, we only support arrays starting from "1" let lower = buf.get_i32(); if lower != 1 { return Err(format!("encountered an array with a lower bound of {lower} in the first dimension; only arrays starting at one are supported").into()); } let mut elements = Vec::with_capacity(len); for _ in 0..len { let value_ref = PgValueRef::get(&mut buf, format, element_type_info.clone())?; elements.push(T::decode(value_ref)?); } Ok(elements) } PgValueFormat::Text => { // no type is provided from the database for the element let element_type_info = T::type_info(); let s = value.as_str()?; // https://github.com/postgres/postgres/blob/a995b371ae29de2d38c4b7881cf414b1560e9746/src/backend/utils/adt/arrayfuncs.c#L718 // trim the wrapping braces let s = &s[1..(s.len() - 1)]; if s.is_empty() { // short-circuit empty arrays up here return Ok(Vec::new()); } // NOTE: Nearly *all* types use ',' as the sequence delimiter. Yes, there is one // that does not. The BOX (not PostGIS) type uses ';' as a delimiter. // TODO: When we add support for BOX we need to figure out some way to make the // delimiter selection let delimiter = ','; let mut done = false; let mut in_quotes = false; let mut in_escape = false; let mut value = String::with_capacity(10); let mut chars = s.chars(); let mut elements = Vec::with_capacity(4); while !done { loop { match chars.next() { Some(ch) => match ch { _ if in_escape => { value.push(ch); in_escape = false; } '"' => { in_quotes = !in_quotes; } '\\' => { in_escape = true; } _ if ch == delimiter && !in_quotes => { break; } _ => { value.push(ch); } }, None => { done = true; break; } } } let value_opt = if value == "NULL" { None } else { Some(value.as_bytes()) }; elements.push(T::decode(PgValueRef { value: value_opt, row: None, type_info: element_type_info.clone(), format, })?); value.clear(); } Ok(elements) } } } } ================================================ FILE: sqlx-postgres/src/types/bigdecimal-range.md ================================================ #### Note: `BigDecimal` Has a Larger Range than `NUMERIC` `BigDecimal` can represent values with a far, far greater range than the `NUMERIC` type in Postgres can. `NUMERIC` is limited to 131,072 digits before the decimal point, and 16,384 digits after it. See [Section 8.1, Numeric Types] of the Postgres manual for details. Meanwhile, `BigDecimal` can theoretically represent a value with an arbitrary number of decimal digits, albeit with a maximum of 263 significant figures. Because encoding in the current API design _must_ be infallible, when attempting to encode a `BigDecimal` that cannot fit in the wire representation of `NUMERIC`, SQLx may instead encode a sentinel value that falls outside the allowed range but is still representable. This will cause the query to return a `DatabaseError` with code `22P03` (`invalid_binary_representation`) and the error message `invalid scale in external "numeric" value` (though this may be subject to change). However, `BigDecimal` should be able to decode any `NUMERIC` value except `NaN`, for which it has no representation. [Section 8.1, Numeric Types]: https://www.postgresql.org/docs/current/datatype-numeric.html ================================================ FILE: sqlx-postgres/src/types/bigdecimal.rs ================================================ use bigdecimal::BigDecimal; use num_bigint::{BigInt, Sign}; use std::cmp; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::numeric::{PgNumeric, PgNumericSign}; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; impl Type for BigDecimal { fn type_info() -> PgTypeInfo { PgTypeInfo::NUMERIC } } impl PgHasArrayType for BigDecimal { fn array_type_info() -> PgTypeInfo { PgTypeInfo::NUMERIC_ARRAY } } impl TryFrom for BigDecimal { type Error = BoxDynError; fn try_from(numeric: PgNumeric) -> Result { Self::try_from(&numeric) } } impl TryFrom<&'_ PgNumeric> for BigDecimal { type Error = BoxDynError; fn try_from(numeric: &'_ PgNumeric) -> Result { let (digits, sign, weight) = match *numeric { PgNumeric::Number { ref digits, sign, weight, .. } => (digits, sign, weight), PgNumeric::NotANumber => { return Err("BigDecimal does not support NaN values".into()); } }; if digits.is_empty() { // Postgres returns an empty digit array for 0 but BigInt expects at least one zero return Ok(0u64.into()); } let sign = match sign { PgNumericSign::Positive => Sign::Plus, PgNumericSign::Negative => Sign::Minus, }; // weight is 0 if the decimal point falls after the first base-10000 digit // // `Vec` capacity cannot exceed `isize::MAX` bytes, so this cast can't wrap in practice. #[allow(clippy::cast_possible_wrap)] let scale = (digits.len() as i64 - weight as i64 - 1) * 4; // no optimized algorithm for base-10 so use base-100 for faster processing let mut cents = Vec::with_capacity(digits.len() * 2); #[allow( clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::cast_sign_loss )] for (i, &digit) in digits.iter().enumerate() { if !PgNumeric::is_valid_digit(digit) { return Err(format!( "PgNumeric to BigDecimal: {i}th digit is out of range {digit}" ) .into()); } cents.push((digit / 100) as u8); cents.push((digit % 100) as u8); } let bigint = BigInt::from_radix_be(sign, ¢s, 100) .ok_or("PgNumeric contained an out-of-range digit")?; Ok(BigDecimal::new(bigint, scale)) } } impl TryFrom<&'_ BigDecimal> for PgNumeric { type Error = BoxDynError; fn try_from(decimal: &BigDecimal) -> Result { let base_10_to_10000 = |chunk: &[u8]| chunk.iter().fold(0i16, |a, &d| a * 10 + d as i16); // NOTE: this unfortunately copies the BigInt internally let (integer, exp) = decimal.as_bigint_and_exponent(); // this routine is specifically optimized for base-10 // FIXME: is there a way to iterate over the digits to avoid the Vec allocation let (sign, base_10) = integer.to_radix_be(10); let base_10_len = i64::try_from(base_10.len()).map_err(|_| { format!( "BigDecimal base-10 length out of range for PgNumeric: {}", base_10.len() ) })?; // weight is positive power of 10000 // exp is the negative power of 10 let weight_10 = base_10_len - exp; // scale is only nonzero when we have fractional digits // since `exp` is the _negative_ decimal exponent, it tells us // exactly what our scale should be let scale: i16 = cmp::max(0, exp).try_into()?; // there's an implicit +1 offset in the interpretation let weight: i16 = if weight_10 <= 0 { weight_10 / 4 - 1 } else { // the `-1` is a fix for an off by 1 error (4 digits should still be 0 weight) (weight_10 - 1) / 4 } .try_into()?; let digits_len = if base_10.len() % 4 != 0 { base_10.len() / 4 + 1 } else { base_10.len() / 4 }; // For efficiency, we want to process the base-10 digits in chunks of 4, // but that means we need to deal with the non-divisible remainder first. let offset = weight_10.rem_euclid(4); // Do a checked conversion to the smallest integer, // so we can widen arbitrarily without triggering lints. let offset = u8::try_from(offset).unwrap_or_else(|_| { panic!("BUG: `offset` should be in the range [0, 4) but is {offset}") }); let mut digits = Vec::with_capacity(digits_len); if let Some(first) = base_10.get(..offset as usize) { if !first.is_empty() { digits.push(base_10_to_10000(first)); } } else if offset != 0 { // If we didn't hit the `if let Some` branch, // then `base_10.len()` must strictly be smaller #[allow(clippy::cast_possible_truncation)] let power = (offset as usize - base_10.len()) as u32; digits.push(base_10_to_10000(&base_10) * 10i16.pow(power)); } if let Some(rest) = base_10.get(offset as usize..) { // `chunk.len()` is always between 1 and 4 #[allow(clippy::cast_possible_truncation)] digits.extend( rest.chunks(4) .map(|chunk| base_10_to_10000(chunk) * 10i16.pow(4 - chunk.len() as u32)), ); } while let Some(&0) = digits.last() { digits.pop(); } Ok(PgNumeric::Number { sign: sign_to_pg(sign), scale, weight, digits, }) } } #[doc=include_str!("bigdecimal-range.md")] impl Encode<'_, Postgres> for BigDecimal { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { PgNumeric::try_from(self)?.encode(buf)?; Ok(IsNull::No) } fn size_hint(&self) -> usize { PgNumeric::size_hint(self.digits()) } } /// ### Note: `NaN` /// `BigDecimal` has a greater range than `NUMERIC` (see the corresponding `Encode` impl for details) /// but cannot represent `NaN`, so decoding may return an error. impl Decode<'_, Postgres> for BigDecimal { fn decode(value: PgValueRef<'_>) -> Result { match value.format() { PgValueFormat::Binary => PgNumeric::decode(value.as_bytes()?)?.try_into(), PgValueFormat::Text => Ok(value.as_str()?.parse::()?), } } } fn sign_to_pg(sign: Sign) -> PgNumericSign { match sign { Sign::Plus | Sign::NoSign => PgNumericSign::Positive, Sign::Minus => PgNumericSign::Negative, } } #[cfg(test)] #[allow(clippy::zero_prefixed_literal)] // Used for clarity mod tests { use super::{BigDecimal, PgNumeric, PgNumericSign}; use std::convert::TryFrom; #[test] fn zero() { let zero: BigDecimal = "0".parse().unwrap(); assert_eq!( PgNumeric::try_from(&zero).unwrap(), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 0, weight: 0, digits: vec![] } ); } #[test] fn one() { let one: BigDecimal = "1".parse().unwrap(); assert_eq!( PgNumeric::try_from(&one).unwrap(), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 0, weight: 0, digits: vec![1] } ); } #[test] fn ten() { let ten: BigDecimal = "10".parse().unwrap(); assert_eq!( PgNumeric::try_from(&ten).unwrap(), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 0, weight: 0, digits: vec![10] } ); } #[test] fn one_hundred() { let one_hundred: BigDecimal = "100".parse().unwrap(); assert_eq!( PgNumeric::try_from(&one_hundred).unwrap(), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 0, weight: 0, digits: vec![100] } ); } #[test] fn ten_thousand() { // BigDecimal doesn't normalize here let ten_thousand: BigDecimal = "10000".parse().unwrap(); assert_eq!( PgNumeric::try_from(&ten_thousand).unwrap(), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 0, weight: 1, digits: vec![1] } ); } #[test] fn two_digits() { let two_digits: BigDecimal = "12345".parse().unwrap(); assert_eq!( PgNumeric::try_from(&two_digits).unwrap(), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 0, weight: 1, digits: vec![1, 2345] } ); } #[test] fn one_tenth() { let one_tenth: BigDecimal = "0.1".parse().unwrap(); assert_eq!( PgNumeric::try_from(&one_tenth).unwrap(), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 1, weight: -1, digits: vec![1000] } ); } #[test] fn one_hundredth() { let one_hundredth: BigDecimal = "0.01".parse().unwrap(); assert_eq!( PgNumeric::try_from(&one_hundredth).unwrap(), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 2, weight: -1, digits: vec![100] } ); } #[test] fn twelve_thousandths() { let twelve_thousandths: BigDecimal = "0.012".parse().unwrap(); assert_eq!( PgNumeric::try_from(&twelve_thousandths).unwrap(), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 3, weight: -1, digits: vec![120] } ); } #[test] fn decimal_1() { let decimal: BigDecimal = "1.2345".parse().unwrap(); assert_eq!( PgNumeric::try_from(&decimal).unwrap(), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 4, weight: 0, digits: vec![1, 2345] } ); } #[test] fn decimal_2() { let decimal: BigDecimal = "0.12345".parse().unwrap(); assert_eq!( PgNumeric::try_from(&decimal).unwrap(), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 5, weight: -1, digits: vec![1234, 5000] } ); } #[test] fn decimal_3() { let decimal: BigDecimal = "0.01234".parse().unwrap(); assert_eq!( PgNumeric::try_from(&decimal).unwrap(), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 5, weight: -1, digits: vec![0123, 4000] } ); } #[test] fn decimal_4() { let decimal: BigDecimal = "12345.67890".parse().unwrap(); assert_eq!( PgNumeric::try_from(&decimal).unwrap(), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 5, weight: 1, digits: vec![1, 2345, 6789] } ); } #[test] fn one_digit_decimal() { let one_digit_decimal: BigDecimal = "0.00001234".parse().unwrap(); assert_eq!( PgNumeric::try_from(&one_digit_decimal).unwrap(), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 8, weight: -2, digits: vec![1234] } ); } #[test] fn issue_423_four_digit() { // This is a regression test for https://github.com/launchbadge/sqlx/issues/423 let four_digit: BigDecimal = "1234".parse().unwrap(); assert_eq!( PgNumeric::try_from(&four_digit).unwrap(), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 0, weight: 0, digits: vec![1234] } ); } #[test] fn issue_423_negative_four_digit() { // This is a regression test for https://github.com/launchbadge/sqlx/issues/423 let negative_four_digit: BigDecimal = "-1234".parse().unwrap(); assert_eq!( PgNumeric::try_from(&negative_four_digit).unwrap(), PgNumeric::Number { sign: PgNumericSign::Negative, scale: 0, weight: 0, digits: vec![1234] } ); } #[test] fn issue_423_eight_digit() { // This is a regression test for https://github.com/launchbadge/sqlx/issues/423 let eight_digit: BigDecimal = "12345678".parse().unwrap(); assert_eq!( PgNumeric::try_from(&eight_digit).unwrap(), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 0, weight: 1, digits: vec![1234, 5678] } ); } #[test] fn issue_423_negative_eight_digit() { // This is a regression test for https://github.com/launchbadge/sqlx/issues/423 let negative_eight_digit: BigDecimal = "-12345678".parse().unwrap(); assert_eq!( PgNumeric::try_from(&negative_eight_digit).unwrap(), PgNumeric::Number { sign: PgNumericSign::Negative, scale: 0, weight: 1, digits: vec![1234, 5678] } ); } } ================================================ FILE: sqlx-postgres/src/types/bit_vec.rs ================================================ use crate::arguments::value_size_int4_checked; use crate::{ decode::Decode, encode::{Encode, IsNull}, error::BoxDynError, types::Type, PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres, }; use bit_vec::BitVec; use sqlx_core::bytes::Buf; use std::{io, mem}; impl Type for BitVec { fn type_info() -> PgTypeInfo { PgTypeInfo::VARBIT } fn compatible(ty: &PgTypeInfo) -> bool { *ty == PgTypeInfo::BIT || *ty == PgTypeInfo::VARBIT } } impl PgHasArrayType for BitVec { fn array_type_info() -> PgTypeInfo { PgTypeInfo::VARBIT_ARRAY } fn array_compatible(ty: &PgTypeInfo) -> bool { *ty == PgTypeInfo::BIT_ARRAY || *ty == PgTypeInfo::VARBIT_ARRAY } } impl Encode<'_, Postgres> for BitVec { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { let len = value_size_int4_checked(self.len())?; buf.extend(len.to_be_bytes()); buf.extend(self.to_bytes()); Ok(IsNull::No) } fn size_hint(&self) -> usize { mem::size_of::() + self.len() } } impl Decode<'_, Postgres> for BitVec { fn decode(value: PgValueRef<'_>) -> Result { match value.format() { PgValueFormat::Binary => { let mut bytes = value.as_bytes()?; let len = bytes.get_i32(); let len = usize::try_from(len).map_err(|_| format!("invalid VARBIT len: {len}"))?; // The smallest amount of data we can read is one byte let bytes_len = len.div_ceil(8); if bytes.remaining() != bytes_len { Err(io::Error::new( io::ErrorKind::InvalidData, "VARBIT length mismatch.", ))?; } let mut bitvec = BitVec::from_bytes(bytes); // Chop off zeroes from the back. We get bits in bytes, so if // our bitvec is not in full bytes, extra zeroes are added to // the end. while bitvec.len() > len { bitvec.pop(); } Ok(bitvec) } PgValueFormat::Text => { let s = value.as_str()?; let mut bit_vec = BitVec::with_capacity(s.len()); for c in s.chars() { match c { '0' => bit_vec.push(false), '1' => bit_vec.push(true), _ => { Err(io::Error::new( io::ErrorKind::InvalidData, "VARBIT data contains other characters than 1 or 0.", ))?; } } } Ok(bit_vec) } } } } ================================================ FILE: sqlx-postgres/src/types/bool.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; impl Type for bool { fn type_info() -> PgTypeInfo { PgTypeInfo::BOOL } } impl PgHasArrayType for bool { fn array_type_info() -> PgTypeInfo { PgTypeInfo::BOOL_ARRAY } } impl Encode<'_, Postgres> for bool { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { buf.push(*self as u8); Ok(IsNull::No) } } impl Decode<'_, Postgres> for bool { fn decode(value: PgValueRef<'_>) -> Result { Ok(match value.format() { PgValueFormat::Binary => value.as_bytes()?[0] != 0, PgValueFormat::Text => match value.as_str()? { "t" => true, "f" => false, s => { return Err(format!("unexpected value {s:?} for boolean").into()); } }, }) } } ================================================ FILE: sqlx-postgres/src/types/bytes.rs ================================================ use std::borrow::Cow; use std::rc::Rc; use std::sync::Arc; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; impl PgHasArrayType for u8 { fn array_type_info() -> PgTypeInfo { PgTypeInfo::BYTEA } } impl PgHasArrayType for &'_ [u8] { fn array_type_info() -> PgTypeInfo { PgTypeInfo::BYTEA_ARRAY } } impl PgHasArrayType for Box<[u8]> { fn array_type_info() -> PgTypeInfo { <[&[u8]] as Type>::type_info() } } impl PgHasArrayType for Vec { fn array_type_info() -> PgTypeInfo { <[&[u8]] as Type>::type_info() } } impl PgHasArrayType for [u8; N] { fn array_type_info() -> PgTypeInfo { <[&[u8]] as Type>::type_info() } } impl Encode<'_, Postgres> for &'_ [u8] { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { buf.extend_from_slice(self); Ok(IsNull::No) } } impl Encode<'_, Postgres> for Vec { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { <&[u8] as Encode>::encode(self, buf) } } impl Encode<'_, Postgres> for [u8; N] { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { <&[u8] as Encode>::encode(self.as_slice(), buf) } } impl<'r> Decode<'r, Postgres> for &'r [u8] { fn decode(value: PgValueRef<'r>) -> Result { match value.format() { PgValueFormat::Binary => value.as_bytes(), PgValueFormat::Text => { Err("unsupported decode to `&[u8]` of BYTEA in a simple query; use a prepared query or decode to `Vec`".into()) } } } } fn text_hex_decode_input(value: PgValueRef<'_>) -> Result<&[u8], BoxDynError> { // BYTEA is formatted as \x followed by hex characters value .as_bytes()? .strip_prefix(b"\\x") .ok_or("text does not start with \\x") .map_err(Into::into) } impl Decode<'_, Postgres> for Vec { fn decode(value: PgValueRef<'_>) -> Result { Ok(match value.format() { PgValueFormat::Binary => value.as_bytes()?.to_owned(), PgValueFormat::Text => hex::decode(text_hex_decode_input(value)?)?, }) } } impl Decode<'_, Postgres> for [u8; N] { fn decode(value: PgValueRef<'_>) -> Result { let mut bytes = [0u8; N]; match value.format() { PgValueFormat::Binary => { bytes = value.as_bytes()?.try_into()?; } PgValueFormat::Text => hex::decode_to_slice(text_hex_decode_input(value)?, &mut bytes)?, }; Ok(bytes) } } forward_encode_impl!(Arc<[u8]>, &[u8], Postgres); forward_encode_impl!(Rc<[u8]>, &[u8], Postgres); forward_encode_impl!(Box<[u8]>, &[u8], Postgres); forward_encode_impl!(Cow<'_, [u8]>, &[u8], Postgres); ================================================ FILE: sqlx-postgres/src/types/chrono/date.rs ================================================ use std::mem; use chrono::{NaiveDate, TimeDelta}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; impl Type for NaiveDate { fn type_info() -> PgTypeInfo { PgTypeInfo::DATE } } impl PgHasArrayType for NaiveDate { fn array_type_info() -> PgTypeInfo { PgTypeInfo::DATE_ARRAY } } impl Encode<'_, Postgres> for NaiveDate { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { // DATE is encoded as the days since epoch let days: i32 = (*self - postgres_epoch_date()) .num_days() .try_into() .map_err(|_| { format!("value {self:?} would overflow binary encoding for Postgres DATE") })?; Encode::::encode(days, buf) } fn size_hint(&self) -> usize { mem::size_of::() } } impl<'r> Decode<'r, Postgres> for NaiveDate { fn decode(value: PgValueRef<'r>) -> Result { Ok(match value.format() { PgValueFormat::Binary => { // DATE is encoded as the days since epoch let days: i32 = Decode::::decode(value)?; let days = TimeDelta::try_days(days.into()) .unwrap_or_else(|| { unreachable!("BUG: days ({days}) as `i32` multiplied into seconds should not overflow `i64`") }); postgres_epoch_date() + days } PgValueFormat::Text => NaiveDate::parse_from_str(value.as_str()?, "%Y-%m-%d")?, }) } } #[inline] fn postgres_epoch_date() -> NaiveDate { NaiveDate::from_ymd_opt(2000, 1, 1).expect("expected 2000-01-01 to be a valid NaiveDate") } ================================================ FILE: sqlx-postgres/src/types/chrono/datetime.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use chrono::{ DateTime, Duration, FixedOffset, Local, NaiveDate, NaiveDateTime, Offset, TimeZone, Utc, }; use std::mem; impl Type for NaiveDateTime { fn type_info() -> PgTypeInfo { PgTypeInfo::TIMESTAMP } } impl Type for DateTime { fn type_info() -> PgTypeInfo { PgTypeInfo::TIMESTAMPTZ } } impl PgHasArrayType for NaiveDateTime { fn array_type_info() -> PgTypeInfo { PgTypeInfo::TIMESTAMP_ARRAY } } impl PgHasArrayType for DateTime { fn array_type_info() -> PgTypeInfo { PgTypeInfo::TIMESTAMPTZ_ARRAY } } impl Encode<'_, Postgres> for NaiveDateTime { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { // TIMESTAMP is encoded as the microseconds since the epoch let micros = (*self - postgres_epoch_datetime()) .num_microseconds() .ok_or_else(|| format!("NaiveDateTime out of range for Postgres: {self:?}"))?; Encode::::encode(micros, buf) } fn size_hint(&self) -> usize { mem::size_of::() } } impl<'r> Decode<'r, Postgres> for NaiveDateTime { fn decode(value: PgValueRef<'r>) -> Result { Ok(match value.format() { PgValueFormat::Binary => { // TIMESTAMP is encoded as the microseconds since the epoch let us = Decode::::decode(value)?; postgres_epoch_datetime() + Duration::microseconds(us) } PgValueFormat::Text => { let s = value.as_str()?; NaiveDateTime::parse_from_str( s, if s.contains('+') { // Contains a time-zone specifier // This is given for timestamptz for some reason // Postgres already guarantees this to always be UTC "%Y-%m-%d %H:%M:%S%.f%#z" } else { "%Y-%m-%d %H:%M:%S%.f" }, )? } }) } } impl Encode<'_, Postgres> for DateTime { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { Encode::::encode(self.naive_utc(), buf) } fn size_hint(&self) -> usize { mem::size_of::() } } impl<'r> Decode<'r, Postgres> for DateTime { fn decode(value: PgValueRef<'r>) -> Result { let fixed = as Decode>::decode(value)?; Ok(Local.from_utc_datetime(&fixed.naive_utc())) } } impl<'r> Decode<'r, Postgres> for DateTime { fn decode(value: PgValueRef<'r>) -> Result { let fixed = as Decode>::decode(value)?; Ok(Utc.from_utc_datetime(&fixed.naive_utc())) } } impl<'r> Decode<'r, Postgres> for DateTime { fn decode(value: PgValueRef<'r>) -> Result { Ok(match value.format() { PgValueFormat::Binary => { let naive = >::decode(value)?; Utc.fix().from_utc_datetime(&naive) } PgValueFormat::Text => { let s = value.as_str()?; DateTime::parse_from_str( s, if s.contains('+') || s.contains('-') { // Contains a time-zone specifier // This is given for timestamptz for some reason // Postgres already guarantees this to always be UTC "%Y-%m-%d %H:%M:%S%.f%#z" } else { "%Y-%m-%d %H:%M:%S%.f" }, )? } }) } } #[inline] fn postgres_epoch_datetime() -> NaiveDateTime { NaiveDate::from_ymd_opt(2000, 1, 1) .expect("expected 2000-01-01 to be a valid NaiveDate") .and_hms_opt(0, 0, 0) .expect("expected 2000-01-01T00:00:00 to be a valid NaiveDateTime") } ================================================ FILE: sqlx-postgres/src/types/chrono/mod.rs ================================================ mod date; mod datetime; mod time; ================================================ FILE: sqlx-postgres/src/types/chrono/time.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use chrono::{Duration, NaiveTime}; use std::mem; impl Type for NaiveTime { fn type_info() -> PgTypeInfo { PgTypeInfo::TIME } } impl PgHasArrayType for NaiveTime { fn array_type_info() -> PgTypeInfo { PgTypeInfo::TIME_ARRAY } } impl Encode<'_, Postgres> for NaiveTime { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { // TIME is encoded as the microseconds since midnight let micros = (*self - NaiveTime::default()) .num_microseconds() .ok_or_else(|| format!("Time out of range for PostgreSQL: {self}"))?; Encode::::encode(micros, buf) } fn size_hint(&self) -> usize { mem::size_of::() } } impl<'r> Decode<'r, Postgres> for NaiveTime { fn decode(value: PgValueRef<'r>) -> Result { Ok(match value.format() { PgValueFormat::Binary => { // TIME is encoded as the microseconds since midnight let us: i64 = Decode::::decode(value)?; NaiveTime::default() + Duration::microseconds(us) } PgValueFormat::Text => NaiveTime::parse_from_str(value.as_str()?, "%H:%M:%S%.f")?, }) } } #[test] fn check_naive_time_default_is_midnight() { // Just a canary in case this changes. assert_eq!( NaiveTime::from_hms_opt(0, 0, 0), Some(NaiveTime::default()), "implementation assumes `NaiveTime::default()` equals midnight" ); } ================================================ FILE: sqlx-postgres/src/types/citext.rs ================================================ use crate::types::array_compatible; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef, Postgres}; use sqlx_core::decode::Decode; use sqlx_core::encode::{Encode, IsNull}; use sqlx_core::error::BoxDynError; use sqlx_core::types::Type; use std::fmt; use std::fmt::{Debug, Display, Formatter}; use std::ops::Deref; use std::str::FromStr; /// Case-insensitive text (`citext`) support for Postgres. /// /// Note that SQLx considers the `citext` type to be compatible with `String` /// and its various derivatives, so direct usage of this type is generally unnecessary. /// /// However, it may be needed, for example, when binding a `citext[]` array, /// as Postgres will generally not accept a `text[]` array (mapped from `Vec`) in its place. /// /// See [the Postgres manual, Appendix F, Section 10][PG.F.10] for details on using `citext`. /// /// [PG.F.10]: https://www.postgresql.org/docs/current/citext.html /// /// ### Note: Extension Required /// The `citext` extension is not enabled by default in Postgres. You will need to do so explicitly: /// /// ```ignore /// CREATE EXTENSION IF NOT EXISTS "citext"; /// ``` /// /// ### Note: `PartialEq` is Case-Sensitive /// This type derives `PartialEq` which forwards to the implementation on `String`, which /// is case-sensitive. This impl exists mainly for testing. /// /// To properly emulate the case-insensitivity of `citext` would require use of locale-aware /// functions in `libc`, and even then would require querying the locale of the database server /// and setting it locally, which is unsafe. #[derive(Clone, Debug, Default, PartialEq)] pub struct PgCiText(pub String); impl Type for PgCiText { fn type_info() -> PgTypeInfo { // Since `citext` is enabled by an extension, it does not have a stable OID. PgTypeInfo::with_name("citext") } fn compatible(ty: &PgTypeInfo) -> bool { <&str as Type>::compatible(ty) } } impl Deref for PgCiText { type Target = str; fn deref(&self) -> &Self::Target { self.0.as_str() } } impl From for PgCiText { fn from(value: String) -> Self { Self(value) } } impl From for String { fn from(value: PgCiText) -> Self { value.0 } } impl FromStr for PgCiText { type Err = core::convert::Infallible; fn from_str(s: &str) -> Result { Ok(PgCiText(s.parse()?)) } } impl Display for PgCiText { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str(&self.0) } } impl PgHasArrayType for PgCiText { fn array_type_info() -> PgTypeInfo { PgTypeInfo::with_name("_citext") } fn array_compatible(ty: &PgTypeInfo) -> bool { array_compatible::<&str>(ty) } } impl Encode<'_, Postgres> for PgCiText { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { <&str as Encode>::encode(&**self, buf) } } impl Decode<'_, Postgres> for PgCiText { fn decode(value: PgValueRef<'_>) -> Result { Ok(PgCiText(value.as_str()?.to_owned())) } } ================================================ FILE: sqlx-postgres/src/types/cube.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use sqlx_core::bytes::Buf; use sqlx_core::Error; use std::mem; use std::str::FromStr; const BYTE_WIDTH: usize = 8; /// const MAX_DIMENSIONS: usize = 100; const IS_POINT_FLAG: u32 = 1 << 31; // FIXME(breaking): these variants are confusingly named and structured // consider changing them or making this an opaque wrapper around `Vec` #[derive(Debug, Clone, PartialEq)] pub enum PgCube { /// A one-dimensional point. // FIXME: `Point1D(f64)` Point(f64), /// An N-dimensional point ("represented internally as a zero-volume cube"). // FIXME: `PointND(f64)` ZeroVolume(Vec), /// A one-dimensional interval with starting and ending points. // FIXME: `Interval1D { start: f64, end: f64 }` OneDimensionInterval(f64, f64), // FIXME: add `Cube3D { lower_left: [f64; 3], upper_right: [f64; 3] }`? /// An N-dimensional cube with points representing lower-left and upper-right corners, respectively. // FIXME: `CubeND { lower_left: Vec, upper_right: Vec }` MultiDimension(Vec>), } #[derive(Copy, Clone, Debug, PartialEq, Eq)] struct Header { dimensions: usize, is_point: bool, } #[derive(Debug, thiserror::Error)] #[error("error decoding CUBE (is_point: {is_point}, dimensions: {dimensions})")] struct DecodeError { is_point: bool, dimensions: usize, message: String, } impl Type for PgCube { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("cube") } } impl PgHasArrayType for PgCube { fn array_type_info() -> PgTypeInfo { PgTypeInfo::with_name("_cube") } } impl<'r> Decode<'r, Postgres> for PgCube { fn decode(value: PgValueRef<'r>) -> Result> { match value.format() { PgValueFormat::Text => Ok(PgCube::from_str(value.as_str()?)?), PgValueFormat::Binary => Ok(PgCube::from_bytes(value.as_bytes()?)?), } } } impl Encode<'_, Postgres> for PgCube { fn produces(&self) -> Option { Some(PgTypeInfo::with_name("cube")) } fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { self.serialize(buf)?; Ok(IsNull::No) } fn size_hint(&self) -> usize { self.header().encoded_size() } } impl FromStr for PgCube { type Err = Error; fn from_str(s: &str) -> Result { let content = s .trim_start_matches('(') .trim_start_matches('[') .trim_end_matches(')') .trim_end_matches(']') .replace(' ', ""); if !content.contains('(') && !content.contains(',') { return parse_point(&content); } if !content.contains("),(") { return parse_zero_volume(&content); } let point_vecs = content.split("),(").collect::>(); if point_vecs.len() == 2 && !point_vecs.iter().any(|pv| pv.contains(',')) { return parse_one_dimensional_interval(point_vecs); } parse_multidimensional_interval(point_vecs) } } impl PgCube { fn header(&self) -> Header { match self { PgCube::Point(..) => Header { is_point: true, dimensions: 1, }, PgCube::ZeroVolume(values) => Header { is_point: true, dimensions: values.len(), }, PgCube::OneDimensionInterval(..) => Header { is_point: false, dimensions: 1, }, PgCube::MultiDimension(multi_values) => Header { is_point: false, dimensions: multi_values.first().map(|arr| arr.len()).unwrap_or(0), }, } } fn from_bytes(mut bytes: &[u8]) -> Result { let header = Header::try_read(&mut bytes)?; if bytes.len() != header.data_size() { return Err(DecodeError::new( &header, format!( "expected {} bytes after header, got {}", header.data_size(), bytes.len() ), ) .into()); } match (header.is_point, header.dimensions) { (true, 1) => Ok(PgCube::Point(bytes.get_f64())), (true, _) => Ok(PgCube::ZeroVolume( read_vec(&mut bytes).map_err(|e| DecodeError::new(&header, e))?, )), (false, 1) => Ok(PgCube::OneDimensionInterval( bytes.get_f64(), bytes.get_f64(), )), (false, _) => Ok(PgCube::MultiDimension(read_cube(&header, bytes)?)), } } fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), String> { let header = self.header(); buff.reserve(header.data_size()); header.try_write(buff)?; match self { PgCube::Point(value) => { buff.extend_from_slice(&value.to_be_bytes()); } PgCube::ZeroVolume(values) => { buff.extend(values.iter().flat_map(|v| v.to_be_bytes())); } PgCube::OneDimensionInterval(x, y) => { buff.extend_from_slice(&x.to_be_bytes()); buff.extend_from_slice(&y.to_be_bytes()); } PgCube::MultiDimension(multi_values) => { if multi_values.len() != 2 { return Err(format!("invalid CUBE value: {self:?}")); } buff.extend( multi_values .iter() .flat_map(|point| point.iter().flat_map(|scalar| scalar.to_be_bytes())), ); } }; Ok(()) } #[cfg(test)] fn serialize_to_vec(&self) -> Vec { let mut buff = PgArgumentBuffer::default(); self.serialize(&mut buff).unwrap(); buff.to_vec() } } fn read_vec(bytes: &mut &[u8]) -> Result, String> { if bytes.len() % BYTE_WIDTH != 0 { return Err(format!( "data length not divisible by {BYTE_WIDTH}: {}", bytes.len() )); } let mut out = Vec::with_capacity(bytes.len() / BYTE_WIDTH); while bytes.has_remaining() { out.push(bytes.get_f64()); } Ok(out) } fn read_cube(header: &Header, mut bytes: &[u8]) -> Result>, String> { if bytes.len() != header.data_size() { return Err(format!( "expected {} bytes, got {}", header.data_size(), bytes.len() )); } let mut out = Vec::with_capacity(2); // Expecting exactly 2 N-dimensional points for _ in 0..2 { let mut point = Vec::new(); for _ in 0..header.dimensions { point.push(bytes.get_f64()); } out.push(point); } Ok(out) } fn parse_float_from_str(s: &str, error_msg: &str) -> Result { s.parse().map_err(|_| Error::Decode(error_msg.into())) } fn parse_point(str: &str) -> Result { Ok(PgCube::Point(parse_float_from_str( str, "Failed to parse point", )?)) } fn parse_zero_volume(content: &str) -> Result { content .split(',') .map(|p| parse_float_from_str(p, "Failed to parse into zero-volume cube")) .collect::, _>>() .map(PgCube::ZeroVolume) } fn parse_one_dimensional_interval(point_vecs: Vec<&str>) -> Result { let x = parse_float_from_str( &remove_parentheses(point_vecs.first().ok_or(Error::Decode( format!("Could not decode cube interval x: {:?}", point_vecs).into(), ))?), "Failed to parse X in one-dimensional interval", )?; let y = parse_float_from_str( &remove_parentheses(point_vecs.get(1).ok_or(Error::Decode( format!("Could not decode cube interval y: {:?}", point_vecs).into(), ))?), "Failed to parse Y in one-dimensional interval", )?; Ok(PgCube::OneDimensionInterval(x, y)) } fn parse_multidimensional_interval(point_vecs: Vec<&str>) -> Result { point_vecs .iter() .map(|&point_vec| { point_vec .split(',') .map(|point| { parse_float_from_str( &remove_parentheses(point), "Failed to parse into multi-dimension cube", ) }) .collect::, _>>() }) .collect::, _>>() .map(PgCube::MultiDimension) } fn remove_parentheses(s: &str) -> String { s.trim_matches(|c| c == '(' || c == ')').to_string() } impl Header { const PACKED_WIDTH: usize = mem::size_of::(); fn encoded_size(&self) -> usize { Self::PACKED_WIDTH + self.data_size() } fn data_size(&self) -> usize { if self.is_point { self.dimensions * BYTE_WIDTH } else { self.dimensions * BYTE_WIDTH * 2 } } fn try_write(&self, buff: &mut PgArgumentBuffer) -> Result<(), String> { if self.dimensions > MAX_DIMENSIONS { return Err(format!( "CUBE dimensionality exceeds allowed maximum ({} > {MAX_DIMENSIONS})", self.dimensions )); } // Cannot overflow thanks to the above check. #[allow(clippy::cast_possible_truncation)] let mut packed = self.dimensions as u32; // https://github.com/postgres/postgres/blob/e3ec9dc1bf4983fcedb6f43c71ea12ee26aefc7a/contrib/cube/cubedata.h#L18-L24 if self.is_point { packed |= IS_POINT_FLAG; } buff.extend(packed.to_be_bytes()); Ok(()) } fn try_read(buf: &mut &[u8]) -> Result { if buf.len() < Self::PACKED_WIDTH { return Err(format!( "expected CUBE data to contain at least {} bytes, got {}", Self::PACKED_WIDTH, buf.len() )); } let packed = buf.get_u32(); let is_point = packed & IS_POINT_FLAG != 0; let dimensions = packed & !IS_POINT_FLAG; // can only overflow on 16-bit platforms let dimensions = usize::try_from(dimensions) .ok() .filter(|&it| it <= MAX_DIMENSIONS) .ok_or_else(|| format!("received CUBE data with higher than expected dimensionality: {dimensions} (is_point: {is_point})"))?; Ok(Self { is_point, dimensions, }) } } impl DecodeError { fn new(header: &Header, message: String) -> Self { DecodeError { is_point: header.is_point, dimensions: header.dimensions, message, } } } #[cfg(test)] mod cube_tests { use std::str::FromStr; use super::PgCube; const POINT_BYTES: &[u8] = &[128, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 0]; const ZERO_VOLUME_BYTES: &[u8] = &[ 128, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 64, 8, 0, 0, 0, 0, 0, 0, ]; const ONE_DIMENSIONAL_INTERVAL_BYTES: &[u8] = &[ 0, 0, 0, 1, 64, 28, 0, 0, 0, 0, 0, 0, 64, 32, 0, 0, 0, 0, 0, 0, ]; const MULTI_DIMENSION_2_DIM_BYTES: &[u8] = &[ 0, 0, 0, 2, 63, 240, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 64, 8, 0, 0, 0, 0, 0, 0, 64, 16, 0, 0, 0, 0, 0, 0, ]; const MULTI_DIMENSION_3_DIM_BYTES: &[u8] = &[ 0, 0, 0, 3, 64, 0, 0, 0, 0, 0, 0, 0, 64, 8, 0, 0, 0, 0, 0, 0, 64, 16, 0, 0, 0, 0, 0, 0, 64, 20, 0, 0, 0, 0, 0, 0, 64, 24, 0, 0, 0, 0, 0, 0, 64, 28, 0, 0, 0, 0, 0, 0, ]; #[test] fn can_deserialise_point_type_byes() { let cube = PgCube::from_bytes(POINT_BYTES).unwrap(); assert_eq!(cube, PgCube::Point(2.)) } #[test] fn can_deserialise_point_type_str() { let cube_1 = PgCube::from_str("(2)").unwrap(); assert_eq!(cube_1, PgCube::Point(2.)); let cube_2 = PgCube::from_str("2").unwrap(); assert_eq!(cube_2, PgCube::Point(2.)); } #[test] fn can_serialise_point_type() { assert_eq!(PgCube::Point(2.).serialize_to_vec(), POINT_BYTES,) } #[test] fn can_deserialise_zero_volume_bytes() { let cube = PgCube::from_bytes(ZERO_VOLUME_BYTES).unwrap(); assert_eq!(cube, PgCube::ZeroVolume(vec![2., 3.])); } #[test] fn can_deserialise_zero_volume_string() { let cube_1 = PgCube::from_str("(2,3,4)").unwrap(); assert_eq!(cube_1, PgCube::ZeroVolume(vec![2., 3., 4.])); let cube_2 = PgCube::from_str("2,3,4").unwrap(); assert_eq!(cube_2, PgCube::ZeroVolume(vec![2., 3., 4.])); } #[test] fn can_serialise_zero_volume() { assert_eq!( PgCube::ZeroVolume(vec![2., 3.]).serialize_to_vec(), ZERO_VOLUME_BYTES ); } #[test] fn can_deserialise_one_dimension_interval_bytes() { let cube = PgCube::from_bytes(ONE_DIMENSIONAL_INTERVAL_BYTES).unwrap(); assert_eq!(cube, PgCube::OneDimensionInterval(7., 8.)) } #[test] fn can_deserialise_one_dimension_interval_string() { let cube_1 = PgCube::from_str("((7),(8))").unwrap(); assert_eq!(cube_1, PgCube::OneDimensionInterval(7., 8.)); let cube_2 = PgCube::from_str("(7),(8)").unwrap(); assert_eq!(cube_2, PgCube::OneDimensionInterval(7., 8.)); } #[test] fn can_serialise_one_dimension_interval() { assert_eq!( PgCube::OneDimensionInterval(7., 8.).serialize_to_vec(), ONE_DIMENSIONAL_INTERVAL_BYTES ) } #[test] fn can_deserialise_multi_dimension_2_dimension_byte() { let cube = PgCube::from_bytes(MULTI_DIMENSION_2_DIM_BYTES).unwrap(); assert_eq!( cube, PgCube::MultiDimension(vec![vec![1., 2.], vec![3., 4.]]) ) } #[test] fn can_deserialise_multi_dimension_2_dimension_string() { let cube_1 = PgCube::from_str("((1,2),(3,4))").unwrap(); assert_eq!( cube_1, PgCube::MultiDimension(vec![vec![1., 2.], vec![3., 4.]]) ); let cube_2 = PgCube::from_str("((1, 2), (3, 4))").unwrap(); assert_eq!( cube_2, PgCube::MultiDimension(vec![vec![1., 2.], vec![3., 4.]]) ); let cube_3 = PgCube::from_str("(1,2),(3,4)").unwrap(); assert_eq!( cube_3, PgCube::MultiDimension(vec![vec![1., 2.], vec![3., 4.]]) ); let cube_4 = PgCube::from_str("(1, 2), (3, 4)").unwrap(); assert_eq!( cube_4, PgCube::MultiDimension(vec![vec![1., 2.], vec![3., 4.]]) ) } #[test] fn can_serialise_multi_dimension_2_dimension() { assert_eq!( PgCube::MultiDimension(vec![vec![1., 2.], vec![3., 4.]]).serialize_to_vec(), MULTI_DIMENSION_2_DIM_BYTES ) } #[test] fn can_deserialise_multi_dimension_3_dimension_bytes() { let cube = PgCube::from_bytes(MULTI_DIMENSION_3_DIM_BYTES).unwrap(); assert_eq!( cube, PgCube::MultiDimension(vec![vec![2., 3., 4.], vec![5., 6., 7.]]) ) } #[test] fn can_deserialise_multi_dimension_3_dimension_string() { let cube = PgCube::from_str("((2,3,4),(5,6,7))").unwrap(); assert_eq!( cube, PgCube::MultiDimension(vec![vec![2., 3., 4.], vec![5., 6., 7.]]) ); let cube_2 = PgCube::from_str("(2,3,4),(5,6,7)").unwrap(); assert_eq!( cube_2, PgCube::MultiDimension(vec![vec![2., 3., 4.], vec![5., 6., 7.]]) ); } #[test] fn can_serialise_multi_dimension_3_dimension() { assert_eq!( PgCube::MultiDimension(vec![vec![2., 3., 4.], vec![5., 6., 7.]]).serialize_to_vec(), MULTI_DIMENSION_3_DIM_BYTES ) } } ================================================ FILE: sqlx-postgres/src/types/float.rs ================================================ use byteorder::{BigEndian, ByteOrder}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; impl Type for f32 { fn type_info() -> PgTypeInfo { PgTypeInfo::FLOAT4 } } impl PgHasArrayType for f32 { fn array_type_info() -> PgTypeInfo { PgTypeInfo::FLOAT4_ARRAY } } impl Encode<'_, Postgres> for f32 { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { buf.extend(&self.to_be_bytes()); Ok(IsNull::No) } } impl Decode<'_, Postgres> for f32 { fn decode(value: PgValueRef<'_>) -> Result { Ok(match value.format() { PgValueFormat::Binary => BigEndian::read_f32(value.as_bytes()?), PgValueFormat::Text => value.as_str()?.parse()?, }) } } impl Type for f64 { fn type_info() -> PgTypeInfo { PgTypeInfo::FLOAT8 } } impl PgHasArrayType for f64 { fn array_type_info() -> PgTypeInfo { PgTypeInfo::FLOAT8_ARRAY } } impl Encode<'_, Postgres> for f64 { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { buf.extend(&self.to_be_bytes()); Ok(IsNull::No) } } impl Decode<'_, Postgres> for f64 { fn decode(value: PgValueRef<'_>) -> Result { Ok(match value.format() { PgValueFormat::Binary => BigEndian::read_f64(value.as_bytes()?), PgValueFormat::Text => value.as_str()?.parse()?, }) } } ================================================ FILE: sqlx-postgres/src/types/geometry/box.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use sqlx_core::bytes::Buf; use std::str::FromStr; const ERROR: &str = "error decoding BOX"; /// ## Postgres Geometric Box type /// /// Description: Rectangular box /// Representation: `((upper_right_x,upper_right_y),(lower_left_x,lower_left_y))` /// /// Boxes are represented by pairs of points that are opposite corners of the box. Values of type box are specified using any of the following syntaxes: /// /// ```text /// ( ( upper_right_x , upper_right_y ) , ( lower_left_x , lower_left_y ) ) /// ( upper_right_x , upper_right_y ) , ( lower_left_x , lower_left_y ) /// upper_right_x , upper_right_y , lower_left_x , lower_left_y /// ``` /// where `(upper_right_x,upper_right_y) and (lower_left_x,lower_left_y)` are any two opposite corners of the box. /// Any two opposite corners can be supplied on input, but the values will be reordered as needed to store the upper right and lower left corners, in that order. /// /// See [Postgres Manual, Section 8.8.4: Geometric Types - Boxes][PG.S.8.8.4] for details. /// /// [PG.S.8.8.4]: https://www.postgresql.org/docs/current/datatype-geometric.html#DATATYPE-GEOMETRIC-BOXES /// #[derive(Debug, Clone, PartialEq)] pub struct PgBox { pub upper_right_x: f64, pub upper_right_y: f64, pub lower_left_x: f64, pub lower_left_y: f64, } impl Type for PgBox { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("box") } } impl PgHasArrayType for PgBox { fn array_type_info() -> PgTypeInfo { PgTypeInfo::with_name("_box") } } impl<'r> Decode<'r, Postgres> for PgBox { fn decode(value: PgValueRef<'r>) -> Result> { match value.format() { PgValueFormat::Text => Ok(PgBox::from_str(value.as_str()?)?), PgValueFormat::Binary => Ok(PgBox::from_bytes(value.as_bytes()?)?), } } } impl Encode<'_, Postgres> for PgBox { fn produces(&self) -> Option { Some(PgTypeInfo::with_name("box")) } fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { self.serialize(buf)?; Ok(IsNull::No) } } impl FromStr for PgBox { type Err = BoxDynError; fn from_str(s: &str) -> Result { let sanitised = s.replace(['(', ')', '[', ']', ' '], ""); let mut parts = sanitised.split(','); let upper_right_x = parts .next() .and_then(|s| s.parse::().ok()) .ok_or_else(|| format!("{}: could not get upper_right_x from {}", ERROR, s))?; let upper_right_y = parts .next() .and_then(|s| s.parse::().ok()) .ok_or_else(|| format!("{}: could not get upper_right_y from {}", ERROR, s))?; let lower_left_x = parts .next() .and_then(|s| s.parse::().ok()) .ok_or_else(|| format!("{}: could not get lower_left_x from {}", ERROR, s))?; let lower_left_y = parts .next() .and_then(|s| s.parse::().ok()) .ok_or_else(|| format!("{}: could not get lower_left_y from {}", ERROR, s))?; if parts.next().is_some() { return Err(format!("{}: too many numbers inputted in {}", ERROR, s).into()); } Ok(PgBox { upper_right_x, upper_right_y, lower_left_x, lower_left_y, }) } } impl PgBox { fn from_bytes(mut bytes: &[u8]) -> Result { let upper_right_x = bytes.get_f64(); let upper_right_y = bytes.get_f64(); let lower_left_x = bytes.get_f64(); let lower_left_y = bytes.get_f64(); Ok(PgBox { upper_right_x, upper_right_y, lower_left_x, lower_left_y, }) } fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), String> { let min_x = &self.upper_right_x.min(self.lower_left_x); let min_y = &self.upper_right_y.min(self.lower_left_y); let max_x = &self.upper_right_x.max(self.lower_left_x); let max_y = &self.upper_right_y.max(self.lower_left_y); buff.extend_from_slice(&max_x.to_be_bytes()); buff.extend_from_slice(&max_y.to_be_bytes()); buff.extend_from_slice(&min_x.to_be_bytes()); buff.extend_from_slice(&min_y.to_be_bytes()); Ok(()) } #[cfg(test)] fn serialize_to_vec(&self) -> Vec { let mut buff = PgArgumentBuffer::default(); self.serialize(&mut buff).unwrap(); buff.to_vec() } } #[cfg(test)] mod box_tests { use std::str::FromStr; use super::PgBox; const BOX_BYTES: &[u8] = &[ 64, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, ]; #[test] fn can_deserialise_box_type_bytes_in_order() { let pg_box = PgBox::from_bytes(BOX_BYTES).unwrap(); assert_eq!( pg_box, PgBox { upper_right_x: 2., upper_right_y: 2., lower_left_x: -2., lower_left_y: -2. } ) } #[test] fn can_deserialise_box_type_str_first_syntax() { let pg_box = PgBox::from_str("[( 1, 2), (3, 4 )]").unwrap(); assert_eq!( pg_box, PgBox { upper_right_x: 1., upper_right_y: 2., lower_left_x: 3., lower_left_y: 4. } ); } #[test] fn can_deserialise_box_type_str_second_syntax() { let pg_box = PgBox::from_str("(( 1, 2), (3, 4 ))").unwrap(); assert_eq!( pg_box, PgBox { upper_right_x: 1., upper_right_y: 2., lower_left_x: 3., lower_left_y: 4. } ); } #[test] fn can_deserialise_box_type_str_third_syntax() { let pg_box = PgBox::from_str("(1, 2), (3, 4 )").unwrap(); assert_eq!( pg_box, PgBox { upper_right_x: 1., upper_right_y: 2., lower_left_x: 3., lower_left_y: 4. } ); } #[test] fn can_deserialise_box_type_str_fourth_syntax() { let pg_box = PgBox::from_str("1, 2, 3, 4").unwrap(); assert_eq!( pg_box, PgBox { upper_right_x: 1., upper_right_y: 2., lower_left_x: 3., lower_left_y: 4. } ); } #[test] fn cannot_deserialise_too_many_numbers() { let input_str = "1, 2, 3, 4, 5"; let pg_box = PgBox::from_str(input_str); assert!(pg_box.is_err()); if let Err(err) = pg_box { assert_eq!( err.to_string(), format!("error decoding BOX: too many numbers inputted in {input_str}") ) } } #[test] fn cannot_deserialise_too_few_numbers() { let input_str = "1, 2, 3 "; let pg_box = PgBox::from_str(input_str); assert!(pg_box.is_err()); if let Err(err) = pg_box { assert_eq!( err.to_string(), format!("error decoding BOX: could not get lower_left_y from {input_str}") ) } } #[test] fn cannot_deserialise_invalid_numbers() { let input_str = "1, 2, 3, FOUR"; let pg_box = PgBox::from_str(input_str); assert!(pg_box.is_err()); if let Err(err) = pg_box { assert_eq!( err.to_string(), format!("error decoding BOX: could not get lower_left_y from {input_str}") ) } } #[test] fn can_deserialise_box_type_str_float() { let pg_box = PgBox::from_str("(1.1, 2.2), (3.3, 4.4)").unwrap(); assert_eq!( pg_box, PgBox { upper_right_x: 1.1, upper_right_y: 2.2, lower_left_x: 3.3, lower_left_y: 4.4 } ); } #[test] fn can_serialise_box_type_in_order() { let pg_box = PgBox { upper_right_x: 2., lower_left_x: -2., upper_right_y: -2., lower_left_y: 2., }; assert_eq!(pg_box.serialize_to_vec(), BOX_BYTES,) } #[test] fn can_serialise_box_type_out_of_order() { let pg_box = PgBox { upper_right_x: -2., lower_left_x: 2., upper_right_y: 2., lower_left_y: -2., }; assert_eq!(pg_box.serialize_to_vec(), BOX_BYTES,) } #[test] fn can_order_box() { let pg_box = PgBox { upper_right_x: -2., lower_left_x: 2., upper_right_y: 2., lower_left_y: -2., }; let bytes = pg_box.serialize_to_vec(); let pg_box = PgBox::from_bytes(&bytes).unwrap(); assert_eq!( pg_box, PgBox { upper_right_x: 2., upper_right_y: 2., lower_left_x: -2., lower_left_y: -2. } ) } } ================================================ FILE: sqlx-postgres/src/types/geometry/circle.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use sqlx_core::bytes::Buf; use sqlx_core::Error; use std::str::FromStr; const ERROR: &str = "error decoding CIRCLE"; /// ## Postgres Geometric Circle type /// /// Description: Circle /// Representation: `< (x, y), radius >` (center point and radius) /// /// ```text /// < ( x , y ) , radius > /// ( ( x , y ) , radius ) /// ( x , y ) , radius /// x , y , radius /// ``` /// where `(x,y)` is the center point. /// /// See [Postgres Manual, Section 8.8.7, Geometric Types - Circles][PG.S.8.8.7] for details. /// /// [PG.S.8.8.7]: https://www.postgresql.org/docs/current/datatype-geometric.html#DATATYPE-CIRCLE /// #[derive(Debug, Clone, PartialEq)] pub struct PgCircle { pub x: f64, pub y: f64, pub radius: f64, } impl Type for PgCircle { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("circle") } } impl PgHasArrayType for PgCircle { fn array_type_info() -> PgTypeInfo { PgTypeInfo::with_name("_circle") } } impl<'r> Decode<'r, Postgres> for PgCircle { fn decode(value: PgValueRef<'r>) -> Result> { match value.format() { PgValueFormat::Text => Ok(PgCircle::from_str(value.as_str()?)?), PgValueFormat::Binary => Ok(PgCircle::from_bytes(value.as_bytes()?)?), } } } impl Encode<'_, Postgres> for PgCircle { fn produces(&self) -> Option { Some(PgTypeInfo::with_name("circle")) } fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { self.serialize(buf)?; Ok(IsNull::No) } } impl FromStr for PgCircle { type Err = BoxDynError; fn from_str(s: &str) -> Result { let sanitised = s.replace(['<', '>', '(', ')', ' '], ""); let mut parts = sanitised.split(','); let x = parts .next() .and_then(|s| s.trim().parse::().ok()) .ok_or_else(|| format!("{}: could not get x from {}", ERROR, s))?; let y = parts .next() .and_then(|s| s.trim().parse::().ok()) .ok_or_else(|| format!("{}: could not get y from {}", ERROR, s))?; let radius = parts .next() .and_then(|s| s.trim().parse::().ok()) .ok_or_else(|| format!("{}: could not get radius from {}", ERROR, s))?; if parts.next().is_some() { return Err(format!("{}: too many numbers inputted in {}", ERROR, s).into()); } if radius < 0. { return Err(format!("{}: cannot have negative radius: {}", ERROR, s).into()); } Ok(PgCircle { x, y, radius }) } } impl PgCircle { fn from_bytes(mut bytes: &[u8]) -> Result { let x = bytes.get_f64(); let y = bytes.get_f64(); let r = bytes.get_f64(); Ok(PgCircle { x, y, radius: r }) } fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), Error> { buff.extend_from_slice(&self.x.to_be_bytes()); buff.extend_from_slice(&self.y.to_be_bytes()); buff.extend_from_slice(&self.radius.to_be_bytes()); Ok(()) } #[cfg(test)] fn serialize_to_vec(&self) -> Vec { let mut buff = PgArgumentBuffer::default(); self.serialize(&mut buff).unwrap(); buff.to_vec() } } #[cfg(test)] mod circle_tests { use std::str::FromStr; use super::PgCircle; const CIRCLE_BYTES: &[u8] = &[ 63, 241, 153, 153, 153, 153, 153, 154, 64, 1, 153, 153, 153, 153, 153, 154, 64, 10, 102, 102, 102, 102, 102, 102, ]; #[test] fn can_deserialise_circle_type_bytes() { let circle = PgCircle::from_bytes(CIRCLE_BYTES).unwrap(); assert_eq!( circle, PgCircle { x: 1.1, y: 2.2, radius: 3.3 } ) } #[test] fn can_deserialise_circle_type_str() { let circle = PgCircle::from_str("<(1, 2), 3 >").unwrap(); assert_eq!( circle, PgCircle { x: 1.0, y: 2.0, radius: 3.0 } ); } #[test] fn can_deserialise_circle_type_str_second_syntax() { let circle = PgCircle::from_str("((1, 2), 3 )").unwrap(); assert_eq!( circle, PgCircle { x: 1.0, y: 2.0, radius: 3.0 } ); } #[test] fn can_deserialise_circle_type_str_third_syntax() { let circle = PgCircle::from_str("(1, 2), 3 ").unwrap(); assert_eq!( circle, PgCircle { x: 1.0, y: 2.0, radius: 3.0 } ); } #[test] fn can_deserialise_circle_type_str_fourth_syntax() { let circle = PgCircle::from_str("1, 2, 3 ").unwrap(); assert_eq!( circle, PgCircle { x: 1.0, y: 2.0, radius: 3.0 } ); } #[test] fn cannot_deserialise_circle_invalid_numbers() { let input_str = "1, 2, Three"; let circle = PgCircle::from_str(input_str); assert!(circle.is_err()); if let Err(err) = circle { assert_eq!( err.to_string(), format!("error decoding CIRCLE: could not get radius from {input_str}") ) } } #[test] fn cannot_deserialise_circle_negative_radius() { let input_str = "1, 2, -3"; let circle = PgCircle::from_str(input_str); assert!(circle.is_err()); if let Err(err) = circle { assert_eq!( err.to_string(), format!("error decoding CIRCLE: cannot have negative radius: {input_str}") ) } } #[test] fn can_deserialise_circle_type_str_float() { let circle = PgCircle::from_str("<(1.1, 2.2), 3.3>").unwrap(); assert_eq!( circle, PgCircle { x: 1.1, y: 2.2, radius: 3.3 } ); } #[test] fn can_serialise_circle_type() { let circle = PgCircle { x: 1.1, y: 2.2, radius: 3.3, }; assert_eq!(circle.serialize_to_vec(), CIRCLE_BYTES,) } } ================================================ FILE: sqlx-postgres/src/types/geometry/line.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use sqlx_core::bytes::Buf; use std::str::FromStr; const ERROR: &str = "error decoding LINE"; /// ## Postgres Geometric Line type /// /// Description: Infinite line /// Representation: `{A, B, C}` /// /// Lines are represented by the linear equation Ax + By + C = 0, where A and B are not both zero. /// /// See [Postgres Manual, Section 8.8.2, Geometric Types - Lines][PG.S.8.8.2] for details. /// /// [PG.S.8.8.2]: https://www.postgresql.org/docs/current/datatype-geometric.html#DATATYPE-LINE /// #[derive(Debug, Clone, PartialEq)] pub struct PgLine { pub a: f64, pub b: f64, pub c: f64, } impl Type for PgLine { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("line") } } impl PgHasArrayType for PgLine { fn array_type_info() -> PgTypeInfo { PgTypeInfo::with_name("_line") } } impl<'r> Decode<'r, Postgres> for PgLine { fn decode(value: PgValueRef<'r>) -> Result> { match value.format() { PgValueFormat::Text => Ok(PgLine::from_str(value.as_str()?)?), PgValueFormat::Binary => Ok(PgLine::from_bytes(value.as_bytes()?)?), } } } impl Encode<'_, Postgres> for PgLine { fn produces(&self) -> Option { Some(PgTypeInfo::with_name("line")) } fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { self.serialize(buf)?; Ok(IsNull::No) } } impl FromStr for PgLine { type Err = BoxDynError; fn from_str(s: &str) -> Result { let mut parts = s .trim_matches(|c| c == '{' || c == '}' || c == ' ') .split(','); let a = parts .next() .and_then(|s| s.trim().parse::().ok()) .ok_or_else(|| format!("{}: could not get a from {}", ERROR, s))?; let b = parts .next() .and_then(|s| s.trim().parse::().ok()) .ok_or_else(|| format!("{}: could not get b from {}", ERROR, s))?; let c = parts .next() .and_then(|s| s.trim().parse::().ok()) .ok_or_else(|| format!("{}: could not get c from {}", ERROR, s))?; if parts.next().is_some() { return Err(format!("{}: too many numbers inputted in {}", ERROR, s).into()); } Ok(PgLine { a, b, c }) } } impl PgLine { fn from_bytes(mut bytes: &[u8]) -> Result { let a = bytes.get_f64(); let b = bytes.get_f64(); let c = bytes.get_f64(); Ok(PgLine { a, b, c }) } fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), BoxDynError> { buff.extend_from_slice(&self.a.to_be_bytes()); buff.extend_from_slice(&self.b.to_be_bytes()); buff.extend_from_slice(&self.c.to_be_bytes()); Ok(()) } #[cfg(test)] fn serialize_to_vec(&self) -> Vec { let mut buff = PgArgumentBuffer::default(); self.serialize(&mut buff).unwrap(); buff.to_vec() } } #[cfg(test)] mod line_tests { use std::str::FromStr; use super::PgLine; const LINE_BYTES: &[u8] = &[ 63, 241, 153, 153, 153, 153, 153, 154, 64, 1, 153, 153, 153, 153, 153, 154, 64, 10, 102, 102, 102, 102, 102, 102, ]; #[test] fn can_deserialise_line_type_bytes() { let line = PgLine::from_bytes(LINE_BYTES).unwrap(); assert_eq!( line, PgLine { a: 1.1, b: 2.2, c: 3.3 } ) } #[test] fn can_deserialise_line_type_str() { let line = PgLine::from_str("{ 1, 2, 3 }").unwrap(); assert_eq!( line, PgLine { a: 1.0, b: 2.0, c: 3.0 } ); } #[test] fn cannot_deserialise_line_too_few_numbers() { let input_str = "{ 1, 2 }"; let line = PgLine::from_str(input_str); assert!(line.is_err()); if let Err(err) = line { assert_eq!( err.to_string(), format!("error decoding LINE: could not get c from {input_str}") ) } } #[test] fn cannot_deserialise_line_too_many_numbers() { let input_str = "{ 1, 2, 3, 4 }"; let line = PgLine::from_str(input_str); assert!(line.is_err()); if let Err(err) = line { assert_eq!( err.to_string(), format!("error decoding LINE: too many numbers inputted in {input_str}") ) } } #[test] fn cannot_deserialise_line_invalid_numbers() { let input_str = "{ 1, 2, three }"; let line = PgLine::from_str(input_str); assert!(line.is_err()); if let Err(err) = line { assert_eq!( err.to_string(), format!("error decoding LINE: could not get c from {input_str}") ) } } #[test] fn can_deserialise_line_type_str_float() { let line = PgLine::from_str("{1.1, 2.2, 3.3}").unwrap(); assert_eq!( line, PgLine { a: 1.1, b: 2.2, c: 3.3 } ); } #[test] fn can_serialise_line_type() { let line = PgLine { a: 1.1, b: 2.2, c: 3.3, }; assert_eq!(line.serialize_to_vec(), LINE_BYTES,) } } ================================================ FILE: sqlx-postgres/src/types/geometry/line_segment.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use sqlx_core::bytes::Buf; use std::str::FromStr; const ERROR: &str = "error decoding LSEG"; /// ## Postgres Geometric Line Segment type /// /// Description: Finite line segment /// Representation: `((start_x,start_y),(end_x,end_y))` /// /// /// Line segments are represented by pairs of points that are the endpoints of the segment. Values of type lseg are specified using any of the following syntaxes: /// ```text /// [ ( start_x , start_y ) , ( end_x , end_y ) ] /// ( ( start_x , start_y ) , ( end_x , end_y ) ) /// ( start_x , start_y ) , ( end_x , end_y ) /// start_x , start_y , end_x , end_y /// ``` /// where `(start_x,start_y) and (end_x,end_y)` are the end points of the line segment. /// /// See [Postgres Manual, Section 8.8.3, Geometric Types - Line Segments][PG.S.8.8.3] for details. /// /// [PG.S.8.8.3]: https://www.postgresql.org/docs/current/datatype-geometric.html#DATATYPE-LSEG /// #[doc(alias = "line segment")] #[derive(Debug, Clone, PartialEq)] pub struct PgLSeg { pub start_x: f64, pub start_y: f64, pub end_x: f64, pub end_y: f64, } impl Type for PgLSeg { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("lseg") } } impl PgHasArrayType for PgLSeg { fn array_type_info() -> PgTypeInfo { PgTypeInfo::with_name("_lseg") } } impl<'r> Decode<'r, Postgres> for PgLSeg { fn decode(value: PgValueRef<'r>) -> Result> { match value.format() { PgValueFormat::Text => Ok(PgLSeg::from_str(value.as_str()?)?), PgValueFormat::Binary => Ok(PgLSeg::from_bytes(value.as_bytes()?)?), } } } impl Encode<'_, Postgres> for PgLSeg { fn produces(&self) -> Option { Some(PgTypeInfo::with_name("lseg")) } fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { self.serialize(buf)?; Ok(IsNull::No) } } impl FromStr for PgLSeg { type Err = BoxDynError; fn from_str(s: &str) -> Result { let sanitised = s.replace(['(', ')', '[', ']', ' '], ""); let mut parts = sanitised.split(','); let start_x = parts .next() .and_then(|s| s.parse::().ok()) .ok_or_else(|| format!("{}: could not get start_x from {}", ERROR, s))?; let start_y = parts .next() .and_then(|s| s.parse::().ok()) .ok_or_else(|| format!("{}: could not get start_y from {}", ERROR, s))?; let end_x = parts .next() .and_then(|s| s.parse::().ok()) .ok_or_else(|| format!("{}: could not get end_x from {}", ERROR, s))?; let end_y = parts .next() .and_then(|s| s.parse::().ok()) .ok_or_else(|| format!("{}: could not get end_y from {}", ERROR, s))?; if parts.next().is_some() { return Err(format!("{}: too many numbers inputted in {}", ERROR, s).into()); } Ok(PgLSeg { start_x, start_y, end_x, end_y, }) } } impl PgLSeg { fn from_bytes(mut bytes: &[u8]) -> Result { let start_x = bytes.get_f64(); let start_y = bytes.get_f64(); let end_x = bytes.get_f64(); let end_y = bytes.get_f64(); Ok(PgLSeg { start_x, start_y, end_x, end_y, }) } fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), BoxDynError> { buff.extend_from_slice(&self.start_x.to_be_bytes()); buff.extend_from_slice(&self.start_y.to_be_bytes()); buff.extend_from_slice(&self.end_x.to_be_bytes()); buff.extend_from_slice(&self.end_y.to_be_bytes()); Ok(()) } #[cfg(test)] fn serialize_to_vec(&self) -> Vec { let mut buff = PgArgumentBuffer::default(); self.serialize(&mut buff).unwrap(); buff.to_vec() } } #[cfg(test)] mod lseg_tests { use std::str::FromStr; use super::PgLSeg; const LINE_SEGMENT_BYTES: &[u8] = &[ 63, 241, 153, 153, 153, 153, 153, 154, 64, 1, 153, 153, 153, 153, 153, 154, 64, 10, 102, 102, 102, 102, 102, 102, 64, 17, 153, 153, 153, 153, 153, 154, ]; #[test] fn can_deserialise_lseg_type_bytes() { let lseg = PgLSeg::from_bytes(LINE_SEGMENT_BYTES).unwrap(); assert_eq!( lseg, PgLSeg { start_x: 1.1, start_y: 2.2, end_x: 3.3, end_y: 4.4 } ) } #[test] fn can_deserialise_lseg_type_str_first_syntax() { let lseg = PgLSeg::from_str("[( 1, 2), (3, 4 )]").unwrap(); assert_eq!( lseg, PgLSeg { start_x: 1., start_y: 2., end_x: 3., end_y: 4. } ); } #[test] fn can_deserialise_lseg_type_str_second_syntax() { let lseg = PgLSeg::from_str("(( 1, 2), (3, 4 ))").unwrap(); assert_eq!( lseg, PgLSeg { start_x: 1., start_y: 2., end_x: 3., end_y: 4. } ); } #[test] fn can_deserialise_lseg_type_str_third_syntax() { let lseg = PgLSeg::from_str("(1, 2), (3, 4 )").unwrap(); assert_eq!( lseg, PgLSeg { start_x: 1., start_y: 2., end_x: 3., end_y: 4. } ); } #[test] fn can_deserialise_lseg_type_str_fourth_syntax() { let lseg = PgLSeg::from_str("1, 2, 3, 4").unwrap(); assert_eq!( lseg, PgLSeg { start_x: 1., start_y: 2., end_x: 3., end_y: 4. } ); } #[test] fn can_deserialise_too_many_numbers() { let input_str = "1, 2, 3, 4, 5"; let lseg = PgLSeg::from_str(input_str); assert!(lseg.is_err()); if let Err(err) = lseg { assert_eq!( err.to_string(), format!("error decoding LSEG: too many numbers inputted in {input_str}") ) } } #[test] fn can_deserialise_too_few_numbers() { let input_str = "1, 2, 3"; let lseg = PgLSeg::from_str(input_str); assert!(lseg.is_err()); if let Err(err) = lseg { assert_eq!( err.to_string(), format!("error decoding LSEG: could not get end_y from {input_str}") ) } } #[test] fn can_deserialise_invalid_numbers() { let input_str = "1, 2, 3, FOUR"; let lseg = PgLSeg::from_str(input_str); assert!(lseg.is_err()); if let Err(err) = lseg { assert_eq!( err.to_string(), format!("error decoding LSEG: could not get end_y from {input_str}") ) } } #[test] fn can_deserialise_lseg_type_str_float() { let lseg = PgLSeg::from_str("(1.1, 2.2), (3.3, 4.4)").unwrap(); assert_eq!( lseg, PgLSeg { start_x: 1.1, start_y: 2.2, end_x: 3.3, end_y: 4.4 } ); } #[test] fn can_serialise_lseg_type() { let lseg = PgLSeg { start_x: 1.1, start_y: 2.2, end_x: 3.3, end_y: 4.4, }; assert_eq!(lseg.serialize_to_vec(), LINE_SEGMENT_BYTES,) } } ================================================ FILE: sqlx-postgres/src/types/geometry/mod.rs ================================================ pub mod r#box; pub mod circle; pub mod line; pub mod line_segment; pub mod path; pub mod point; pub mod polygon; ================================================ FILE: sqlx-postgres/src/types/geometry/path.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::{PgPoint, Type}; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use sqlx_core::bytes::Buf; use sqlx_core::Error; use std::mem; use std::str::FromStr; const BYTE_WIDTH: usize = mem::size_of::(); /// ## Postgres Geometric Path type /// /// Description: Open path or Closed path (similar to polygon) /// Representation: Open `[(x1,y1),...]`, Closed `((x1,y1),...)` /// /// Paths are represented by lists of connected points. Paths can be open, where the first and last points in the list are considered not connected, or closed, where the first and last points are considered connected. /// Values of type path are specified using any of the following syntaxes: /// ```text /// [ ( x1 , y1 ) , ... , ( xn , yn ) ] /// ( ( x1 , y1 ) , ... , ( xn , yn ) ) /// ( x1 , y1 ) , ... , ( xn , yn ) /// ( x1 , y1 , ... , xn , yn ) /// x1 , y1 , ... , xn , yn /// ``` /// where the points are the end points of the line segments comprising the path. Square brackets `([])` indicate an open path, while parentheses `(())` indicate a closed path. /// When the outermost parentheses are omitted, as in the third through fifth syntaxes, a closed path is assumed. /// /// See [Postgres Manual, Section 8.8.5, Geometric Types - Paths][PG.S.8.8.5] for details. /// /// [PG.S.8.8.5]: https://www.postgresql.org/docs/current/datatype-geometric.html#DATATYPE-GEOMETRIC-PATHS /// #[derive(Debug, Clone, PartialEq)] pub struct PgPath { pub closed: bool, pub points: Vec, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] struct Header { is_closed: bool, length: usize, } impl Type for PgPath { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("path") } } impl PgHasArrayType for PgPath { fn array_type_info() -> PgTypeInfo { PgTypeInfo::with_name("_path") } } impl<'r> Decode<'r, Postgres> for PgPath { fn decode(value: PgValueRef<'r>) -> Result> { match value.format() { PgValueFormat::Text => Ok(PgPath::from_str(value.as_str()?)?), PgValueFormat::Binary => Ok(PgPath::from_bytes(value.as_bytes()?)?), } } } impl Encode<'_, Postgres> for PgPath { fn produces(&self) -> Option { Some(PgTypeInfo::with_name("path")) } fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { self.serialize(buf)?; Ok(IsNull::No) } } impl FromStr for PgPath { type Err = Error; fn from_str(s: &str) -> Result { let closed = !s.contains('['); let sanitised = s.replace(['(', ')', '[', ']', ' '], ""); let parts = sanitised.split(',').collect::>(); let mut points = vec![]; if parts.len() % 2 != 0 { return Err(Error::Decode( format!("Unmatched pair in PATH: {}", s).into(), )); } for chunk in parts.chunks_exact(2) { if let [x_str, y_str] = chunk { let x = parse_float_from_str(x_str, "could not get x")?; let y = parse_float_from_str(y_str, "could not get y")?; let point = PgPoint { x, y }; points.push(point); } } if !points.is_empty() { return Ok(PgPath { points, closed }); } Err(Error::Decode( format!("could not get path from {}", s).into(), )) } } impl PgPath { fn header(&self) -> Header { Header { is_closed: self.closed, length: self.points.len(), } } fn from_bytes(mut bytes: &[u8]) -> Result { let header = Header::try_read(&mut bytes)?; if bytes.len() != header.data_size() { return Err(format!( "expected {} bytes after header, got {}", header.data_size(), bytes.len() ) .into()); } if bytes.len() % BYTE_WIDTH * 2 != 0 { return Err(format!( "data length not divisible by pairs of {BYTE_WIDTH}: {}", bytes.len() ) .into()); } let mut out_points = Vec::with_capacity(bytes.len() / (BYTE_WIDTH * 2)); while bytes.has_remaining() { let point = PgPoint { x: bytes.get_f64(), y: bytes.get_f64(), }; out_points.push(point) } Ok(PgPath { closed: header.is_closed, points: out_points, }) } fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), BoxDynError> { let header = self.header(); buff.reserve(header.data_size()); header.try_write(buff)?; for point in &self.points { buff.extend_from_slice(&point.x.to_be_bytes()); buff.extend_from_slice(&point.y.to_be_bytes()); } Ok(()) } #[cfg(test)] fn serialize_to_vec(&self) -> Vec { let mut buff = PgArgumentBuffer::default(); self.serialize(&mut buff).unwrap(); buff.to_vec() } } impl Header { const HEADER_WIDTH: usize = mem::size_of::() + mem::size_of::(); fn data_size(&self) -> usize { self.length * BYTE_WIDTH * 2 } fn try_read(buf: &mut &[u8]) -> Result { if buf.len() < Self::HEADER_WIDTH { return Err(format!( "expected PATH data to contain at least {} bytes, got {}", Self::HEADER_WIDTH, buf.len() )); } let is_closed = buf.get_i8(); let length = buf.get_i32(); let length = usize::try_from(length).ok().ok_or_else(|| { format!( "received PATH data length: {length}. Expected length between 0 and {}", usize::MAX ) })?; Ok(Self { is_closed: is_closed != 0, length, }) } fn try_write(&self, buff: &mut PgArgumentBuffer) -> Result<(), String> { let is_closed = self.is_closed as i8; let length = i32::try_from(self.length).map_err(|_| { format!( "PATH length exceeds allowed maximum ({} > {})", self.length, i32::MAX ) })?; buff.extend(is_closed.to_be_bytes()); buff.extend(length.to_be_bytes()); Ok(()) } } fn parse_float_from_str(s: &str, error_msg: &str) -> Result { s.parse().map_err(|_| Error::Decode(error_msg.into())) } #[cfg(test)] mod path_tests { use std::str::FromStr; use crate::types::PgPoint; use super::PgPath; const PATH_CLOSED_BYTES: &[u8] = &[ 1, 0, 0, 0, 2, 63, 240, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 64, 8, 0, 0, 0, 0, 0, 0, 64, 16, 0, 0, 0, 0, 0, 0, ]; const PATH_OPEN_BYTES: &[u8] = &[ 0, 0, 0, 0, 2, 63, 240, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 64, 8, 0, 0, 0, 0, 0, 0, 64, 16, 0, 0, 0, 0, 0, 0, ]; const PATH_UNEVEN_POINTS: &[u8] = &[ 0, 0, 0, 0, 2, 63, 240, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 64, 8, 0, 0, 0, 0, 0, 0, 64, 16, 0, 0, ]; #[test] fn can_deserialise_path_type_bytes_closed() { let path = PgPath::from_bytes(PATH_CLOSED_BYTES).unwrap(); assert_eq!( path, PgPath { closed: true, points: vec![PgPoint { x: 1.0, y: 2.0 }, PgPoint { x: 3.0, y: 4.0 }] } ) } #[test] fn cannot_deserialise_path_type_uneven_point_bytes() { let path = PgPath::from_bytes(PATH_UNEVEN_POINTS); assert!(path.is_err()); if let Err(err) = path { assert_eq!( err.to_string(), format!("expected 32 bytes after header, got 28") ) } } #[test] fn can_deserialise_path_type_bytes_open() { let path = PgPath::from_bytes(PATH_OPEN_BYTES).unwrap(); assert_eq!( path, PgPath { closed: false, points: vec![PgPoint { x: 1.0, y: 2.0 }, PgPoint { x: 3.0, y: 4.0 }] } ) } #[test] fn can_deserialise_path_type_str_first_syntax() { let path = PgPath::from_str("[( 1, 2), (3, 4 )]").unwrap(); assert_eq!( path, PgPath { closed: false, points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }] } ); } #[test] fn cannot_deserialise_path_type_str_uneven_points_first_syntax() { let input_str = "[( 1, 2), (3)]"; let path = PgPath::from_str(input_str); assert!(path.is_err()); if let Err(err) = path { assert_eq!( err.to_string(), format!("error occurred while decoding: Unmatched pair in PATH: {input_str}") ) } } #[test] fn can_deserialise_path_type_str_second_syntax() { let path = PgPath::from_str("(( 1, 2), (3, 4 ))").unwrap(); assert_eq!( path, PgPath { closed: true, points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }] } ); } #[test] fn can_deserialise_path_type_str_third_syntax() { let path = PgPath::from_str("(1, 2), (3, 4 )").unwrap(); assert_eq!( path, PgPath { closed: true, points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }] } ); } #[test] fn can_deserialise_path_type_str_fourth_syntax() { let path = PgPath::from_str("1, 2, 3, 4").unwrap(); assert_eq!( path, PgPath { closed: true, points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }] } ); } #[test] fn can_deserialise_path_type_str_float() { let path = PgPath::from_str("(1.1, 2.2), (3.3, 4.4)").unwrap(); assert_eq!( path, PgPath { closed: true, points: vec![PgPoint { x: 1.1, y: 2.2 }, PgPoint { x: 3.3, y: 4.4 }] } ); } #[test] fn can_serialise_path_type() { let path = PgPath { closed: true, points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }], }; assert_eq!(path.serialize_to_vec(), PATH_CLOSED_BYTES,) } } ================================================ FILE: sqlx-postgres/src/types/geometry/point.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use sqlx_core::bytes::Buf; use sqlx_core::Error; use std::str::FromStr; /// ## Postgres Geometric Point type /// /// Description: Point on a plane /// Representation: `(x, y)` /// /// Points are the fundamental two-dimensional building block for geometric types. Values of type point are specified using either of the following syntaxes: /// ```text /// ( x , y ) /// x , y /// ```` /// where x and y are the respective coordinates, as floating-point numbers. /// /// See [Postgres Manual, Section 8.8.1, Geometric Types - Points][PG.S.8.8.1] for details. /// /// [PG.S.8.8.1]: https://www.postgresql.org/docs/current/datatype-geometric.html#DATATYPE-GEOMETRIC-POINTS /// #[derive(Debug, Clone, PartialEq)] pub struct PgPoint { pub x: f64, pub y: f64, } impl Type for PgPoint { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("point") } } impl PgHasArrayType for PgPoint { fn array_type_info() -> PgTypeInfo { PgTypeInfo::with_name("_point") } } impl<'r> Decode<'r, Postgres> for PgPoint { fn decode(value: PgValueRef<'r>) -> Result> { match value.format() { PgValueFormat::Text => Ok(PgPoint::from_str(value.as_str()?)?), PgValueFormat::Binary => Ok(PgPoint::from_bytes(value.as_bytes()?)?), } } } impl Encode<'_, Postgres> for PgPoint { fn produces(&self) -> Option { Some(PgTypeInfo::with_name("point")) } fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { self.serialize(buf)?; Ok(IsNull::No) } } fn parse_float_from_str(s: &str, error_msg: &str) -> Result { s.trim() .parse() .map_err(|_| Error::Decode(error_msg.into())) } impl FromStr for PgPoint { type Err = BoxDynError; fn from_str(s: &str) -> Result { let (x_str, y_str) = s .trim_matches(|c| c == '(' || c == ')' || c == ' ') .split_once(',') .ok_or_else(|| format!("error decoding POINT: could not get x and y from {}", s))?; let x = parse_float_from_str(x_str, "error decoding POINT: could not get x")?; let y = parse_float_from_str(y_str, "error decoding POINT: could not get y")?; Ok(PgPoint { x, y }) } } impl PgPoint { fn from_bytes(mut bytes: &[u8]) -> Result { let x = bytes.get_f64(); let y = bytes.get_f64(); Ok(PgPoint { x, y }) } fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), BoxDynError> { buff.extend_from_slice(&self.x.to_be_bytes()); buff.extend_from_slice(&self.y.to_be_bytes()); Ok(()) } #[cfg(test)] fn serialize_to_vec(&self) -> Vec { let mut buff = PgArgumentBuffer::default(); self.serialize(&mut buff).unwrap(); buff.to_vec() } } #[cfg(test)] mod point_tests { use std::str::FromStr; use super::PgPoint; const POINT_BYTES: &[u8] = &[ 64, 0, 204, 204, 204, 204, 204, 205, 64, 20, 204, 204, 204, 204, 204, 205, ]; #[test] fn can_deserialise_point_type_bytes() { let point = PgPoint::from_bytes(POINT_BYTES).unwrap(); assert_eq!(point, PgPoint { x: 2.1, y: 5.2 }) } #[test] fn can_deserialise_point_type_str() { let point = PgPoint::from_str("(2, 3)").unwrap(); assert_eq!(point, PgPoint { x: 2., y: 3. }); } #[test] fn can_deserialise_point_type_str_float() { let point = PgPoint::from_str("(2.5, 3.4)").unwrap(); assert_eq!(point, PgPoint { x: 2.5, y: 3.4 }); } #[test] fn can_serialise_point_type() { let point = PgPoint { x: 2.1, y: 5.2 }; assert_eq!(point.serialize_to_vec(), POINT_BYTES,) } } ================================================ FILE: sqlx-postgres/src/types/geometry/polygon.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::{PgPoint, Type}; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use sqlx_core::bytes::Buf; use sqlx_core::Error; use std::mem; use std::str::FromStr; const BYTE_WIDTH: usize = mem::size_of::(); /// ## Postgres Geometric Polygon type /// /// Description: Polygon (similar to closed polygon) /// Representation: `((x1,y1),...)` /// /// Polygons are represented by lists of points (the vertices of the polygon). Polygons are very similar to closed paths; the essential semantic difference is that a polygon is considered to include the area within it, while a path is not. /// An important implementation difference between polygons and paths is that the stored representation of a polygon includes its smallest bounding box. This speeds up certain search operations, although computing the bounding box adds overhead while constructing new polygons. /// Values of type polygon are specified using any of the following syntaxes: /// /// ```text /// ( ( x1 , y1 ) , ... , ( xn , yn ) ) /// ( x1 , y1 ) , ... , ( xn , yn ) /// ( x1 , y1 , ... , xn , yn ) /// x1 , y1 , ... , xn , yn /// ``` /// /// where the points are the end points of the line segments comprising the boundary of the polygon. /// /// See [Postgres Manual, Section 8.8.6, Geometric Types - Polygons][PG.S.8.8.6] for details. /// /// [PG.S.8.8.6]: https://www.postgresql.org/docs/current/datatype-geometric.html#DATATYPE-POLYGON /// #[derive(Debug, Clone, PartialEq)] pub struct PgPolygon { pub points: Vec, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] struct Header { length: usize, } impl Type for PgPolygon { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("polygon") } } impl PgHasArrayType for PgPolygon { fn array_type_info() -> PgTypeInfo { PgTypeInfo::with_name("_polygon") } } impl<'r> Decode<'r, Postgres> for PgPolygon { fn decode(value: PgValueRef<'r>) -> Result> { match value.format() { PgValueFormat::Text => Ok(PgPolygon::from_str(value.as_str()?)?), PgValueFormat::Binary => Ok(PgPolygon::from_bytes(value.as_bytes()?)?), } } } impl Encode<'_, Postgres> for PgPolygon { fn produces(&self) -> Option { Some(PgTypeInfo::with_name("polygon")) } fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { self.serialize(buf)?; Ok(IsNull::No) } } impl FromStr for PgPolygon { type Err = Error; fn from_str(s: &str) -> Result { let sanitised = s.replace(['(', ')', '[', ']', ' '], ""); let parts = sanitised.split(',').collect::>(); let mut points = vec![]; if parts.len() % 2 != 0 { return Err(Error::Decode( format!("Unmatched pair in POLYGON: {}", s).into(), )); } for chunk in parts.chunks_exact(2) { if let [x_str, y_str] = chunk { let x = parse_float_from_str(x_str, "could not get x")?; let y = parse_float_from_str(y_str, "could not get y")?; let point = PgPoint { x, y }; points.push(point); } } if !points.is_empty() { return Ok(PgPolygon { points }); } Err(Error::Decode( format!("could not get polygon from {}", s).into(), )) } } impl PgPolygon { fn header(&self) -> Header { Header { length: self.points.len(), } } fn from_bytes(mut bytes: &[u8]) -> Result { let header = Header::try_read(&mut bytes)?; if bytes.len() != header.data_size() { return Err(format!( "expected {} bytes after header, got {}", header.data_size(), bytes.len() ) .into()); } if bytes.len() % BYTE_WIDTH * 2 != 0 { return Err(format!( "data length not divisible by pairs of {BYTE_WIDTH}: {}", bytes.len() ) .into()); } let mut out_points = Vec::with_capacity(bytes.len() / (BYTE_WIDTH * 2)); while bytes.has_remaining() { let point = PgPoint { x: bytes.get_f64(), y: bytes.get_f64(), }; out_points.push(point) } Ok(PgPolygon { points: out_points }) } fn serialize(&self, buff: &mut PgArgumentBuffer) -> Result<(), BoxDynError> { let header = self.header(); buff.reserve(header.data_size()); header.try_write(buff)?; for point in &self.points { buff.extend_from_slice(&point.x.to_be_bytes()); buff.extend_from_slice(&point.y.to_be_bytes()); } Ok(()) } #[cfg(test)] fn serialize_to_vec(&self) -> Vec { let mut buff = PgArgumentBuffer::default(); self.serialize(&mut buff).unwrap(); buff.to_vec() } } impl Header { const HEADER_WIDTH: usize = mem::size_of::() + mem::size_of::(); fn data_size(&self) -> usize { self.length * BYTE_WIDTH * 2 } fn try_read(buf: &mut &[u8]) -> Result { if buf.len() < Self::HEADER_WIDTH { return Err(format!( "expected polygon data to contain at least {} bytes, got {}", Self::HEADER_WIDTH, buf.len() )); } let length = buf.get_i32(); let length = usize::try_from(length).ok().ok_or_else(|| { format!( "received polygon with length: {length}. Expected length between 0 and {}", usize::MAX ) })?; Ok(Self { length }) } fn try_write(&self, buff: &mut PgArgumentBuffer) -> Result<(), String> { let length = i32::try_from(self.length).map_err(|_| { format!( "polygon length exceeds allowed maximum ({} > {})", self.length, i32::MAX ) })?; buff.extend(length.to_be_bytes()); Ok(()) } } fn parse_float_from_str(s: &str, error_msg: &str) -> Result { s.parse().map_err(|_| Error::Decode(error_msg.into())) } #[cfg(test)] mod polygon_tests { use std::str::FromStr; use crate::types::PgPoint; use super::PgPolygon; const POLYGON_BYTES: &[u8] = &[ 0, 0, 0, 12, 192, 0, 0, 0, 0, 0, 0, 0, 192, 8, 0, 0, 0, 0, 0, 0, 191, 240, 0, 0, 0, 0, 0, 0, 192, 8, 0, 0, 0, 0, 0, 0, 191, 240, 0, 0, 0, 0, 0, 0, 191, 240, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 64, 8, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 64, 8, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 192, 8, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 192, 8, 0, 0, 0, 0, 0, 0, 63, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, 240, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 0, ]; #[test] fn can_deserialise_polygon_type_bytes() { let polygon = PgPolygon::from_bytes(POLYGON_BYTES).unwrap(); assert_eq!( polygon, PgPolygon { points: vec![ PgPoint { x: -2., y: -3. }, PgPoint { x: -1., y: -3. }, PgPoint { x: -1., y: -1. }, PgPoint { x: 1., y: 1. }, PgPoint { x: 1., y: 3. }, PgPoint { x: 2., y: 3. }, PgPoint { x: 2., y: -3. }, PgPoint { x: 1., y: -3. }, PgPoint { x: 1., y: 0. }, PgPoint { x: -1., y: 0. }, PgPoint { x: -1., y: -2. }, PgPoint { x: -2., y: -2. } ] } ) } #[test] fn can_deserialise_polygon_type_str_first_syntax() { let polygon = PgPolygon::from_str("[( 1, 2), (3, 4 )]").unwrap(); assert_eq!( polygon, PgPolygon { points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }] } ); } #[test] fn can_deserialise_polygon_type_str_second_syntax() { let polygon = PgPolygon::from_str("(( 1, 2), (3, 4 ))").unwrap(); assert_eq!( polygon, PgPolygon { points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }] } ); } #[test] fn cannot_deserialise_polygon_type_str_uneven_points_first_syntax() { let input_str = "[( 1, 2), (3)]"; let polygon = PgPolygon::from_str(input_str); assert!(polygon.is_err()); if let Err(err) = polygon { assert_eq!( err.to_string(), format!("error occurred while decoding: Unmatched pair in POLYGON: {input_str}") ) } } #[test] fn cannot_deserialise_polygon_type_str_invalid_numbers() { let input_str = "[( 1, 2), (2, three)]"; let polygon = PgPolygon::from_str(input_str); assert!(polygon.is_err()); if let Err(err) = polygon { assert_eq!( err.to_string(), format!("error occurred while decoding: could not get y") ) } } #[test] fn can_deserialise_polygon_type_str_third_syntax() { let polygon = PgPolygon::from_str("(1, 2), (3, 4 )").unwrap(); assert_eq!( polygon, PgPolygon { points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }] } ); } #[test] fn can_deserialise_polygon_type_str_fourth_syntax() { let polygon = PgPolygon::from_str("1, 2, 3, 4").unwrap(); assert_eq!( polygon, PgPolygon { points: vec![PgPoint { x: 1., y: 2. }, PgPoint { x: 3., y: 4. }] } ); } #[test] fn can_deserialise_polygon_type_str_float() { let polygon = PgPolygon::from_str("(1.1, 2.2), (3.3, 4.4)").unwrap(); assert_eq!( polygon, PgPolygon { points: vec![PgPoint { x: 1.1, y: 2.2 }, PgPoint { x: 3.3, y: 4.4 }] } ); } #[test] fn can_serialise_polygon_type() { let polygon = PgPolygon { points: vec![ PgPoint { x: -2., y: -3. }, PgPoint { x: -1., y: -3. }, PgPoint { x: -1., y: -1. }, PgPoint { x: 1., y: 1. }, PgPoint { x: 1., y: 3. }, PgPoint { x: 2., y: 3. }, PgPoint { x: 2., y: -3. }, PgPoint { x: 1., y: -3. }, PgPoint { x: 1., y: 0. }, PgPoint { x: -1., y: 0. }, PgPoint { x: -1., y: -2. }, PgPoint { x: -2., y: -2. }, ], }; assert_eq!(polygon.serialize_to_vec(), POLYGON_BYTES,) } } ================================================ FILE: sqlx-postgres/src/types/hstore.rs ================================================ use std::{ collections::{btree_map, BTreeMap}, mem, ops::{Deref, DerefMut}, str, }; use crate::{ decode::Decode, encode::{Encode, IsNull}, error::BoxDynError, types::Type, PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef, Postgres, }; use sqlx_core::bytes::Buf; /// Key-value support (`hstore`) for Postgres. /// /// SQLx currently maps `hstore` to a `BTreeMap>` but this may be expanded in /// future to allow for user defined types. /// /// See [the Postgres manual, Appendix F, Section 18][PG.F.18] /// /// [PG.F.18]: https://www.postgresql.org/docs/current/hstore.html /// /// ### Note: Requires Postgres 8.3+ /// Introduced as a method for storing unstructured data, the `hstore` extension was first added in /// Postgres 8.3. /// /// /// ### Note: Extension Required /// The `hstore` extension is not enabled by default in Postgres. You will need to do so explicitly: /// /// ```ignore /// CREATE EXTENSION IF NOT EXISTS hstore; /// ``` /// /// # Examples /// /// ``` /// # use sqlx_postgres::types::PgHstore; /// // Shows basic usage of the PgHstore type. /// // /// #[derive(Clone, Debug, Default, Eq, PartialEq)] /// struct UserCreate<'a> { /// username: &'a str, /// password: &'a str, /// additional_data: PgHstore /// } /// /// let mut new_user = UserCreate { /// username: "name.surname@email.com", /// password: "@super_secret_1", /// ..Default::default() /// }; /// /// new_user.additional_data.insert("department".to_string(), Some("IT".to_string())); /// new_user.additional_data.insert("equipment_issued".to_string(), None); /// ``` /// ```ignore /// query_scalar::<_, i64>( /// "insert into user(username, password, additional_data) values($1, $2, $3) returning id" /// ) /// .bind(new_user.username) /// .bind(new_user.password) /// .bind(new_user.additional_data) /// .fetch_one(pg_conn) /// .await?; /// ``` /// /// ``` /// # use sqlx_postgres::types::PgHstore; /// // PgHstore implements FromIterator to simplify construction. /// // /// let additional_data = PgHstore::from_iter([ /// ("department".to_string(), Some("IT".to_string())), /// ("equipment_issued".to_string(), None), /// ]); /// /// assert_eq!(additional_data["department"], Some("IT".to_string())); /// assert_eq!(additional_data["equipment_issued"], None); /// /// // Also IntoIterator for ease of iteration. /// // /// for (key, value) in additional_data { /// println!("{key}: {value:?}"); /// } /// ``` /// #[derive(Clone, Debug, Default, Eq, PartialEq)] #[cfg_attr(feature = "offline", derive(serde::Serialize, serde::Deserialize))] pub struct PgHstore(pub BTreeMap>); impl Deref for PgHstore { type Target = BTreeMap>; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for PgHstore { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl FromIterator<(String, String)> for PgHstore { fn from_iter>(iter: T) -> Self { iter.into_iter().map(|(k, v)| (k, Some(v))).collect() } } impl FromIterator<(String, Option)> for PgHstore { fn from_iter)>>(iter: T) -> Self { let mut result = Self::default(); for (key, value) in iter { result.0.insert(key, value); } result } } impl IntoIterator for PgHstore { type Item = (String, Option); type IntoIter = btree_map::IntoIter>; fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl Type for PgHstore { fn type_info() -> PgTypeInfo { PgTypeInfo::with_name("hstore") } } impl PgHasArrayType for PgHstore { fn array_type_info() -> PgTypeInfo { PgTypeInfo::array_of("hstore") } } impl<'r> Decode<'r, Postgres> for PgHstore { fn decode(value: PgValueRef<'r>) -> Result { let mut buf = <&[u8] as Decode>::decode(value)?; let len = read_length(&mut buf)?; let len = usize::try_from(len).map_err(|_| format!("PgHstore: length out of range: {len}"))?; let mut result = Self::default(); for i in 0..len { let key = read_string(&mut buf) .map_err(|e| format!("PgHstore: error reading {i}th key: {e}"))? .ok_or_else(|| format!("PgHstore: expected {i}th key, got nothing"))?; let value = read_string(&mut buf) .map_err(|e| format!("PgHstore: error reading value for key {key:?}: {e}"))?; result.insert(key, value); } if !buf.is_empty() { tracing::warn!("{} unread bytes at the end of HSTORE value", buf.len()); } Ok(result) } } impl Encode<'_, Postgres> for PgHstore { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { buf.extend_from_slice(&i32::to_be_bytes( self.0 .len() .try_into() .map_err(|_| format!("PgHstore length out of range: {}", self.0.len()))?, )); for (i, (key, val)) in self.0.iter().enumerate() { let key_bytes = key.as_bytes(); let key_len = i32::try_from(key_bytes.len()).map_err(|_| { // Doesn't make sense to print the key itself: it's more than 2 GiB long! format!( "PgHstore: length of {i}th key out of range: {} bytes", key_bytes.len() ) })?; buf.extend_from_slice(&i32::to_be_bytes(key_len)); buf.extend_from_slice(key_bytes); match val { Some(val) => { let val_bytes = val.as_bytes(); let val_len = i32::try_from(val_bytes.len()).map_err(|_| { format!( "PgHstore: value length for key {key:?} out of range: {} bytes", val_bytes.len() ) })?; buf.extend_from_slice(&i32::to_be_bytes(val_len)); buf.extend_from_slice(val_bytes); } None => { buf.extend_from_slice(&i32::to_be_bytes(-1)); } } } Ok(IsNull::No) } } fn read_length(buf: &mut &[u8]) -> Result { if buf.len() < mem::size_of::() { return Err(format!( "expected {} bytes, got {}", mem::size_of::(), buf.len() )); } Ok(buf.get_i32()) } fn read_string(buf: &mut &[u8]) -> Result, String> { let len = read_length(buf)?; match len { -1 => Ok(None), len => { let len = usize::try_from(len).map_err(|_| format!("string length out of range: {len}"))?; if buf.len() < len { return Err(format!("expected {len} bytes, got {}", buf.len())); } let (val, rest) = buf.split_at(len); *buf = rest; Ok(Some( str::from_utf8(val).map_err(|e| e.to_string())?.to_string(), )) } } } #[cfg(test)] mod test { use super::*; use crate::PgValueFormat; const EMPTY: &str = "00000000"; const NAME_SURNAME_AGE: &str = "0000000300000003616765ffffffff000000046e616d65000000044a6f686e000000077375726e616d6500000003446f65"; #[test] fn hstore_deserialize_ok() { let empty = hex::decode(EMPTY).unwrap(); let name_surname_age = hex::decode(NAME_SURNAME_AGE).unwrap(); let empty = PgValueRef { value: Some(empty.as_slice()), row: None, type_info: PgTypeInfo::with_name("hstore"), format: PgValueFormat::Binary, }; let name_surname = PgValueRef { value: Some(name_surname_age.as_slice()), row: None, type_info: PgTypeInfo::with_name("hstore"), format: PgValueFormat::Binary, }; let res_empty = PgHstore::decode(empty).unwrap(); let res_name_surname = PgHstore::decode(name_surname).unwrap(); assert!(res_empty.is_empty()); assert_eq!(res_name_surname["name"], Some("John".to_string())); assert_eq!(res_name_surname["surname"], Some("Doe".to_string())); assert_eq!(res_name_surname["age"], None); } #[test] #[should_panic(expected = "PgHstore: length out of range: -5")] fn hstore_deserialize_buffer_length_error() { let buf = PgValueRef { value: Some(&[255, 255, 255, 251]), row: None, type_info: PgTypeInfo::with_name("hstore"), format: PgValueFormat::Binary, }; PgHstore::decode(buf).unwrap(); } #[test] fn hstore_serialize_ok() { let mut buff = PgArgumentBuffer::default(); let _ = PgHstore::from_iter::<[(String, String); 0]>([]) .encode_by_ref(&mut buff) .unwrap(); assert_eq!(hex::encode(buff.as_slice()), EMPTY); buff.clear(); let _ = PgHstore::from_iter([ ("name".to_string(), Some("John".to_string())), ("surname".to_string(), Some("Doe".to_string())), ("age".to_string(), None), ]) .encode_by_ref(&mut buff) .unwrap(); assert_eq!(hex::encode(buff.as_slice()), NAME_SURNAME_AGE); } } ================================================ FILE: sqlx-postgres/src/types/int.rs ================================================ use byteorder::{BigEndian, ByteOrder}; use std::num::{NonZeroI16, NonZeroI32, NonZeroI64}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; fn int_decode(value: PgValueRef<'_>) -> Result { Ok(match value.format() { PgValueFormat::Text => value.as_str()?.parse()?, PgValueFormat::Binary => { let buf = value.as_bytes()?; // Return error if buf is empty or is more than 8 bytes match buf.len() { 0 => { return Err("Value Buffer found empty while decoding to integer type".into()); } buf_len @ 9.. => { return Err(format!( "Value Buffer exceeds 8 bytes while decoding to integer type. Buffer size = {} bytes ", buf_len ) .into()); } _ => {} } BigEndian::read_int(buf, buf.len()) } }) } impl Type for i8 { fn type_info() -> PgTypeInfo { PgTypeInfo::CHAR } } impl PgHasArrayType for i8 { fn array_type_info() -> PgTypeInfo { PgTypeInfo::CHAR_ARRAY } } impl Encode<'_, Postgres> for i8 { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { buf.extend(&self.to_be_bytes()); Ok(IsNull::No) } } impl Decode<'_, Postgres> for i8 { fn decode(value: PgValueRef<'_>) -> Result { // note: decoding here is for the `"char"` type as Postgres does not have a native 1-byte integer type. // https://github.com/postgres/postgres/blob/master/src/backend/utils/adt/char.c#L58-L60 match value.format() { PgValueFormat::Binary => int_decode(value)?.try_into().map_err(Into::into), PgValueFormat::Text => { let text = value.as_str()?; // A value of 0 is represented with the empty string. if text.is_empty() { return Ok(0); } if text.starts_with('\\') { // For values between 0x80 and 0xFF, it's encoded in octal. return Ok(i8::from_str_radix(text.trim_start_matches('\\'), 8)?); } // Wrapping is the whole idea. #[allow(clippy::cast_possible_wrap)] Ok(text.as_bytes()[0] as i8) } } } } impl Type for i16 { fn type_info() -> PgTypeInfo { PgTypeInfo::INT2 } } impl PgHasArrayType for i16 { fn array_type_info() -> PgTypeInfo { PgTypeInfo::INT2_ARRAY } } impl Encode<'_, Postgres> for i16 { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { buf.extend(&self.to_be_bytes()); Ok(IsNull::No) } } impl Decode<'_, Postgres> for i16 { fn decode(value: PgValueRef<'_>) -> Result { int_decode(value)?.try_into().map_err(Into::into) } } impl Type for i32 { fn type_info() -> PgTypeInfo { PgTypeInfo::INT4 } } impl PgHasArrayType for i32 { fn array_type_info() -> PgTypeInfo { PgTypeInfo::INT4_ARRAY } } impl Encode<'_, Postgres> for i32 { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { buf.extend(&self.to_be_bytes()); Ok(IsNull::No) } } impl Decode<'_, Postgres> for i32 { fn decode(value: PgValueRef<'_>) -> Result { int_decode(value)?.try_into().map_err(Into::into) } } impl Type for i64 { fn type_info() -> PgTypeInfo { PgTypeInfo::INT8 } } impl PgHasArrayType for i64 { fn array_type_info() -> PgTypeInfo { PgTypeInfo::INT8_ARRAY } } impl Encode<'_, Postgres> for i64 { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { buf.extend(&self.to_be_bytes()); Ok(IsNull::No) } } impl Decode<'_, Postgres> for i64 { fn decode(value: PgValueRef<'_>) -> Result { int_decode(value) } } impl PgHasArrayType for NonZeroI16 { fn array_type_info() -> PgTypeInfo { PgTypeInfo::INT2_ARRAY } } impl PgHasArrayType for NonZeroI32 { fn array_type_info() -> PgTypeInfo { PgTypeInfo::INT4_ARRAY } } impl PgHasArrayType for NonZeroI64 { fn array_type_info() -> PgTypeInfo { PgTypeInfo::INT8_ARRAY } } ================================================ FILE: sqlx-postgres/src/types/interval.rs ================================================ use std::mem; use byteorder::{NetworkEndian, ReadBytesExt}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; // `PgInterval` is available for direct access to the INTERVAL type #[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, Default)] pub struct PgInterval { pub months: i32, pub days: i32, pub microseconds: i64, } impl Type for PgInterval { fn type_info() -> PgTypeInfo { PgTypeInfo::INTERVAL } } impl PgHasArrayType for PgInterval { fn array_type_info() -> PgTypeInfo { PgTypeInfo::INTERVAL_ARRAY } } impl<'de> Decode<'de, Postgres> for PgInterval { fn decode(value: PgValueRef<'de>) -> Result { match value.format() { PgValueFormat::Binary => { let mut buf = value.as_bytes()?; let microseconds = buf.read_i64::()?; let days = buf.read_i32::()?; let months = buf.read_i32::()?; Ok(PgInterval { months, days, microseconds, }) } // TODO: Implement parsing of text mode PgValueFormat::Text => { Err("not implemented: decode `INTERVAL` in text mode (unprepared queries)".into()) } } } } impl Encode<'_, Postgres> for PgInterval { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { buf.extend(&self.microseconds.to_be_bytes()); buf.extend(&self.days.to_be_bytes()); buf.extend(&self.months.to_be_bytes()); Ok(IsNull::No) } fn size_hint(&self) -> usize { 2 * mem::size_of::() } } // We then implement Encode + Type for std Duration, chrono Duration, and time Duration // This is to enable ease-of-use for encoding when its simple impl Type for std::time::Duration { fn type_info() -> PgTypeInfo { PgTypeInfo::INTERVAL } } impl PgHasArrayType for std::time::Duration { fn array_type_info() -> PgTypeInfo { PgTypeInfo::INTERVAL_ARRAY } } impl Encode<'_, Postgres> for std::time::Duration { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { PgInterval::try_from(*self)?.encode_by_ref(buf) } fn size_hint(&self) -> usize { 2 * mem::size_of::() } } impl TryFrom for PgInterval { type Error = BoxDynError; /// Convert a `std::time::Duration` to a `PgInterval` /// /// This returns an error if there is a loss of precision using nanoseconds or if there is a /// microsecond overflow. fn try_from(value: std::time::Duration) -> Result { if value.as_nanos() % 1000 != 0 { return Err("PostgreSQL `INTERVAL` does not support nanoseconds precision".into()); } Ok(Self { months: 0, days: 0, microseconds: value.as_micros().try_into()?, }) } } #[cfg(feature = "chrono")] impl Type for chrono::Duration { fn type_info() -> PgTypeInfo { PgTypeInfo::INTERVAL } } #[cfg(feature = "chrono")] impl PgHasArrayType for chrono::Duration { fn array_type_info() -> PgTypeInfo { PgTypeInfo::INTERVAL_ARRAY } } #[cfg(feature = "chrono")] impl Encode<'_, Postgres> for chrono::Duration { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { let pg_interval = PgInterval::try_from(*self)?; pg_interval.encode_by_ref(buf) } fn size_hint(&self) -> usize { 2 * mem::size_of::() } } #[cfg(feature = "chrono")] impl TryFrom for PgInterval { type Error = BoxDynError; /// Convert a `chrono::Duration` to a `PgInterval`. /// /// This returns an error if there is a loss of precision using nanoseconds or if there is a /// nanosecond overflow. fn try_from(value: chrono::Duration) -> Result { value .num_nanoseconds() .map_or::, _>( Err("Overflow has occurred for PostgreSQL `INTERVAL`".into()), |nanoseconds| { if nanoseconds % 1000 != 0 { return Err( "PostgreSQL `INTERVAL` does not support nanoseconds precision".into(), ); } Ok(()) }, )?; value.num_microseconds().map_or( Err("Overflow has occurred for PostgreSQL `INTERVAL`".into()), |microseconds| { Ok(Self { months: 0, days: 0, microseconds, }) }, ) } } #[cfg(feature = "time")] impl Type for time::Duration { fn type_info() -> PgTypeInfo { PgTypeInfo::INTERVAL } } #[cfg(feature = "time")] impl PgHasArrayType for time::Duration { fn array_type_info() -> PgTypeInfo { PgTypeInfo::INTERVAL_ARRAY } } #[cfg(feature = "time")] impl Encode<'_, Postgres> for time::Duration { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { let pg_interval = PgInterval::try_from(*self)?; pg_interval.encode_by_ref(buf) } fn size_hint(&self) -> usize { 2 * mem::size_of::() } } #[cfg(feature = "time")] impl TryFrom for PgInterval { type Error = BoxDynError; /// Convert a `time::Duration` to a `PgInterval`. /// /// This returns an error if there is a loss of precision using nanoseconds or if there is a /// microsecond overflow. fn try_from(value: time::Duration) -> Result { if value.whole_nanoseconds() % 1000 != 0 { return Err("PostgreSQL `INTERVAL` does not support nanoseconds precision".into()); } Ok(Self { months: 0, days: 0, microseconds: value.whole_microseconds().try_into()?, }) } } #[test] fn test_encode_interval() { let mut buf = PgArgumentBuffer::default(); let interval = PgInterval { months: 0, days: 0, microseconds: 0, }; assert!(matches!( Encode::::encode(interval, &mut buf), Ok(IsNull::No) )); assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); buf.clear(); let interval = PgInterval { months: 0, days: 0, microseconds: 1_000, }; assert!(matches!( Encode::::encode(interval, &mut buf), Ok(IsNull::No) )); assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 3, 232, 0, 0, 0, 0, 0, 0, 0, 0]); buf.clear(); let interval = PgInterval { months: 0, days: 0, microseconds: 1_000_000, }; assert!(matches!( Encode::::encode(interval, &mut buf), Ok(IsNull::No) )); assert_eq!(&**buf, [0, 0, 0, 0, 0, 15, 66, 64, 0, 0, 0, 0, 0, 0, 0, 0]); buf.clear(); let interval = PgInterval { months: 0, days: 0, microseconds: 3_600_000_000, }; assert!(matches!( Encode::::encode(interval, &mut buf), Ok(IsNull::No) )); assert_eq!( &**buf, [0, 0, 0, 0, 214, 147, 164, 0, 0, 0, 0, 0, 0, 0, 0, 0] ); buf.clear(); let interval = PgInterval { months: 0, days: 1, microseconds: 0, }; assert!(matches!( Encode::::encode(interval, &mut buf), Ok(IsNull::No) )); assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0]); buf.clear(); let interval = PgInterval { months: 1, days: 0, microseconds: 0, }; assert!(matches!( Encode::::encode(interval, &mut buf), Ok(IsNull::No) )); assert_eq!(&**buf, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); buf.clear(); assert_eq!( PgInterval::default(), PgInterval { months: 0, days: 0, microseconds: 0, } ); } #[test] fn test_pginterval_std() { // Case for positive duration let interval = PgInterval { days: 0, months: 0, microseconds: 27_000, }; assert_eq!( &PgInterval::try_from(std::time::Duration::from_micros(27_000)).unwrap(), &interval ); // Case when precision loss occurs assert!(PgInterval::try_from(std::time::Duration::from_nanos(27_000_001)).is_err()); // Case when microsecond overflow occurs assert!(PgInterval::try_from(std::time::Duration::from_secs(20_000_000_000_000)).is_err()); } #[test] #[cfg(feature = "chrono")] fn test_pginterval_chrono() { // Case for positive duration let interval = PgInterval { days: 0, months: 0, microseconds: 27_000, }; assert_eq!( &PgInterval::try_from(chrono::Duration::microseconds(27_000)).unwrap(), &interval ); // Case for negative duration let interval = PgInterval { days: 0, months: 0, microseconds: -27_000, }; assert_eq!( &PgInterval::try_from(chrono::Duration::microseconds(-27_000)).unwrap(), &interval ); // Case when precision loss occurs assert!(PgInterval::try_from(chrono::Duration::nanoseconds(27_000_001)).is_err()); assert!(PgInterval::try_from(chrono::Duration::nanoseconds(-27_000_001)).is_err()); // Case when nanosecond overflow occurs assert!(PgInterval::try_from(chrono::Duration::seconds(10_000_000_000)).is_err()); assert!(PgInterval::try_from(chrono::Duration::seconds(-10_000_000_000)).is_err()); } #[test] #[cfg(feature = "time")] fn test_pginterval_time() { // Case for positive duration let interval = PgInterval { days: 0, months: 0, microseconds: 27_000, }; assert_eq!( &PgInterval::try_from(time::Duration::microseconds(27_000)).unwrap(), &interval ); // Case for negative duration let interval = PgInterval { days: 0, months: 0, microseconds: -27_000, }; assert_eq!( &PgInterval::try_from(time::Duration::microseconds(-27_000)).unwrap(), &interval ); // Case when precision loss occurs assert!(PgInterval::try_from(time::Duration::nanoseconds(27_000_001)).is_err()); assert!(PgInterval::try_from(time::Duration::nanoseconds(-27_000_001)).is_err()); // Case when microsecond overflow occurs assert!(PgInterval::try_from(time::Duration::seconds(10_000_000_000_000)).is_err()); assert!(PgInterval::try_from(time::Duration::seconds(-10_000_000_000_000)).is_err()); } ================================================ FILE: sqlx-postgres/src/types/ipnet/ipaddr.rs ================================================ use std::net::IpAddr; use ipnet::IpNet; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef, Postgres}; impl Type for IpAddr where IpNet: Type, { fn type_info() -> PgTypeInfo { IpNet::type_info() } fn compatible(ty: &PgTypeInfo) -> bool { IpNet::compatible(ty) } } impl PgHasArrayType for IpAddr { fn array_type_info() -> PgTypeInfo { ::array_type_info() } fn array_compatible(ty: &PgTypeInfo) -> bool { ::array_compatible(ty) } } impl<'db> Encode<'db, Postgres> for IpAddr where IpNet: Encode<'db, Postgres>, { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { IpNet::from(*self).encode_by_ref(buf) } fn size_hint(&self) -> usize { IpNet::from(*self).size_hint() } } impl<'db> Decode<'db, Postgres> for IpAddr where IpNet: Decode<'db, Postgres>, { fn decode(value: PgValueRef<'db>) -> Result { let ipnet = IpNet::decode(value)?; if matches!(ipnet, IpNet::V4(net) if net.prefix_len() != 32) || matches!(ipnet, IpNet::V6(net) if net.prefix_len() != 128) { Err("lossy decode from inet/cidr")? } Ok(ipnet.addr()) } } ================================================ FILE: sqlx-postgres/src/types/ipnet/ipnet.rs ================================================ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; #[cfg(feature = "ipnet")] use ipnet::{IpNet, Ipv4Net, Ipv6Net}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; // https://github.com/postgres/postgres/blob/574925bfd0a8175f6e161936ea11d9695677ba09/src/include/utils/inet.h#L39 // Technically this is a magic number here but it doesn't make sense to drag in the whole of `libc` // just for one constant. const PGSQL_AF_INET: u8 = 2; // AF_INET const PGSQL_AF_INET6: u8 = PGSQL_AF_INET + 1; impl Type for IpNet { fn type_info() -> PgTypeInfo { PgTypeInfo::INET } fn compatible(ty: &PgTypeInfo) -> bool { *ty == PgTypeInfo::CIDR || *ty == PgTypeInfo::INET } } impl PgHasArrayType for IpNet { fn array_type_info() -> PgTypeInfo { PgTypeInfo::INET_ARRAY } fn array_compatible(ty: &PgTypeInfo) -> bool { *ty == PgTypeInfo::CIDR_ARRAY || *ty == PgTypeInfo::INET_ARRAY } } impl Encode<'_, Postgres> for IpNet { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { // https://github.com/postgres/postgres/blob/574925bfd0a8175f6e161936ea11d9695677ba09/src/backend/utils/adt/network.c#L293 // https://github.com/postgres/postgres/blob/574925bfd0a8175f6e161936ea11d9695677ba09/src/backend/utils/adt/network.c#L271 match self { IpNet::V4(net) => { buf.push(PGSQL_AF_INET); // ip_family buf.push(net.prefix_len()); // ip_bits buf.push(0); // is_cidr buf.push(4); // nb (number of bytes) buf.extend_from_slice(&net.addr().octets()) // address } IpNet::V6(net) => { buf.push(PGSQL_AF_INET6); // ip_family buf.push(net.prefix_len()); // ip_bits buf.push(0); // is_cidr buf.push(16); // nb (number of bytes) buf.extend_from_slice(&net.addr().octets()); // address } } Ok(IsNull::No) } fn size_hint(&self) -> usize { match self { IpNet::V4(_) => 8, IpNet::V6(_) => 20, } } } impl Decode<'_, Postgres> for IpNet { fn decode(value: PgValueRef<'_>) -> Result { let bytes = match value.format() { PgValueFormat::Binary => value.as_bytes()?, PgValueFormat::Text => { let s = value.as_str()?; println!("{s}"); if s.contains('/') { return Ok(s.parse()?); } // IpNet::from_str doesn't handle conversion from IpAddr to IpNet let addr: IpAddr = s.parse()?; return Ok(addr.into()); } }; if bytes.len() >= 8 { let family = bytes[0]; let prefix = bytes[1]; let _is_cidr = bytes[2] != 0; let len = bytes[3]; match family { PGSQL_AF_INET => { if bytes.len() == 8 && len == 4 { let inet = Ipv4Net::new( Ipv4Addr::new(bytes[4], bytes[5], bytes[6], bytes[7]), prefix, )?; return Ok(IpNet::V4(inet)); } } PGSQL_AF_INET6 => { if bytes.len() == 20 && len == 16 { let inet = Ipv6Net::new( Ipv6Addr::from([ bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19], ]), prefix, )?; return Ok(IpNet::V6(inet)); } } _ => { return Err(format!("unknown ip family {family}").into()); } } } Err("invalid data received when expecting an INET".into()) } } ================================================ FILE: sqlx-postgres/src/types/ipnet/mod.rs ================================================ // Prefer `ipnetwork` over `ipnet` because it was implemented first (want to avoid breaking change). #[cfg(not(feature = "ipnetwork"))] mod ipaddr; // Parent module is named after the `ipnet` crate, this is named after the `IpNet` type. #[allow(clippy::module_inception)] mod ipnet; ================================================ FILE: sqlx-postgres/src/types/ipnetwork/ipaddr.rs ================================================ use std::net::IpAddr; use ipnetwork::IpNetwork; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef, Postgres}; impl Type for IpAddr where IpNetwork: Type, { fn type_info() -> PgTypeInfo { IpNetwork::type_info() } fn compatible(ty: &PgTypeInfo) -> bool { IpNetwork::compatible(ty) } } impl PgHasArrayType for IpAddr { fn array_type_info() -> PgTypeInfo { ::array_type_info() } fn array_compatible(ty: &PgTypeInfo) -> bool { ::array_compatible(ty) } } impl<'db> Encode<'db, Postgres> for IpAddr where IpNetwork: Encode<'db, Postgres>, { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { IpNetwork::from(*self).encode_by_ref(buf) } fn size_hint(&self) -> usize { IpNetwork::from(*self).size_hint() } } impl<'db> Decode<'db, Postgres> for IpAddr where IpNetwork: Decode<'db, Postgres>, { fn decode(value: PgValueRef<'db>) -> Result { let ipnetwork = IpNetwork::decode(value)?; if ipnetwork.is_ipv4() && ipnetwork.prefix() != 32 || ipnetwork.is_ipv6() && ipnetwork.prefix() != 128 { Err("lossy decode from inet/cidr")? } Ok(ipnetwork.ip()) } } ================================================ FILE: sqlx-postgres/src/types/ipnetwork/ipnetwork.rs ================================================ use std::net::{Ipv4Addr, Ipv6Addr}; use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; // https://github.com/postgres/postgres/blob/574925bfd0a8175f6e161936ea11d9695677ba09/src/include/utils/inet.h#L39 // Technically this is a magic number here but it doesn't make sense to drag in the whole of `libc` // just for one constant. const PGSQL_AF_INET: u8 = 2; // AF_INET const PGSQL_AF_INET6: u8 = PGSQL_AF_INET + 1; impl Type for IpNetwork { fn type_info() -> PgTypeInfo { PgTypeInfo::INET } fn compatible(ty: &PgTypeInfo) -> bool { *ty == PgTypeInfo::CIDR || *ty == PgTypeInfo::INET } } impl PgHasArrayType for IpNetwork { fn array_type_info() -> PgTypeInfo { PgTypeInfo::INET_ARRAY } fn array_compatible(ty: &PgTypeInfo) -> bool { *ty == PgTypeInfo::CIDR_ARRAY || *ty == PgTypeInfo::INET_ARRAY } } impl Encode<'_, Postgres> for IpNetwork { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { // https://github.com/postgres/postgres/blob/574925bfd0a8175f6e161936ea11d9695677ba09/src/backend/utils/adt/network.c#L293 // https://github.com/postgres/postgres/blob/574925bfd0a8175f6e161936ea11d9695677ba09/src/backend/utils/adt/network.c#L271 match self { IpNetwork::V4(net) => { buf.push(PGSQL_AF_INET); // ip_family buf.push(net.prefix()); // ip_bits buf.push(0); // is_cidr buf.push(4); // nb (number of bytes) buf.extend_from_slice(&net.ip().octets()) // address } IpNetwork::V6(net) => { buf.push(PGSQL_AF_INET6); // ip_family buf.push(net.prefix()); // ip_bits buf.push(0); // is_cidr buf.push(16); // nb (number of bytes) buf.extend_from_slice(&net.ip().octets()); // address } } Ok(IsNull::No) } fn size_hint(&self) -> usize { match self { IpNetwork::V4(_) => 8, IpNetwork::V6(_) => 20, } } } impl Decode<'_, Postgres> for IpNetwork { fn decode(value: PgValueRef<'_>) -> Result { let bytes = match value.format() { PgValueFormat::Binary => value.as_bytes()?, PgValueFormat::Text => { return Ok(value.as_str()?.parse()?); } }; if bytes.len() >= 8 { let family = bytes[0]; let prefix = bytes[1]; let _is_cidr = bytes[2] != 0; let len = bytes[3]; match family { PGSQL_AF_INET => { if bytes.len() == 8 && len == 4 { let inet = Ipv4Network::new( Ipv4Addr::new(bytes[4], bytes[5], bytes[6], bytes[7]), prefix, )?; return Ok(IpNetwork::V4(inet)); } } PGSQL_AF_INET6 => { if bytes.len() == 20 && len == 16 { let inet = Ipv6Network::new( Ipv6Addr::from([ bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17], bytes[18], bytes[19], ]), prefix, )?; return Ok(IpNetwork::V6(inet)); } } _ => { return Err(format!("unknown ip family {family}").into()); } } } Err("invalid data received when expecting an INET".into()) } } ================================================ FILE: sqlx-postgres/src/types/ipnetwork/mod.rs ================================================ mod ipaddr; // Parent module is named after the `ipnetwork` crate, this is named after the `IpNetwork` type. #[allow(clippy::module_inception)] mod ipnetwork; ================================================ FILE: sqlx-postgres/src/types/json.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::array_compatible; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use serde::{Deserialize, Serialize}; use serde_json::value::RawValue as JsonRawValue; use serde_json::Value as JsonValue; pub(crate) use sqlx_core::types::{Json, Type}; // // In general, most applications should prefer to store JSON data as jsonb, // unless there are quite specialized needs, such as legacy assumptions // about ordering of object keys. impl Type for Json { fn type_info() -> PgTypeInfo { PgTypeInfo::JSONB } fn compatible(ty: &PgTypeInfo) -> bool { *ty == PgTypeInfo::JSON || *ty == PgTypeInfo::JSONB } } impl PgHasArrayType for Json { fn array_type_info() -> PgTypeInfo { PgTypeInfo::JSONB_ARRAY } fn array_compatible(ty: &PgTypeInfo) -> bool { array_compatible::>(ty) } } impl PgHasArrayType for JsonValue { fn array_type_info() -> PgTypeInfo { PgTypeInfo::JSONB_ARRAY } fn array_compatible(ty: &PgTypeInfo) -> bool { array_compatible::(ty) } } impl PgHasArrayType for JsonRawValue { fn array_type_info() -> PgTypeInfo { PgTypeInfo::JSONB_ARRAY } fn array_compatible(ty: &PgTypeInfo) -> bool { array_compatible::(ty) } } impl Encode<'_, Postgres> for Json where T: Serialize, { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { // we have a tiny amount of dynamic behavior depending if we are resolved to be JSON // instead of JSONB buf.patch(|buf, ty: &PgTypeInfo| { if *ty == PgTypeInfo::JSON || *ty == PgTypeInfo::JSON_ARRAY { buf[0] = b' '; } }); // JSONB version (as of 2020-03-20) buf.push(1); // the JSON data written to the buffer is the same regardless of parameter type serde_json::to_writer(&mut **buf, &self.0)?; Ok(IsNull::No) } } impl<'r, T: 'r> Decode<'r, Postgres> for Json where T: Deserialize<'r>, { fn decode(value: PgValueRef<'r>) -> Result { let mut buf = value.as_bytes()?; if value.format() == PgValueFormat::Binary && value.type_info == PgTypeInfo::JSONB { assert_eq!( buf[0], 1, "unsupported JSONB format version {}; please open an issue", buf[0] ); buf = &buf[1..]; } serde_json::from_slice(buf).map(Json).map_err(Into::into) } } ================================================ FILE: sqlx-postgres/src/types/lquery.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use bitflags::bitflags; use std::fmt::{self, Display, Formatter}; use std::io::Write; use std::ops::Deref; use std::str::FromStr; use crate::types::ltree::{PgLTreeLabel, PgLTreeParseError}; /// Represents lquery specific errors #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum PgLQueryParseError { #[error("lquery cannot be empty")] EmptyString, #[error("unexpected character in lquery")] UnexpectedCharacter, #[error("error parsing integer: {0}")] ParseIntError(#[from] std::num::ParseIntError), #[error("error parsing integer: {0}")] LTreeParrseError(#[from] PgLTreeParseError), /// LQuery version not supported #[error("lquery version not supported")] InvalidLqueryVersion, } /// Container for a Label Tree Query (`lquery`) in Postgres. /// /// See /// /// ### Note: Requires Postgres 13+ /// /// This integration requires that the `lquery` type support the binary format in the Postgres /// wire protocol, which only became available in Postgres 13. /// ([Postgres 13.0 Release Notes, Additional Modules](https://www.postgresql.org/docs/13/release-13.html#id-1.11.6.11.5.14)) /// /// Ideally, SQLx's Postgres driver should support falling back to text format for types /// which don't have `typsend` and `typrecv` entries in `pg_type`, but that work still needs /// to be done. /// /// ### Note: Extension Required /// The `ltree` extension is not enabled by default in Postgres. You will need to do so explicitly: /// /// ```ignore /// CREATE EXTENSION IF NOT EXISTS "ltree"; /// ``` #[derive(Clone, Debug, Default, PartialEq)] pub struct PgLQuery { levels: Vec, } // TODO: maybe a QueryBuilder pattern would be nice here impl PgLQuery { /// creates default/empty lquery pub fn new() -> Self { Self::default() } pub fn from(levels: Vec) -> Self { Self { levels } } /// push a query level pub fn push(&mut self, level: PgLQueryLevel) { self.levels.push(level); } /// pop a query level pub fn pop(&mut self) -> Option { self.levels.pop() } /// creates lquery from an iterator with checking labels // TODO: this should just be removed but I didn't want to bury it in a massive diff #[deprecated = "renamed to `try_from_iter()`"] #[allow(clippy::should_implement_trait)] pub fn from_iter(levels: I) -> Result where S: Into, I: IntoIterator, { let mut lquery = Self::default(); for level in levels { lquery.push(PgLQueryLevel::from_str(&level.into())?); } Ok(lquery) } /// Create an `LQUERY` from an iterator of label strings. /// /// Returns an error if any label fails to parse according to [`PgLQueryLevel::from_str()`]. pub fn try_from_iter(levels: I) -> Result where S: AsRef, I: IntoIterator, { levels .into_iter() .map(|level| level.as_ref().parse::()) .collect() } } impl FromIterator for PgLQuery { fn from_iter>(iter: T) -> Self { Self::from(iter.into_iter().collect()) } } impl IntoIterator for PgLQuery { type Item = PgLQueryLevel; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.levels.into_iter() } } impl FromStr for PgLQuery { type Err = PgLQueryParseError; fn from_str(s: &str) -> Result { Ok(Self { levels: s .split('.') .map(PgLQueryLevel::from_str) .collect::>()?, }) } } impl Display for PgLQuery { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut iter = self.levels.iter(); if let Some(label) = iter.next() { write!(f, "{label}")?; for label in iter { write!(f, ".{label}")?; } } Ok(()) } } impl Deref for PgLQuery { type Target = [PgLQueryLevel]; fn deref(&self) -> &Self::Target { &self.levels } } impl Type for PgLQuery { fn type_info() -> PgTypeInfo { // Since `ltree` is enabled by an extension, it does not have a stable OID. PgTypeInfo::with_name("lquery") } } impl PgHasArrayType for PgLQuery { fn array_type_info() -> PgTypeInfo { PgTypeInfo::with_name("_lquery") } } impl Encode<'_, Postgres> for PgLQuery { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { buf.extend(1i8.to_le_bytes()); write!(buf, "{self}")?; Ok(IsNull::No) } } impl<'r> Decode<'r, Postgres> for PgLQuery { fn decode(value: PgValueRef<'r>) -> Result { match value.format() { PgValueFormat::Binary => { let bytes = value.as_bytes()?; let version = i8::from_le_bytes([bytes[0]; 1]); if version != 1 { return Err(Box::new(PgLQueryParseError::InvalidLqueryVersion)); } Ok(Self::from_str(std::str::from_utf8(&bytes[1..])?)?) } PgValueFormat::Text => Ok(Self::from_str(value.as_str()?)?), } } } bitflags! { /// Modifiers that can be set to non-star labels #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct PgLQueryVariantFlag: u16 { /// * - Match any label with this prefix, for example foo* matches foobar const ANY_END = 0x01; /// @ - Match case-insensitively, for example a@ matches A const IN_CASE = 0x02; /// % - Match initial underscore-separated words const SUBLEXEME = 0x04; } } impl Display for PgLQueryVariantFlag { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if self.contains(PgLQueryVariantFlag::ANY_END) { write!(f, "*")?; } if self.contains(PgLQueryVariantFlag::IN_CASE) { write!(f, "@")?; } if self.contains(PgLQueryVariantFlag::SUBLEXEME) { write!(f, "%")?; } Ok(()) } } #[derive(Clone, Debug, PartialEq)] pub struct PgLQueryVariant { label: PgLTreeLabel, modifiers: PgLQueryVariantFlag, } impl Display for PgLQueryVariant { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}{}", self.label, self.modifiers) } } #[derive(Clone, Debug, PartialEq)] pub enum PgLQueryLevel { /// match any label (*) with optional at least / at most numbers Star(Option, Option), /// match any of specified labels with optional flags NonStar(Vec), /// match none of specified labels with optional flags NotNonStar(Vec), } impl FromStr for PgLQueryLevel { type Err = PgLQueryParseError; fn from_str(s: &str) -> Result { let bytes = s.as_bytes(); if bytes.is_empty() { Err(PgLQueryParseError::EmptyString) } else { match bytes[0] { b'*' => { if bytes.len() > 1 { let parts = s[2..s.len() - 1].split(',').collect::>(); match parts.len() { 1 => { let number = parts[0].parse()?; Ok(PgLQueryLevel::Star(Some(number), Some(number))) } 2 => Ok(PgLQueryLevel::Star( Some(parts[0].parse()?), Some(parts[1].parse()?), )), _ => Err(PgLQueryParseError::UnexpectedCharacter), } } else { Ok(PgLQueryLevel::Star(None, None)) } } b'!' => Ok(PgLQueryLevel::NotNonStar( s[1..] .split('|') .map(PgLQueryVariant::from_str) .collect::, PgLQueryParseError>>()?, )), _ => Ok(PgLQueryLevel::NonStar( s.split('|') .map(PgLQueryVariant::from_str) .collect::, PgLQueryParseError>>()?, )), } } } } impl FromStr for PgLQueryVariant { type Err = PgLQueryParseError; fn from_str(s: &str) -> Result { let mut label_length = s.len(); let mut modifiers = PgLQueryVariantFlag::empty(); for b in s.bytes().rev() { match b { b'@' => modifiers.insert(PgLQueryVariantFlag::IN_CASE), b'*' => modifiers.insert(PgLQueryVariantFlag::ANY_END), b'%' => modifiers.insert(PgLQueryVariantFlag::SUBLEXEME), _ => break, } label_length -= 1; } Ok(PgLQueryVariant { label: PgLTreeLabel::new(&s[0..label_length])?, modifiers, }) } } fn write_variants(f: &mut Formatter<'_>, variants: &[PgLQueryVariant], not: bool) -> fmt::Result { let mut iter = variants.iter(); if let Some(variant) = iter.next() { write!(f, "{}{}", if not { "!" } else { "" }, variant)?; for variant in iter { write!(f, ".{variant}")?; } } Ok(()) } impl Display for PgLQueryLevel { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { PgLQueryLevel::Star(Some(at_least), Some(at_most)) => { if at_least == at_most { write!(f, "*{{{at_least}}}") } else { write!(f, "*{{{at_least},{at_most}}}") } } PgLQueryLevel::Star(Some(at_least), _) => write!(f, "*{{{at_least},}}"), PgLQueryLevel::Star(_, Some(at_most)) => write!(f, "*{{,{at_most}}}"), PgLQueryLevel::Star(_, _) => write!(f, "*"), PgLQueryLevel::NonStar(variants) => write_variants(f, variants, false), PgLQueryLevel::NotNonStar(variants) => write_variants(f, variants, true), } } } ================================================ FILE: sqlx-postgres/src/types/ltree.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use std::fmt::{self, Display, Formatter}; use std::io::Write; use std::ops::Deref; use std::str::FromStr; /// Represents ltree specific errors #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum PgLTreeParseError { /// LTree labels can only contain [A-Za-z0-9_] #[error("ltree label contains invalid characters")] InvalidLtreeLabel, /// LTree version not supported #[error("ltree version not supported")] InvalidLtreeVersion, } #[derive(Clone, Debug, Default, PartialEq)] pub struct PgLTreeLabel(String); impl PgLTreeLabel { pub fn new(label: S) -> Result where S: Into, { let label = label.into(); if label.len() <= 256 && label .bytes() .all(|c| c.is_ascii_alphabetic() || c.is_ascii_digit() || c == b'_') { Ok(Self(label)) } else { Err(PgLTreeParseError::InvalidLtreeLabel) } } } impl Deref for PgLTreeLabel { type Target = str; fn deref(&self) -> &Self::Target { self.0.as_str() } } impl FromStr for PgLTreeLabel { type Err = PgLTreeParseError; fn from_str(s: &str) -> Result { PgLTreeLabel::new(s) } } impl Display for PgLTreeLabel { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } /// Container for a Label Tree (`ltree`) in Postgres. /// /// See /// /// ### Note: Requires Postgres 13+ /// /// This integration requires that the `ltree` type support the binary format in the Postgres /// wire protocol, which only became available in Postgres 13. /// ([Postgres 13.0 Release Notes, Additional Modules](https://www.postgresql.org/docs/13/release-13.html#id-1.11.6.11.5.14)) /// /// Ideally, SQLx's Postgres driver should support falling back to text format for types /// which don't have `typsend` and `typrecv` entries in `pg_type`, but that work still needs /// to be done. /// /// ### Note: Extension Required /// The `ltree` extension is not enabled by default in Postgres. You will need to do so explicitly: /// /// ```ignore /// CREATE EXTENSION IF NOT EXISTS "ltree"; /// ``` #[derive(Clone, Debug, Default, PartialEq)] pub struct PgLTree { labels: Vec, } impl PgLTree { /// creates default/empty ltree pub fn new() -> Self { Self::default() } /// creates ltree from a [`Vec`] pub fn from_labels(labels: Vec) -> Self { Self { labels } } /// creates ltree from an iterator with checking labels // TODO: this should just be removed but I didn't want to bury it in a massive diff #[deprecated = "renamed to `try_from_iter()`"] #[allow(clippy::should_implement_trait)] pub fn from_iter(labels: I) -> Result where String: From, I: IntoIterator, { let mut ltree = Self::default(); for label in labels { ltree.push(PgLTreeLabel::new(label)?); } Ok(ltree) } /// Create an `LTREE` from an iterator of label strings. /// /// Returns an error if any label fails to parse according to [`PgLTreeLabel::new()`]. pub fn try_from_iter(labels: I) -> Result where S: Into, I: IntoIterator, { labels.into_iter().map(PgLTreeLabel::new).collect() } /// push a label to ltree pub fn push(&mut self, label: PgLTreeLabel) { self.labels.push(label); } /// pop a label from ltree pub fn pop(&mut self) -> Option { self.labels.pop() } } impl From> for PgLTree { fn from(labels: Vec) -> Self { Self { labels } } } impl FromIterator for PgLTree { fn from_iter>(iter: T) -> Self { Self { labels: iter.into_iter().collect(), } } } impl IntoIterator for PgLTree { type Item = PgLTreeLabel; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.labels.into_iter() } } impl FromStr for PgLTree { type Err = PgLTreeParseError; fn from_str(s: &str) -> Result { Ok(Self { labels: s .split('.') .map(PgLTreeLabel::new) .collect::, Self::Err>>()?, }) } } impl Display for PgLTree { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut iter = self.labels.iter(); if let Some(label) = iter.next() { write!(f, "{label}")?; for label in iter { write!(f, ".{label}")?; } } Ok(()) } } impl Deref for PgLTree { type Target = [PgLTreeLabel]; fn deref(&self) -> &Self::Target { &self.labels } } impl Type for PgLTree { fn type_info() -> PgTypeInfo { // Since `ltree` is enabled by an extension, it does not have a stable OID. PgTypeInfo::with_name("ltree") } } impl PgHasArrayType for PgLTree { fn array_type_info() -> PgTypeInfo { PgTypeInfo::with_name("_ltree") } } impl Encode<'_, Postgres> for PgLTree { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { buf.extend(1i8.to_le_bytes()); write!(buf, "{self}")?; Ok(IsNull::No) } } impl<'r> Decode<'r, Postgres> for PgLTree { fn decode(value: PgValueRef<'r>) -> Result { match value.format() { PgValueFormat::Binary => { let bytes = value.as_bytes()?; let version = i8::from_le_bytes([bytes[0]; 1]); if version != 1 { return Err(Box::new(PgLTreeParseError::InvalidLtreeVersion)); } Ok(Self::from_str(std::str::from_utf8(&bytes[1..])?)?) } PgValueFormat::Text => Ok(Self::from_str(value.as_str()?)?), } } } ================================================ FILE: sqlx-postgres/src/types/mac_address.rs ================================================ use mac_address::MacAddress; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; impl Type for MacAddress { fn type_info() -> PgTypeInfo { PgTypeInfo::MACADDR } fn compatible(ty: &PgTypeInfo) -> bool { *ty == PgTypeInfo::MACADDR } } impl PgHasArrayType for MacAddress { fn array_type_info() -> PgTypeInfo { PgTypeInfo::MACADDR_ARRAY } } impl Encode<'_, Postgres> for MacAddress { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { buf.extend_from_slice(&self.bytes()); // write just the address Ok(IsNull::No) } fn size_hint(&self) -> usize { 6 } } impl Decode<'_, Postgres> for MacAddress { fn decode(value: PgValueRef<'_>) -> Result { let bytes = match value.format() { PgValueFormat::Binary => value.as_bytes()?, PgValueFormat::Text => { return Ok(value.as_str()?.parse()?); } }; if bytes.len() == 6 { return Ok(MacAddress::new(bytes.try_into().unwrap())); } Err("invalid data received when expecting an MACADDR".into()) } } ================================================ FILE: sqlx-postgres/src/types/mod.rs ================================================ //! Conversions between Rust and **Postgres** types. //! //! # Types //! //! | Rust type | Postgres type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `bool` | BOOL | //! | `i8` | "CHAR" | //! | `i16` | SMALLINT, SMALLSERIAL, INT2 | //! | `i32` | INT, SERIAL, INT4 | //! | `i64` | BIGINT, BIGSERIAL, INT8 | //! | `f32` | REAL, FLOAT4 | //! | `f64` | DOUBLE PRECISION, FLOAT8 | //! | `&str`, [`String`] | VARCHAR, CHAR(N), TEXT, NAME, CITEXT | //! | `&[u8]`, `Vec` | BYTEA | //! | `()` | VOID | //! | [`PgInterval`] | INTERVAL | //! | [`PgRange`](PgRange) | INT8RANGE, INT4RANGE, TSRANGE, TSTZRANGE, DATERANGE, NUMRANGE | //! | [`PgMoney`] | MONEY | //! | [`PgLTree`] | LTREE | //! | [`PgLQuery`] | LQUERY | //! | [`PgCiText`] | CITEXT1 | //! | [`PgCube`] | CUBE | //! | [`PgPoint`] | POINT | //! | [`PgLine`] | LINE | //! | [`PgLSeg`] | LSEG | //! | [`PgBox`] | BOX | //! | [`PgPath`] | PATH | //! | [`PgPolygon`] | POLYGON | //! | [`PgCircle`] | CIRCLE | //! | [`PgHstore`] | HSTORE | //! //! 1 SQLx generally considers `CITEXT` to be compatible with `String`, `&str`, etc., //! but this wrapper type is available for edge cases, such as `CITEXT[]` which Postgres //! does not consider to be compatible with `TEXT[]`. //! //! ### [`bigdecimal`](https://crates.io/crates/bigdecimal) //! Requires the `bigdecimal` Cargo feature flag. //! //! | Rust type | Postgres type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `bigdecimal::BigDecimal` | NUMERIC | //! #![doc=include_str!("bigdecimal-range.md")] //! //! ### [`rust_decimal`](https://crates.io/crates/rust_decimal) //! Requires the `rust_decimal` Cargo feature flag. //! //! | Rust type | Postgres type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `rust_decimal::Decimal` | NUMERIC | //! #![doc=include_str!("rust_decimal-range.md")] //! //! ### [`chrono`](https://crates.io/crates/chrono) //! //! Requires the `chrono` Cargo feature flag. //! //! | Rust type | Postgres type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `chrono::DateTime` | TIMESTAMPTZ | //! | `chrono::DateTime` | TIMESTAMPTZ | //! | `chrono::NaiveDateTime` | TIMESTAMP | //! | `chrono::NaiveDate` | DATE | //! | `chrono::NaiveTime` | TIME | //! | [`PgTimeTz`] | TIMETZ | //! //! ### [`time`](https://crates.io/crates/time) //! //! Requires the `time` Cargo feature flag. //! //! | Rust type | Postgres type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `time::PrimitiveDateTime` | TIMESTAMP | //! | `time::OffsetDateTime` | TIMESTAMPTZ | //! | `time::Date` | DATE | //! | `time::Time` | TIME | //! | [`PgTimeTz`] | TIMETZ | //! //! ### [`uuid`](https://crates.io/crates/uuid) //! //! Requires the `uuid` Cargo feature flag. //! //! | Rust type | Postgres type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `uuid::Uuid` | UUID | //! //! ### [`ipnetwork`](https://crates.io/crates/ipnetwork) //! //! Requires the `ipnetwork` Cargo feature flag (takes precedence over `ipnet` if both are used). //! //! | Rust type | Postgres type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `ipnetwork::IpNetwork` | INET, CIDR | //! | `std::net::IpAddr` | INET, CIDR | //! //! Note that because `IpAddr` does not support network prefixes, it is an error to attempt to decode //! an `IpAddr` from a `INET` or `CIDR` value with a network prefix smaller than the address' full width: //! `/32` for IPv4 addresses and `/128` for IPv6 addresses. //! //! `IpNetwork` does not have this limitation. //! //! ### [`ipnet`](https://crates.io/crates/ipnet) //! //! Requires the `ipnet` Cargo feature flag. //! //! | Rust type | Postgres type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `ipnet::IpNet` | INET, CIDR | //! | `std::net::IpAddr` | INET, CIDR | //! //! The same `IpAddr` limitation for smaller network prefixes applies as with `ipnet`. //! //! ### [`mac_address`](https://crates.io/crates/mac_address) //! //! Requires the `mac_address` Cargo feature flag. //! //! | Rust type | Postgres type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `mac_address::MacAddress` | MACADDR | //! //! ### [`bit-vec`](https://crates.io/crates/bit-vec) //! //! Requires the `bit-vec` Cargo feature flag. //! //! | Rust type | Postgres type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `bit_vec::BitVec` | BIT, VARBIT | //! //! ### [`json`](https://crates.io/crates/serde_json) //! //! Requires the `json` Cargo feature flag. //! //! | Rust type | Postgres type(s) | //! |---------------------------------------|------------------------------------------------------| //! | [`Json`] | JSON, JSONB | //! | `serde_json::Value` | JSON, JSONB | //! | `&serde_json::value::RawValue` | JSON, JSONB | //! //! `Value` and `RawValue` from `serde_json` can be used for unstructured JSON data with //! Postgres. //! //! [`Json`](crate::types::Json) can be used for structured JSON data with Postgres. //! //! # [Composite types](https://www.postgresql.org/docs/current/rowtypes.html) //! //! User-defined composite types are supported through a derive for `Type`. //! //! ```text //! CREATE TYPE inventory_item AS ( //! name text, //! supplier_id integer, //! price numeric //! ); //! ``` //! //! ```rust,ignore //! #[derive(sqlx::Type)] //! #[sqlx(type_name = "inventory_item")] //! struct InventoryItem { //! name: String, //! supplier_id: i32, //! price: BigDecimal, //! } //! ``` //! //! Anonymous composite types are represented as tuples. Note that anonymous composites may only //! be returned and not sent to Postgres (this is a limitation of postgres). //! //! # Arrays //! //! One-dimensional arrays are supported as `Vec` or `&[T]` where `T` implements `Type`. //! //! # [Enumerations](https://www.postgresql.org/docs/current/datatype-enum.html) //! //! User-defined enumerations are supported through a derive for `Type`. //! //! ```text //! CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy'); //! ``` //! //! ```rust,ignore //! #[derive(sqlx::Type)] //! #[sqlx(type_name = "mood", rename_all = "lowercase")] //! enum Mood { Sad, Ok, Happy } //! ``` //! //! Rust enumerations may also be defined to be represented as an integer using `repr`. //! The following type expects a SQL type of `INTEGER` or `INT4` and will convert to/from the //! Rust enumeration. //! //! ```rust,ignore //! #[derive(sqlx::Type)] //! #[repr(i32)] //! enum Mood { Sad = 0, Ok = 1, Happy = 2 } //! ``` //! //! Rust enumerations may also be defined to be represented as a string using `type_name = "text"`. //! The following type expects a SQL type of `TEXT` and will convert to/from the Rust enumeration. //! //! ```rust,ignore //! #[derive(sqlx::Type)] //! #[sqlx(type_name = "text")] //! enum Mood { Sad, Ok, Happy } //! ``` //! //! Note that an error can occur if you attempt to decode a value not contained within the enum //! definition. //! use crate::type_info::PgTypeKind; use crate::{PgTypeInfo, Postgres}; #[cfg(feature = "json")] pub(crate) use sqlx_core::types::Json; pub(crate) use sqlx_core::types::Type; mod array; mod bool; mod bytes; mod citext; mod float; mod hstore; mod int; mod interval; #[cfg(feature = "json")] mod json; mod lquery; mod ltree; mod money; mod oid; mod range; mod record; mod str; mod text; mod tuple; mod void; #[cfg(any(feature = "chrono", feature = "time"))] mod time_tz; #[cfg(feature = "bigdecimal")] mod bigdecimal; mod cube; mod geometry; #[cfg(any(feature = "bigdecimal", feature = "rust_decimal"))] mod numeric; #[cfg(feature = "rust_decimal")] mod rust_decimal; #[cfg(feature = "chrono")] mod chrono; #[cfg(feature = "time")] mod time; #[cfg(feature = "uuid")] mod uuid; #[cfg(feature = "ipnet")] mod ipnet; #[cfg(feature = "ipnetwork")] mod ipnetwork; #[cfg(feature = "mac_address")] mod mac_address; #[cfg(feature = "bit-vec")] mod bit_vec; pub use array::PgHasArrayType; pub use citext::PgCiText; pub use cube::PgCube; pub use geometry::circle::PgCircle; pub use geometry::line::PgLine; pub use geometry::line_segment::PgLSeg; pub use geometry::path::PgPath; pub use geometry::point::PgPoint; pub use geometry::polygon::PgPolygon; pub use geometry::r#box::PgBox; pub use hstore::PgHstore; pub use interval::PgInterval; pub use lquery::PgLQuery; pub use lquery::PgLQueryLevel; pub use lquery::PgLQueryVariant; pub use lquery::PgLQueryVariantFlag; pub use ltree::PgLTree; pub use ltree::PgLTreeLabel; pub use ltree::PgLTreeParseError; pub use money::PgMoney; pub use oid::Oid; pub use range::PgRange; #[cfg(any(feature = "chrono", feature = "time"))] pub use time_tz::PgTimeTz; // used in derive(Type) for `struct` // but the interface is not considered part of the public API #[doc(hidden)] pub use record::{PgRecordDecoder, PgRecordEncoder}; // Type::compatible impl appropriate for arrays fn array_compatible + ?Sized>(ty: &PgTypeInfo) -> bool { // we require the declared type to be an _array_ with an // element type that is acceptable if let PgTypeKind::Array(element) = &ty.kind() { return E::compatible(element); } false } ================================================ FILE: sqlx-postgres/src/types/money.rs ================================================ use crate::{ decode::Decode, encode::{Encode, IsNull}, error::BoxDynError, types::Type, {PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}, }; use byteorder::{BigEndian, ByteOrder}; use std::{ io, ops::{Add, AddAssign, Sub, SubAssign}, }; /// The PostgreSQL [`MONEY`] type stores a currency amount with a fixed fractional /// precision. The fractional precision is determined by the database's /// `lc_monetary` setting. /// /// Data is read and written as 64-bit signed integers, and conversion into a /// decimal should be done using the right precision. /// /// Reading `MONEY` value in text format is not supported and will cause an error. /// /// ### `locale_frac_digits` /// This parameter corresponds to the number of digits after the decimal separator. /// /// This value must match what Postgres is expecting for the locale set in the database /// or else the decimal value you see on the client side will not match the `money` value /// on the server side. /// /// **For _most_ locales, this value is `2`.** /// /// If you're not sure what locale your database is set to or how many decimal digits it specifies, /// you can execute `SHOW lc_monetary;` to get the locale name, and then look it up in this list /// (you can ignore the `.utf8` prefix): /// /// /// If that link is dead and you're on a POSIX-compliant system (Unix, FreeBSD) you can also execute: /// /// ```sh /// $ LC_MONETARY= locale -k frac_digits /// ``` /// /// And the value you want is `N` in `frac_digits=N`. If you have shell access to the database /// server you should execute it there as available locales may differ between machines. /// /// Note that if `frac_digits` for the locale is outside the range `[0, 10]`, Postgres assumes /// it's a sentinel value and defaults to 2: /// /// /// [`MONEY`]: https://www.postgresql.org/docs/current/datatype-money.html #[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] pub struct PgMoney( /// The raw integer value sent over the wire; for locales with `frac_digits=2` (i.e. most /// of them), this will be the value in whole cents. /// /// E.g. for `select '$123.45'::money` with a locale of `en_US` (`frac_digits=2`), /// this will be `12345`. /// /// If the currency of your locale does not have fractional units, e.g. Yen, then this will /// just be the units of the currency. /// /// See the type-level docs for an explanation of `locale_frac_units`. pub i64, ); impl PgMoney { /// Convert the money value into a [`BigDecimal`] using `locale_frac_digits`. /// /// See the type-level docs for an explanation of `locale_frac_digits`. /// /// [`BigDecimal`]: bigdecimal::BigDecimal #[cfg(feature = "bigdecimal")] pub fn to_bigdecimal(self, locale_frac_digits: i64) -> bigdecimal::BigDecimal { let digits = num_bigint::BigInt::from(self.0); bigdecimal::BigDecimal::new(digits, locale_frac_digits) } /// Convert the money value into a [`Decimal`] using `locale_frac_digits`. /// /// See the type-level docs for an explanation of `locale_frac_digits`. /// /// [`Decimal`]: rust_decimal::Decimal #[cfg(feature = "rust_decimal")] pub fn to_decimal(self, locale_frac_digits: u32) -> rust_decimal::Decimal { rust_decimal::Decimal::new(self.0, locale_frac_digits) } /// Convert a [`Decimal`] value into money using `locale_frac_digits`. /// /// See the type-level docs for an explanation of `locale_frac_digits`. /// /// Note that `Decimal` has 96 bits of precision, but `PgMoney` only has 63 plus the sign bit. /// If the value is larger than 63 bits it will be truncated. /// /// [`Decimal`]: rust_decimal::Decimal #[cfg(feature = "rust_decimal")] pub fn from_decimal(mut decimal: rust_decimal::Decimal, locale_frac_digits: u32) -> Self { // this is all we need to convert to our expected locale's `frac_digits` decimal.rescale(locale_frac_digits); /// a mask to bitwise-AND with an `i64` to zero the sign bit const SIGN_MASK: i64 = i64::MAX; let is_negative = decimal.is_sign_negative(); let serialized = decimal.serialize(); // interpret bytes `4..12` as an i64, ignoring the sign bit // this is where truncation occurs let value = i64::from_le_bytes( *<&[u8; 8]>::try_from(&serialized[4..12]) .expect("BUG: slice of serialized should be 8 bytes"), ) & SIGN_MASK; // zero out the sign bit // negate if necessary Self(if is_negative { -value } else { value }) } /// Convert a [`BigDecimal`](bigdecimal::BigDecimal) value into money using the correct precision /// defined in the PostgreSQL settings. The default precision is two. #[cfg(feature = "bigdecimal")] pub fn from_bigdecimal( decimal: bigdecimal::BigDecimal, locale_frac_digits: u32, ) -> Result { use bigdecimal::ToPrimitive; let multiplier = bigdecimal::BigDecimal::new( num_bigint::BigInt::from(10i128.pow(locale_frac_digits)), 0, ); let cents = decimal * multiplier; let money = cents.to_i64().ok_or_else(|| { io::Error::new( io::ErrorKind::InvalidData, "Provided BigDecimal could not convert to i64: overflow.", ) })?; Ok(Self(money)) } } impl Type for PgMoney { fn type_info() -> PgTypeInfo { PgTypeInfo::MONEY } } impl PgHasArrayType for PgMoney { fn array_type_info() -> PgTypeInfo { PgTypeInfo::MONEY_ARRAY } } impl From for PgMoney where T: Into, { fn from(num: T) -> Self { Self(num.into()) } } impl Encode<'_, Postgres> for PgMoney { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { buf.extend(&self.0.to_be_bytes()); Ok(IsNull::No) } } impl Decode<'_, Postgres> for PgMoney { fn decode(value: PgValueRef<'_>) -> Result { match value.format() { PgValueFormat::Binary => { let cents = BigEndian::read_i64(value.as_bytes()?); Ok(PgMoney(cents)) } PgValueFormat::Text => { let error = io::Error::new( io::ErrorKind::InvalidData, "Reading a `MONEY` value in text format is not supported.", ); Err(Box::new(error)) } } } } impl Add for PgMoney { type Output = PgMoney; /// Adds two monetary values. /// /// # Panics /// Panics if overflowing the `i64::MAX`. fn add(self, rhs: PgMoney) -> Self::Output { self.0 .checked_add(rhs.0) .map(PgMoney) .expect("overflow adding money amounts") } } impl AddAssign for PgMoney { /// An assigning add for two monetary values. /// /// # Panics /// Panics if overflowing the `i64::MAX`. fn add_assign(&mut self, rhs: PgMoney) { self.0 = self .0 .checked_add(rhs.0) .expect("overflow adding money amounts") } } impl Sub for PgMoney { type Output = PgMoney; /// Subtracts two monetary values. /// /// # Panics /// Panics if underflowing the `i64::MIN`. fn sub(self, rhs: PgMoney) -> Self::Output { self.0 .checked_sub(rhs.0) .map(PgMoney) .expect("overflow subtracting money amounts") } } impl SubAssign for PgMoney { /// An assigning subtract for two monetary values. /// /// # Panics /// Panics if underflowing the `i64::MIN`. fn sub_assign(&mut self, rhs: PgMoney) { self.0 = self .0 .checked_sub(rhs.0) .expect("overflow subtracting money amounts") } } #[cfg(test)] mod tests { use super::PgMoney; #[test] fn adding_works() { assert_eq!(PgMoney(3), PgMoney(1) + PgMoney(2)) } #[test] fn add_assign_works() { let mut money = PgMoney(1); money += PgMoney(2); assert_eq!(PgMoney(3), money); } #[test] fn subtracting_works() { assert_eq!(PgMoney(4), PgMoney(5) - PgMoney(1)) } #[test] fn sub_assign_works() { let mut money = PgMoney(1); money -= PgMoney(2); assert_eq!(PgMoney(-1), money); } #[test] fn default_value() { let money = PgMoney::default(); assert_eq!(money, PgMoney(0)); } #[test] #[should_panic] fn add_overflow_panics() { let _ = PgMoney(i64::MAX) + PgMoney(1); } #[test] #[should_panic] fn add_assign_overflow_panics() { let mut money = PgMoney(i64::MAX); money += PgMoney(1); } #[test] #[should_panic] fn sub_overflow_panics() { let _ = PgMoney(i64::MIN) - PgMoney(1); } #[test] #[should_panic] fn sub_assign_overflow_panics() { let mut money = PgMoney(i64::MIN); money -= PgMoney(1); } #[test] #[cfg(feature = "bigdecimal")] fn conversion_to_bigdecimal_works() { let money = PgMoney(12345); assert_eq!( bigdecimal::BigDecimal::new(num_bigint::BigInt::from(12345), 2), money.to_bigdecimal(2) ); } #[test] #[cfg(feature = "rust_decimal")] fn conversion_to_decimal_works() { assert_eq!( rust_decimal::Decimal::new(12345, 2), PgMoney(12345).to_decimal(2) ); } #[test] #[cfg(feature = "rust_decimal")] fn conversion_from_decimal_works() { assert_eq!( PgMoney(12345), PgMoney::from_decimal(rust_decimal::Decimal::new(12345, 2), 2) ); assert_eq!( PgMoney(12345), PgMoney::from_decimal(rust_decimal::Decimal::new(123450, 3), 2) ); assert_eq!( PgMoney(-12345), PgMoney::from_decimal(rust_decimal::Decimal::new(-123450, 3), 2) ); assert_eq!( PgMoney(-12300), PgMoney::from_decimal(rust_decimal::Decimal::new(-123, 0), 2) ); } #[test] #[cfg(feature = "bigdecimal")] fn conversion_from_bigdecimal_works() { let dec = bigdecimal::BigDecimal::new(num_bigint::BigInt::from(12345), 2); assert_eq!(PgMoney(12345), PgMoney::from_bigdecimal(dec, 2).unwrap()); } } ================================================ FILE: sqlx-postgres/src/types/numeric.rs ================================================ use sqlx_core::bytes::Buf; use std::num::Saturating; use crate::error::BoxDynError; use crate::PgArgumentBuffer; /// Represents a `NUMERIC` value in the **Postgres** wire protocol. #[derive(Debug, PartialEq, Eq)] pub(crate) enum PgNumeric { /// Equivalent to the `'NaN'` value in Postgres. The result of, e.g. `1 / 0`. NotANumber, /// A populated `NUMERIC` value. /// /// A description of these fields can be found here (although the type being described is the /// version for in-memory calculations, the field names are the same): /// https://github.com/postgres/postgres/blob/bcd1c3630095e48bc3b1eb0fc8e8c8a7c851eba1/src/backend/utils/adt/numeric.c#L224-L269 Number { /// The sign of the value: positive (also set for 0 and -0), or negative. sign: PgNumericSign, /// The digits of the number in base-10000 with the most significant digit first /// (big-endian). /// /// The length of this vector must not overflow `i16` for the binary protocol. /// /// *Note*: the `Encode` implementation will panic if any digit is `>= 10000`. digits: Vec, /// The scaling factor of the number, such that the value will be interpreted as /// /// ```text /// digits[0] * 10,000 ^ weight /// + digits[1] * 10,000 ^ (weight - 1) /// ... /// + digits[N] * 10,000 ^ (weight - N) where N = digits.len() - 1 /// ``` /// May be negative. weight: i16, /// How many _decimal_ (base-10) digits following the decimal point to consider in /// arithmetic regardless of how many actually follow the decimal point as determined by /// `weight`--the comment in the Postgres code linked above recommends using this only for /// ignoring unnecessary trailing zeroes (as trimming nonzero digits means reducing the /// precision of the value). /// /// Must be `>= 0`. scale: i16, }, } // https://github.com/postgres/postgres/blob/bcd1c3630095e48bc3b1eb0fc8e8c8a7c851eba1/src/backend/utils/adt/numeric.c#L167-L170 const SIGN_POS: u16 = 0x0000; const SIGN_NEG: u16 = 0x4000; const SIGN_NAN: u16 = 0xC000; // overflows i16 (C equivalent truncates from integer literal) /// Possible sign values for [PgNumeric]. #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[repr(u16)] pub(crate) enum PgNumericSign { Positive = SIGN_POS, Negative = SIGN_NEG, } impl PgNumericSign { fn try_from_u16(val: u16) -> Result { match val { SIGN_POS => Ok(PgNumericSign::Positive), SIGN_NEG => Ok(PgNumericSign::Negative), SIGN_NAN => unreachable!("sign value for NaN passed to PgNumericSign"), _ => Err(format!("invalid value for PgNumericSign: {val:#04X}").into()), } } } impl PgNumeric { /// Equivalent value of `0::numeric`. pub const ZERO: Self = PgNumeric::Number { sign: PgNumericSign::Positive, digits: vec![], weight: 0, scale: 0, }; pub(crate) fn is_valid_digit(digit: i16) -> bool { (0..10_000).contains(&digit) } pub(crate) fn size_hint(decimal_digits: u64) -> usize { let mut size_hint = Saturating(decimal_digits); // BigDecimal::digits() gives us base-10 digits, so we divide by 4 to get base-10000 digits // and since this is just a hint we just always round up size_hint /= 4; size_hint += 1; // Times two bytes for each base-10000 digit size_hint *= 2; // Plus `weight` and `scale` size_hint += 8; usize::try_from(size_hint.0).unwrap_or(usize::MAX) } pub(crate) fn decode(mut buf: &[u8]) -> Result { // https://github.com/postgres/postgres/blob/bcd1c3630095e48bc3b1eb0fc8e8c8a7c851eba1/src/backend/utils/adt/numeric.c#L874 let num_digits = buf.get_u16(); let weight = buf.get_i16(); let sign = buf.get_u16(); let scale = buf.get_i16(); if sign == SIGN_NAN { Ok(PgNumeric::NotANumber) } else { let digits: Vec<_> = (0..num_digits).map(|_| buf.get_i16()).collect::<_>(); Ok(PgNumeric::Number { sign: PgNumericSign::try_from_u16(sign)?, scale, weight, digits, }) } } /// ### Errors /// /// * If `digits.len()` overflows `i16` /// * If any element in `digits` is greater than or equal to 10000 pub(crate) fn encode(&self, buf: &mut PgArgumentBuffer) -> Result<(), String> { match *self { PgNumeric::Number { ref digits, sign, scale, weight, } => { let digits_len = i16::try_from(digits.len()).map_err(|_| { format!( "PgNumeric digits.len() ({}) should not overflow i16", digits.len() ) })?; buf.extend(&digits_len.to_be_bytes()); buf.extend(&weight.to_be_bytes()); buf.extend(&(sign as i16).to_be_bytes()); buf.extend(&scale.to_be_bytes()); for (i, &digit) in digits.iter().enumerate() { if !Self::is_valid_digit(digit) { return Err(format!("{i}th PgNumeric digit out of range: {digit}")); } buf.extend(&digit.to_be_bytes()); } } PgNumeric::NotANumber => { buf.extend(&0_i16.to_be_bytes()); buf.extend(&0_i16.to_be_bytes()); buf.extend(&SIGN_NAN.to_be_bytes()); buf.extend(&0_i16.to_be_bytes()); } } Ok(()) } } ================================================ FILE: sqlx-postgres/src/types/oid.rs ================================================ use byteorder::{BigEndian, ByteOrder}; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; /// The PostgreSQL [`OID`] type stores an object identifier, /// used internally by PostgreSQL as primary keys for various system tables. /// /// [`OID`]: https://www.postgresql.org/docs/current/datatype-oid.html #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, Default)] pub struct Oid( /// The raw unsigned integer value sent over the wire pub u32, ); impl Type for Oid { fn type_info() -> PgTypeInfo { PgTypeInfo::OID } } impl PgHasArrayType for Oid { fn array_type_info() -> PgTypeInfo { PgTypeInfo::OID_ARRAY } } impl Encode<'_, Postgres> for Oid { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { buf.extend(&self.0.to_be_bytes()); Ok(IsNull::No) } } impl Decode<'_, Postgres> for Oid { fn decode(value: PgValueRef<'_>) -> Result { Ok(Self(match value.format() { PgValueFormat::Binary => BigEndian::read_u32(value.as_bytes()?), PgValueFormat::Text => value.as_str()?.parse()?, })) } } #[cfg(feature = "offline")] impl serde::Serialize for Oid { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.0.serialize(serializer) } } #[cfg(feature = "offline")] impl<'de> serde::Deserialize<'de> for Oid { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { u32::deserialize(deserializer).map(Self) } } ================================================ FILE: sqlx-postgres/src/types/range.rs ================================================ use std::fmt::{self, Debug, Display, Formatter}; use std::ops::{Bound, Range, RangeBounds, RangeFrom, RangeInclusive, RangeTo, RangeToInclusive}; use bitflags::bitflags; use sqlx_core::bytes::Buf; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::type_info::PgTypeKind; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; // https://github.com/postgres/postgres/blob/2f48ede080f42b97b594fb14102c82ca1001b80c/src/include/utils/rangetypes.h#L35-L44 bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] struct RangeFlags: u8 { const EMPTY = 0x01; const LB_INC = 0x02; const UB_INC = 0x04; const LB_INF = 0x08; const UB_INF = 0x10; const LB_NULL = 0x20; // not used const UB_NULL = 0x40; // not used const CONTAIN_EMPTY = 0x80; // internal } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct PgRange { pub start: Bound, pub end: Bound, } impl From<[Bound; 2]> for PgRange { fn from(v: [Bound; 2]) -> Self { let [start, end] = v; Self { start, end } } } impl From<(Bound, Bound)> for PgRange { fn from(v: (Bound, Bound)) -> Self { Self { start: v.0, end: v.1, } } } impl From> for PgRange { fn from(v: Range) -> Self { Self { start: Bound::Included(v.start), end: Bound::Excluded(v.end), } } } impl From> for PgRange { fn from(v: RangeFrom) -> Self { Self { start: Bound::Included(v.start), end: Bound::Unbounded, } } } impl From> for PgRange { fn from(v: RangeInclusive) -> Self { let (start, end) = v.into_inner(); Self { start: Bound::Included(start), end: Bound::Included(end), } } } impl From> for PgRange { fn from(v: RangeTo) -> Self { Self { start: Bound::Unbounded, end: Bound::Excluded(v.end), } } } impl From> for PgRange { fn from(v: RangeToInclusive) -> Self { Self { start: Bound::Unbounded, end: Bound::Included(v.end), } } } impl RangeBounds for PgRange { fn start_bound(&self) -> Bound<&T> { match self.start { Bound::Included(ref start) => Bound::Included(start), Bound::Excluded(ref start) => Bound::Excluded(start), Bound::Unbounded => Bound::Unbounded, } } fn end_bound(&self) -> Bound<&T> { match self.end { Bound::Included(ref end) => Bound::Included(end), Bound::Excluded(ref end) => Bound::Excluded(end), Bound::Unbounded => Bound::Unbounded, } } } impl Type for PgRange { fn type_info() -> PgTypeInfo { PgTypeInfo::INT4_RANGE } fn compatible(ty: &PgTypeInfo) -> bool { range_compatible::(ty) } } impl Type for PgRange { fn type_info() -> PgTypeInfo { PgTypeInfo::INT8_RANGE } fn compatible(ty: &PgTypeInfo) -> bool { range_compatible::(ty) } } #[cfg(feature = "bigdecimal")] impl Type for PgRange { fn type_info() -> PgTypeInfo { PgTypeInfo::NUM_RANGE } fn compatible(ty: &PgTypeInfo) -> bool { range_compatible::(ty) } } #[cfg(feature = "rust_decimal")] impl Type for PgRange { fn type_info() -> PgTypeInfo { PgTypeInfo::NUM_RANGE } fn compatible(ty: &PgTypeInfo) -> bool { range_compatible::(ty) } } #[cfg(feature = "chrono")] impl Type for PgRange { fn type_info() -> PgTypeInfo { PgTypeInfo::DATE_RANGE } fn compatible(ty: &PgTypeInfo) -> bool { range_compatible::(ty) } } #[cfg(feature = "chrono")] impl Type for PgRange { fn type_info() -> PgTypeInfo { PgTypeInfo::TS_RANGE } fn compatible(ty: &PgTypeInfo) -> bool { range_compatible::(ty) } } #[cfg(feature = "chrono")] impl Type for PgRange> { fn type_info() -> PgTypeInfo { PgTypeInfo::TSTZ_RANGE } fn compatible(ty: &PgTypeInfo) -> bool { range_compatible::>(ty) } } #[cfg(feature = "time")] impl Type for PgRange { fn type_info() -> PgTypeInfo { PgTypeInfo::DATE_RANGE } fn compatible(ty: &PgTypeInfo) -> bool { range_compatible::(ty) } } #[cfg(feature = "time")] impl Type for PgRange { fn type_info() -> PgTypeInfo { PgTypeInfo::TS_RANGE } fn compatible(ty: &PgTypeInfo) -> bool { range_compatible::(ty) } } #[cfg(feature = "time")] impl Type for PgRange { fn type_info() -> PgTypeInfo { PgTypeInfo::TSTZ_RANGE } fn compatible(ty: &PgTypeInfo) -> bool { range_compatible::(ty) } } impl PgHasArrayType for PgRange { fn array_type_info() -> PgTypeInfo { PgTypeInfo::INT4_RANGE_ARRAY } } impl PgHasArrayType for PgRange { fn array_type_info() -> PgTypeInfo { PgTypeInfo::INT8_RANGE_ARRAY } } #[cfg(feature = "bigdecimal")] impl PgHasArrayType for PgRange { fn array_type_info() -> PgTypeInfo { PgTypeInfo::NUM_RANGE_ARRAY } } #[cfg(feature = "rust_decimal")] impl PgHasArrayType for PgRange { fn array_type_info() -> PgTypeInfo { PgTypeInfo::NUM_RANGE_ARRAY } } #[cfg(feature = "chrono")] impl PgHasArrayType for PgRange { fn array_type_info() -> PgTypeInfo { PgTypeInfo::DATE_RANGE_ARRAY } } #[cfg(feature = "chrono")] impl PgHasArrayType for PgRange { fn array_type_info() -> PgTypeInfo { PgTypeInfo::TS_RANGE_ARRAY } } #[cfg(feature = "chrono")] impl PgHasArrayType for PgRange> { fn array_type_info() -> PgTypeInfo { PgTypeInfo::TSTZ_RANGE_ARRAY } } #[cfg(feature = "time")] impl PgHasArrayType for PgRange { fn array_type_info() -> PgTypeInfo { PgTypeInfo::DATE_RANGE_ARRAY } } #[cfg(feature = "time")] impl PgHasArrayType for PgRange { fn array_type_info() -> PgTypeInfo { PgTypeInfo::TS_RANGE_ARRAY } } #[cfg(feature = "time")] impl PgHasArrayType for PgRange { fn array_type_info() -> PgTypeInfo { PgTypeInfo::TSTZ_RANGE_ARRAY } } impl<'q, T> Encode<'q, Postgres> for PgRange where T: Encode<'q, Postgres>, { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { // https://github.com/postgres/postgres/blob/2f48ede080f42b97b594fb14102c82ca1001b80c/src/backend/utils/adt/rangetypes.c#L245 let mut flags = RangeFlags::empty(); flags |= match self.start { Bound::Included(_) => RangeFlags::LB_INC, Bound::Unbounded => RangeFlags::LB_INF, Bound::Excluded(_) => RangeFlags::empty(), }; flags |= match self.end { Bound::Included(_) => RangeFlags::UB_INC, Bound::Unbounded => RangeFlags::UB_INF, Bound::Excluded(_) => RangeFlags::empty(), }; buf.push(flags.bits()); if let Bound::Included(v) | Bound::Excluded(v) = &self.start { buf.encode(v)?; } if let Bound::Included(v) | Bound::Excluded(v) = &self.end { buf.encode(v)?; } // ranges are themselves never null Ok(IsNull::No) } } impl<'r, T> Decode<'r, Postgres> for PgRange where T: Type + for<'a> Decode<'a, Postgres>, { fn decode(value: PgValueRef<'r>) -> Result { match value.format { PgValueFormat::Binary => { let element_ty = if let PgTypeKind::Range(element) = &value.type_info.0.kind() { element } else { return Err(format!("unexpected non-range type {}", value.type_info).into()); }; let mut buf = value.as_bytes()?; let mut start = Bound::Unbounded; let mut end = Bound::Unbounded; let flags = RangeFlags::from_bits_truncate(buf.get_u8()); if flags.contains(RangeFlags::EMPTY) { return Ok(PgRange { start, end }); } if !flags.contains(RangeFlags::LB_INF) { let value = T::decode(PgValueRef::get(&mut buf, value.format, element_ty.clone())?)?; start = if flags.contains(RangeFlags::LB_INC) { Bound::Included(value) } else { Bound::Excluded(value) }; } if !flags.contains(RangeFlags::UB_INF) { let value = T::decode(PgValueRef::get(&mut buf, value.format, element_ty.clone())?)?; end = if flags.contains(RangeFlags::UB_INC) { Bound::Included(value) } else { Bound::Excluded(value) }; } Ok(PgRange { start, end }) } PgValueFormat::Text => { // https://github.com/postgres/postgres/blob/2f48ede080f42b97b594fb14102c82ca1001b80c/src/backend/utils/adt/rangetypes.c#L2046 let mut start = None; let mut end = None; let s = value.as_str()?; // remember the bounds let sb = s.as_bytes(); let lower = sb[0] as char; let upper = sb[sb.len() - 1] as char; // trim the wrapping braces/brackets let s = &s[1..(s.len() - 1)]; let mut chars = s.chars(); let mut element = String::new(); let mut done = false; let mut quoted = false; let mut in_quotes = false; let mut in_escape = false; let mut prev_ch = '\0'; let mut count = 0; while !done { element.clear(); loop { match chars.next() { Some(ch) => { match ch { _ if in_escape => { element.push(ch); in_escape = false; } '"' if in_quotes => { in_quotes = false; } '"' => { in_quotes = true; quoted = true; if prev_ch == '"' { element.push('"') } } '\\' if !in_escape => { in_escape = true; } ',' if !in_quotes => break, _ => { element.push(ch); } } prev_ch = ch; } None => { done = true; break; } } } count += 1; if !element.is_empty() || quoted { let value = Some(T::decode(PgValueRef { type_info: T::type_info(), format: PgValueFormat::Text, value: Some(element.as_bytes()), row: None, })?); if count == 1 { start = value; } else if count == 2 { end = value; } else { return Err("more than 2 elements found in a range".into()); } } } let start = parse_bound(lower, start)?; let end = parse_bound(upper, end)?; Ok(PgRange { start, end }) } } } } fn parse_bound(ch: char, value: Option) -> Result, BoxDynError> { Ok(if let Some(value) = value { match ch { '(' | ')' => Bound::Excluded(value), '[' | ']' => Bound::Included(value), _ => { return Err(format!( "expected `(`, ')', '[', or `]` but found `{ch}` for range literal" ) .into()); } } } else { Bound::Unbounded }) } impl Display for PgRange where T: Display, { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match &self.start { Bound::Unbounded => f.write_str("(,")?, Bound::Excluded(v) => write!(f, "({v},")?, Bound::Included(v) => write!(f, "[{v},")?, } match &self.end { Bound::Unbounded => f.write_str(")")?, Bound::Excluded(v) => write!(f, "{v})")?, Bound::Included(v) => write!(f, "{v}]")?, } Ok(()) } } fn range_compatible>(ty: &PgTypeInfo) -> bool { // we require the declared type to be a _range_ with an // element type that is acceptable if let PgTypeKind::Range(element) = &ty.kind() { return E::compatible(element); } false } ================================================ FILE: sqlx-postgres/src/types/record.rs ================================================ use sqlx_core::bytes::Buf; use crate::decode::Decode; use crate::encode::Encode; use crate::error::{mismatched_types, BoxDynError}; use crate::type_info::TypeInfo; use crate::type_info::{PgType, PgTypeKind}; use crate::types::Oid; use crate::types::Type; use crate::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; #[doc(hidden)] pub struct PgRecordEncoder<'a> { buf: &'a mut PgArgumentBuffer, off: usize, num: u32, } impl<'a> PgRecordEncoder<'a> { #[doc(hidden)] pub fn new(buf: &'a mut PgArgumentBuffer) -> Self { let off = buf.len(); // reserve space for a field count buf.extend(&(0_u32).to_be_bytes()); Self { buf, off, num: 0 } } #[doc(hidden)] pub fn finish(&mut self) { // fill in the record length self.buf[self.off..(self.off + 4)].copy_from_slice(&self.num.to_be_bytes()); } #[doc(hidden)] pub fn encode<'q, T>(&mut self, value: T) -> Result<&mut Self, BoxDynError> where 'a: 'q, T: Encode<'q, Postgres> + Type, { let ty = value.produces().unwrap_or_else(T::type_info); match ty.0 { // push a hole for this type ID // to be filled in on query execution PgType::DeclareWithName(name) => self.buf.patch_type_by_name(&name), PgType::DeclareArrayOf(array) => self.buf.patch_array_type(array), // write type id pg_type => self.buf.extend(&pg_type.oid().0.to_be_bytes()), } self.buf.encode(value)?; self.num += 1; Ok(self) } } #[doc(hidden)] pub struct PgRecordDecoder<'r> { buf: &'r [u8], typ: PgTypeInfo, fmt: PgValueFormat, ind: usize, } impl<'r> PgRecordDecoder<'r> { #[doc(hidden)] pub fn new(value: PgValueRef<'r>) -> Result { let fmt = value.format(); let mut buf = value.as_bytes()?; let typ = value.type_info; match fmt { PgValueFormat::Binary => { let _len = buf.get_u32(); } PgValueFormat::Text => { // remove the enclosing `(` .. `)` buf = &buf[1..(buf.len() - 1)]; } } Ok(Self { buf, fmt, typ, ind: 0, }) } #[doc(hidden)] pub fn try_decode(&mut self) -> Result where T: for<'a> Decode<'a, Postgres> + Type, { if self.buf.is_empty() { return Err(format!("no field `{0}` found on record", self.ind).into()); } match self.fmt { PgValueFormat::Binary => { let element_type_oid = Oid(self.buf.get_u32()); let element_type_opt = self.find_type_info(&self.typ, element_type_oid)?; if let Some(ty) = &element_type_opt { if !ty.is_null() && !T::compatible(ty) { return Err(mismatched_types::(ty)); } } let element_type = element_type_opt .ok_or_else(|| BoxDynError::from(format!("custom types in records are not fully supported yet: failed to retrieve type info for field {} with type oid {}", self.ind, element_type_oid.0)))?; self.ind += 1; T::decode(PgValueRef::get(&mut self.buf, self.fmt, element_type)?) } PgValueFormat::Text => { let mut element = String::new(); let mut quoted = false; let mut in_quotes = false; let mut in_escape = false; let mut prev_ch = '\0'; while !self.buf.is_empty() { let ch = self.buf.get_u8() as char; match ch { _ if in_escape => { element.push(ch); in_escape = false; } '"' if in_quotes => { in_quotes = false; } '"' => { in_quotes = true; quoted = true; if prev_ch == '"' { element.push('"') } } '\\' if !in_escape => { in_escape = true; } ',' if !in_quotes => break, _ => { element.push(ch); } } prev_ch = ch; } let buf = if element.is_empty() && !quoted { // completely empty input means NULL None } else { Some(element.as_bytes()) }; // NOTE: we do not call [`accepts`] or give a chance to from a user as // TEXT sequences are not strongly typed T::decode(PgValueRef { // NOTE: We pass `0` as the type ID because we don't have a reasonable value // we could use. type_info: PgTypeInfo::with_oid(Oid(0)), format: self.fmt, value: buf, row: None, }) } } } fn find_type_info( &self, typ: &PgTypeInfo, oid: Oid, ) -> Result, BoxDynError> { match typ.kind() { PgTypeKind::Simple if typ.0 == PgType::Record => Ok(PgTypeInfo::try_from_oid(oid)), PgTypeKind::Composite(fields) => { let ty = fields[self.ind].1.clone(); if ty.0.oid() != oid { return Err("unexpected mismatch of composite type information".into()); } Ok(Some(ty)) } PgTypeKind::Domain(domain) => self.find_type_info(domain, oid), _ => Err("unexpected custom type being decoded as a composite type".into()), } } } ================================================ FILE: sqlx-postgres/src/types/rust_decimal-range.md ================================================ #### Note: `rust_decimal::Decimal` Has a Smaller Range than `NUMERIC` `NUMERIC` is can have up to 131,072 digits before the decimal point, and 16,384 digits after it. See [Section 8.1, Numeric Types] of the Postgres manual for details. However, `rust_decimal::Decimal` is limited to a maximum absolute magnitude of 296 - 1, a number with 67 decimal digits, and a minimum absolute magnitude of 10-28, a number with, unsurprisingly, 28 decimal digits. Thus, in contrast with `BigDecimal`, `NUMERIC` can actually represent every possible value of `rust_decimal::Decimal`, but not the other way around. This means that encoding should never fail, but decoding can. ================================================ FILE: sqlx-postgres/src/types/rust_decimal.rs ================================================ use rust_decimal::Decimal; use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::numeric::{PgNumeric, PgNumericSign}; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use rust_decimal::MathematicalOps; impl Type for Decimal { fn type_info() -> PgTypeInfo { PgTypeInfo::NUMERIC } } impl PgHasArrayType for Decimal { fn array_type_info() -> PgTypeInfo { PgTypeInfo::NUMERIC_ARRAY } } impl TryFrom for Decimal { type Error = BoxDynError; fn try_from(numeric: PgNumeric) -> Result { Decimal::try_from(&numeric) } } impl TryFrom<&'_ PgNumeric> for Decimal { type Error = BoxDynError; fn try_from(numeric: &'_ PgNumeric) -> Result { let (digits, sign, mut weight, scale) = match *numeric { PgNumeric::Number { ref digits, sign, weight, scale, } => (digits, sign, weight, scale), PgNumeric::NotANumber => { return Err("Decimal does not support NaN values".into()); } }; if digits.is_empty() { // Postgres returns an empty digit array for 0 return Ok(Decimal::ZERO); } let scale = u32::try_from(scale) .map_err(|_| format!("invalid scale value for Pg NUMERIC: {scale}"))?; let mut value = Decimal::ZERO; // Sum over `digits`, multiply each by its weight and add it to `value`. for &digit in digits { let mul = Decimal::from(10_000i16) .checked_powi(weight as i64) .ok_or("value not representable as rust_decimal::Decimal")?; let part = Decimal::from(digit) .checked_mul(mul) .ok_or("value not representable as rust_decimal::Decimal")?; value = value .checked_add(part) .ok_or("value not representable as rust_decimal::Decimal")?; weight = weight.checked_sub(1).ok_or("weight underflowed")?; } match sign { PgNumericSign::Positive => value.set_sign_positive(true), PgNumericSign::Negative => value.set_sign_negative(true), } value.rescale(scale); Ok(value) } } impl From for PgNumeric { fn from(value: Decimal) -> Self { PgNumeric::from(&value) } } // This impl is effectively infallible because `NUMERIC` has a greater range than `Decimal`. impl From<&'_ Decimal> for PgNumeric { // Impl has been manually validated. #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] fn from(decimal: &Decimal) -> Self { if Decimal::is_zero(decimal) { return PgNumeric::ZERO; } assert!( (0u32..=28).contains(&decimal.scale()), "decimal scale out of range {:?}", decimal.unpack(), ); // Cannot overflow: always in the range [0, 28] let scale = decimal.scale() as u16; let mut mantissa = decimal.mantissa().unsigned_abs(); // If our scale is not a multiple of 4, we need to go to the next multiple. let groups_diff = scale % 4; if groups_diff > 0 { let remainder = 4 - groups_diff as u32; let power = 10u32.pow(remainder) as u128; // Impossible to overflow; 0 <= mantissa <= 2^96, // and we're multiplying by at most 1,000 (giving us a result < 2^106) mantissa *= power; } // Array to store max mantissa of Decimal in Postgres decimal format. let mut digits = Vec::with_capacity(8); // Convert to base-10000. while mantissa != 0 { // Cannot overflow or wrap because of the modulus digits.push((mantissa % 10_000) as i16); mantissa /= 10_000; } // We started with the low digits first, but they should actually be at the end. digits.reverse(); // Cannot overflow: strictly smaller than `scale`. let digits_after_decimal = scale.div_ceil(4) as i16; // `mantissa` contains at most 29 decimal digits (log10(2^96)), // split into at most 8 4-digit segments. assert!( digits.len() <= 8, "digits.len() out of range: {}; unpacked: {:?}", digits.len(), decimal.unpack() ); // Cannot overflow; at most 8 let num_digits = digits.len() as i16; // Find how many 4-digit segments should go before the decimal point. // `weight = 0` puts just `digit[0]` before the decimal point, and the rest after. let weight = num_digits - digits_after_decimal - 1; // Remove non-significant zeroes. while let Some(&0) = digits.last() { digits.pop(); } PgNumeric::Number { sign: match decimal.is_sign_negative() { false => PgNumericSign::Positive, true => PgNumericSign::Negative, }, // Cannot overflow; between 0 and 28 scale: scale as i16, weight, digits, } } } impl Encode<'_, Postgres> for Decimal { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { PgNumeric::from(self).encode(buf)?; Ok(IsNull::No) } } #[doc=include_str!("rust_decimal-range.md")] impl Decode<'_, Postgres> for Decimal { fn decode(value: PgValueRef<'_>) -> Result { match value.format() { PgValueFormat::Binary => PgNumeric::decode(value.as_bytes()?)?.try_into(), PgValueFormat::Text => Ok(value.as_str()?.parse::()?), } } } #[cfg(test)] #[allow(clippy::zero_prefixed_literal)] // Used for clarity mod tests { use super::{Decimal, PgNumeric, PgNumericSign}; use std::convert::TryFrom; #[test] fn zero() { let zero: Decimal = "0".parse().unwrap(); assert_eq!(PgNumeric::from(&zero), PgNumeric::ZERO,); assert_eq!(Decimal::try_from(&PgNumeric::ZERO).unwrap(), Decimal::ZERO); } #[test] fn one() { let one: Decimal = "1".parse().unwrap(); assert_eq!( PgNumeric::from(&one), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 0, weight: 0, digits: vec![1] } ); } #[test] fn ten() { let ten: Decimal = "10".parse().unwrap(); assert_eq!( PgNumeric::from(&ten), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 0, weight: 0, digits: vec![10] } ); } #[test] fn one_hundred() { let one_hundred: Decimal = "100".parse().unwrap(); assert_eq!( PgNumeric::from(&one_hundred), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 0, weight: 0, digits: vec![100] } ); } #[test] fn ten_thousand() { // Decimal doesn't normalize here let ten_thousand: Decimal = "10000".parse().unwrap(); assert_eq!( PgNumeric::from(&ten_thousand), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 0, weight: 1, digits: vec![1] } ); } #[test] fn two_digits() { let two_digits: Decimal = "12345".parse().unwrap(); assert_eq!( PgNumeric::from(&two_digits), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 0, weight: 1, digits: vec![1, 2345] } ); } #[test] fn one_tenth() { let one_tenth: Decimal = "0.1".parse().unwrap(); assert_eq!( PgNumeric::from(&one_tenth), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 1, weight: -1, digits: vec![1000] } ); } #[test] fn decimal_1() { let decimal: Decimal = "1.2345".parse().unwrap(); assert_eq!( PgNumeric::from(&decimal), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 4, weight: 0, digits: vec![1, 2345] } ); } #[test] fn decimal_2() { let decimal: Decimal = "0.12345".parse().unwrap(); assert_eq!( PgNumeric::from(&decimal), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 5, weight: -1, digits: vec![1234, 5000] } ); } #[test] fn decimal_3() { let decimal: Decimal = "0.01234".parse().unwrap(); assert_eq!( PgNumeric::from(&decimal), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 5, weight: -1, digits: vec![0123, 4000] } ); } #[test] fn decimal_4() { let decimal: Decimal = "12345.67890".parse().unwrap(); let expected_numeric = PgNumeric::Number { sign: PgNumericSign::Positive, scale: 5, weight: 1, digits: vec![1, 2345, 6789], }; assert_eq!(PgNumeric::from(&decimal), expected_numeric); let actual_decimal = Decimal::try_from(expected_numeric).unwrap(); assert_eq!(actual_decimal, decimal); assert_eq!(actual_decimal.mantissa(), 1234567890); assert_eq!(actual_decimal.scale(), 5); } #[test] fn one_digit_decimal() { let one_digit_decimal: Decimal = "0.00001234".parse().unwrap(); let expected_numeric = PgNumeric::Number { sign: PgNumericSign::Positive, scale: 8, weight: -2, digits: vec![1234], }; assert_eq!(PgNumeric::from(&one_digit_decimal), expected_numeric); let actual_decimal = Decimal::try_from(expected_numeric).unwrap(); assert_eq!(actual_decimal, one_digit_decimal); assert_eq!(actual_decimal.mantissa(), 1234); assert_eq!(actual_decimal.scale(), 8); } #[test] fn max_value() { let expected_numeric = PgNumeric::Number { sign: PgNumericSign::Positive, scale: 0, weight: 7, digits: vec![7, 9228, 1625, 1426, 4337, 5935, 4395, 0335], }; assert_eq!(PgNumeric::from(&Decimal::MAX), expected_numeric); let actual_decimal = Decimal::try_from(expected_numeric).unwrap(); assert_eq!(actual_decimal, Decimal::MAX); // Value split by 10,000's to match the expected digits[] assert_eq!( actual_decimal.mantissa(), 7_9228_1625_1426_4337_5935_4395_0335 ); assert_eq!(actual_decimal.scale(), 0); } #[test] fn mult_overflow() { // -24_0711_6702_1036_7100_2022_8579_3280.00 let large_negative_number = PgNumeric::Number { sign: PgNumericSign::Negative, scale: 0, weight: 7, digits: vec![24, 0711, 6702, 1036, 7100, 2022, 8579, 3280], }; assert!(Decimal::try_from(large_negative_number).is_err()); } #[test] fn max_value_max_scale() { let mut max_value_max_scale = Decimal::MAX; max_value_max_scale.set_scale(28).unwrap(); let expected_numeric = PgNumeric::Number { sign: PgNumericSign::Positive, scale: 28, weight: 0, digits: vec![7, 9228, 1625, 1426, 4337, 5935, 4395, 0335], }; assert_eq!(PgNumeric::from(&max_value_max_scale), expected_numeric); let actual_decimal = Decimal::try_from(expected_numeric).unwrap(); assert_eq!(actual_decimal, max_value_max_scale); assert_eq!( actual_decimal.mantissa(), 79_228_162_514_264_337_593_543_950_335 ); assert_eq!(actual_decimal.scale(), 28); } #[test] fn issue_423_four_digit() { // This is a regression test for https://github.com/launchbadge/sqlx/issues/423 let four_digit: Decimal = "1234".parse().unwrap(); assert_eq!( PgNumeric::from(&four_digit), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 0, weight: 0, digits: vec![1234] } ); } #[test] fn issue_423_negative_four_digit() { // This is a regression test for https://github.com/launchbadge/sqlx/issues/423 let negative_four_digit: Decimal = "-1234".parse().unwrap(); assert_eq!( PgNumeric::from(&negative_four_digit), PgNumeric::Number { sign: PgNumericSign::Negative, scale: 0, weight: 0, digits: vec![1234] } ); } #[test] fn issue_423_eight_digit() { // This is a regression test for https://github.com/launchbadge/sqlx/issues/423 let eight_digit: Decimal = "12345678".parse().unwrap(); assert_eq!( PgNumeric::from(&eight_digit), PgNumeric::Number { sign: PgNumericSign::Positive, scale: 0, weight: 1, digits: vec![1234, 5678] } ); } #[test] fn issue_423_negative_eight_digit() { // This is a regression test for https://github.com/launchbadge/sqlx/issues/423 let negative_eight_digit: Decimal = "-12345678".parse().unwrap(); assert_eq!( PgNumeric::from(&negative_eight_digit), PgNumeric::Number { sign: PgNumericSign::Negative, scale: 0, weight: 1, digits: vec![1234, 5678] } ); } #[test] fn issue_2247_trailing_zeros() { // This is a regression test for https://github.com/launchbadge/sqlx/issues/2247 let one_hundred: Decimal = "100.00".parse().unwrap(); let expected_numeric = PgNumeric::Number { sign: PgNumericSign::Positive, scale: 2, weight: 0, digits: vec![100], }; assert_eq!(PgNumeric::from(&one_hundred), expected_numeric); let actual_decimal = Decimal::try_from(expected_numeric).unwrap(); assert_eq!(actual_decimal, one_hundred); assert_eq!(actual_decimal.mantissa(), 10000); assert_eq!(actual_decimal.scale(), 2); } } ================================================ FILE: sqlx-postgres/src/types/str.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::array_compatible; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueRef, Postgres}; use std::borrow::Cow; use std::rc::Rc; use std::sync::Arc; impl Type for str { fn type_info() -> PgTypeInfo { PgTypeInfo::TEXT } fn compatible(ty: &PgTypeInfo) -> bool { [ PgTypeInfo::TEXT, PgTypeInfo::NAME, PgTypeInfo::BPCHAR, PgTypeInfo::VARCHAR, PgTypeInfo::UNKNOWN, PgTypeInfo::with_name("citext"), ] .contains(ty) } } impl Type for String { fn type_info() -> PgTypeInfo { <&str as Type>::type_info() } fn compatible(ty: &PgTypeInfo) -> bool { <&str as Type>::compatible(ty) } } impl PgHasArrayType for &'_ str { fn array_type_info() -> PgTypeInfo { PgTypeInfo::TEXT_ARRAY } fn array_compatible(ty: &PgTypeInfo) -> bool { array_compatible::<&str>(ty) } } impl PgHasArrayType for Cow<'_, str> { fn array_type_info() -> PgTypeInfo { <&str as PgHasArrayType>::array_type_info() } fn array_compatible(ty: &PgTypeInfo) -> bool { <&str as PgHasArrayType>::array_compatible(ty) } } impl PgHasArrayType for Box { fn array_type_info() -> PgTypeInfo { <&str as PgHasArrayType>::array_type_info() } fn array_compatible(ty: &PgTypeInfo) -> bool { <&str as PgHasArrayType>::array_compatible(ty) } } impl PgHasArrayType for String { fn array_type_info() -> PgTypeInfo { <&str as PgHasArrayType>::array_type_info() } fn array_compatible(ty: &PgTypeInfo) -> bool { <&str as PgHasArrayType>::array_compatible(ty) } } impl Encode<'_, Postgres> for &'_ str { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { buf.extend(self.as_bytes()); Ok(IsNull::No) } } impl<'r> Decode<'r, Postgres> for &'r str { fn decode(value: PgValueRef<'r>) -> Result { value.as_str() } } impl Decode<'_, Postgres> for String { fn decode(value: PgValueRef<'_>) -> Result { Ok(value.as_str()?.to_owned()) } } forward_encode_impl!(Arc, &str, Postgres); forward_encode_impl!(Rc, &str, Postgres); forward_encode_impl!(Cow<'_, str>, &str, Postgres); forward_encode_impl!(Box, &str, Postgres); forward_encode_impl!(String, &str, Postgres); ================================================ FILE: sqlx-postgres/src/types/text.rs ================================================ use crate::{PgArgumentBuffer, PgTypeInfo, PgValueRef, Postgres}; use sqlx_core::decode::Decode; use sqlx_core::encode::{Encode, IsNull}; use sqlx_core::error::BoxDynError; use sqlx_core::types::{Text, Type}; use std::fmt::Display; use std::str::FromStr; use std::io::Write; impl Type for Text { fn type_info() -> PgTypeInfo { >::type_info() } fn compatible(ty: &PgTypeInfo) -> bool { >::compatible(ty) } } impl Encode<'_, Postgres> for Text where T: Display, { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { write!(**buf, "{}", self.0)?; Ok(IsNull::No) } } impl<'r, T> Decode<'r, Postgres> for Text where T: FromStr, BoxDynError: From<::Err>, { fn decode(value: PgValueRef<'r>) -> Result { let s: &str = Decode::::decode(value)?; Ok(Self(s.parse()?)) } } ================================================ FILE: sqlx-postgres/src/types/time/date.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::time::PG_EPOCH; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use std::mem; use time::macros::format_description; use time::{Date, Duration}; impl Type for Date { fn type_info() -> PgTypeInfo { PgTypeInfo::DATE } } impl PgHasArrayType for Date { fn array_type_info() -> PgTypeInfo { PgTypeInfo::DATE_ARRAY } } impl Encode<'_, Postgres> for Date { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { // DATE is encoded as number of days since epoch (2000-01-01) let days: i32 = (*self - PG_EPOCH).whole_days().try_into().map_err(|_| { format!("value {self:?} would overflow binary encoding for Postgres DATE") })?; Encode::::encode(days, buf) } fn size_hint(&self) -> usize { mem::size_of::() } } impl<'r> Decode<'r, Postgres> for Date { fn decode(value: PgValueRef<'r>) -> Result { Ok(match value.format() { PgValueFormat::Binary => { // DATE is encoded as the days since epoch let days: i32 = Decode::::decode(value)?; PG_EPOCH + Duration::days(days.into()) } PgValueFormat::Text => Date::parse( value.as_str()?, &format_description!("[year]-[month]-[day]"), )?, }) } } ================================================ FILE: sqlx-postgres/src/types/time/datetime.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::time::PG_EPOCH; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use std::borrow::Cow; use std::mem; use time::macros::format_description; use time::macros::offset; use time::{Duration, OffsetDateTime, PrimitiveDateTime}; impl Type for PrimitiveDateTime { fn type_info() -> PgTypeInfo { PgTypeInfo::TIMESTAMP } } impl Type for OffsetDateTime { fn type_info() -> PgTypeInfo { PgTypeInfo::TIMESTAMPTZ } } impl PgHasArrayType for PrimitiveDateTime { fn array_type_info() -> PgTypeInfo { PgTypeInfo::TIMESTAMP_ARRAY } } impl PgHasArrayType for OffsetDateTime { fn array_type_info() -> PgTypeInfo { PgTypeInfo::TIMESTAMPTZ_ARRAY } } impl Encode<'_, Postgres> for PrimitiveDateTime { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { // TIMESTAMP is encoded as the microseconds since the epoch let micros: i64 = (*self - PG_EPOCH.midnight()) .whole_microseconds() .try_into() .map_err(|_| { format!("value {self:?} would overflow binary encoding for Postgres TIME") })?; Encode::::encode(micros, buf) } fn size_hint(&self) -> usize { mem::size_of::() } } impl<'r> Decode<'r, Postgres> for PrimitiveDateTime { fn decode(value: PgValueRef<'r>) -> Result { Ok(match value.format() { PgValueFormat::Binary => { // TIMESTAMP is encoded as the microseconds since the epoch let us = Decode::::decode(value)?; PG_EPOCH.midnight() + Duration::microseconds(us) } PgValueFormat::Text => { let s = value.as_str()?; // If there is no decimal point we need to add one. let s = if s.contains('.') { Cow::Borrowed(s) } else { Cow::Owned(format!("{s}.0")) }; // Contains a time-zone specifier // This is given for timestamptz for some reason // Postgres already guarantees this to always be UTC if s.contains('+') { PrimitiveDateTime::parse(&s, &format_description!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond][offset_hour]"))? } else { PrimitiveDateTime::parse( &s, &format_description!( "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]" ), )? } } }) } } impl Encode<'_, Postgres> for OffsetDateTime { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { let utc = self.to_offset(offset!(UTC)); let primitive = PrimitiveDateTime::new(utc.date(), utc.time()); Encode::::encode(primitive, buf) } fn size_hint(&self) -> usize { mem::size_of::() } } impl<'r> Decode<'r, Postgres> for OffsetDateTime { fn decode(value: PgValueRef<'r>) -> Result { Ok(>::decode(value)?.assume_utc()) } } ================================================ FILE: sqlx-postgres/src/types/time/mod.rs ================================================ mod date; mod datetime; // Parent module is named after the `time` crate, this module is named after the `TIME` SQL type. #[allow(clippy::module_inception)] mod time; #[rustfmt::skip] const PG_EPOCH: ::time::Date = ::time::macros::date!(2000-1-1); ================================================ FILE: sqlx-postgres/src/types/time/time.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use std::mem; use time::macros::format_description; use time::{Duration, Time}; impl Type for Time { fn type_info() -> PgTypeInfo { PgTypeInfo::TIME } } impl PgHasArrayType for Time { fn array_type_info() -> PgTypeInfo { PgTypeInfo::TIME_ARRAY } } impl Encode<'_, Postgres> for Time { fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { // TIME is encoded as the microseconds since midnight. // // A truncating cast is fine because `self - Time::MIDNIGHT` cannot exceed a span of 24 hours. #[allow(clippy::cast_possible_truncation)] let micros: i64 = (*self - Time::MIDNIGHT).whole_microseconds() as i64; Encode::::encode(micros, buf) } fn size_hint(&self) -> usize { mem::size_of::() } } impl<'r> Decode<'r, Postgres> for Time { fn decode(value: PgValueRef<'r>) -> Result { Ok(match value.format() { PgValueFormat::Binary => { // TIME is encoded as the microseconds since midnight let us = Decode::::decode(value)?; Time::MIDNIGHT + Duration::microseconds(us) } PgValueFormat::Text => Time::parse( value.as_str()?, // Postgres will not include the subsecond part if it's zero. &format_description!("[hour]:[minute]:[second][optional [.[subsecond]]]"), )?, }) } } ================================================ FILE: sqlx-postgres/src/types/time_tz.rs ================================================ use crate::decode::Decode; use crate::encode::{Encode, IsNull}; use crate::error::BoxDynError; use crate::types::Type; use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres}; use byteorder::{BigEndian, ReadBytesExt}; use std::io::Cursor; use std::mem; #[cfg(feature = "time")] type DefaultTime = ::time::Time; #[cfg(all(not(feature = "time"), feature = "chrono"))] type DefaultTime = ::chrono::NaiveTime; #[cfg(feature = "time")] type DefaultOffset = ::time::UtcOffset; #[cfg(all(not(feature = "time"), feature = "chrono"))] type DefaultOffset = ::chrono::FixedOffset; /// Represents a moment of time, in a specified timezone. /// /// # Warning /// /// `PgTimeTz` provides `TIMETZ` and is supported only for reading from legacy databases. /// [PostgreSQL recommends] to use `TIMESTAMPTZ` instead. /// /// [PostgreSQL recommends]: https://wiki.postgresql.org/wiki/Don't_Do_This#Don.27t_use_timetz #[derive(Debug, PartialEq, Clone, Copy)] pub struct PgTimeTz