From 3e54169c30c4bccd9b5a4ce72ed123dbf677e96d Mon Sep 17 00:00:00 2001 From: Caio Date: Thu, 4 Jan 2024 02:26:30 -0300 Subject: [PATCH] Add Pool Manager --- .scripts/internal-tests.sh | 14 +- Cargo.lock | 203 +----------------- README.md | 10 +- wtx-bench/src/postgres.rs | 5 +- wtx-macros/src/pkg/data_format.rs | 10 +- wtx-ui/Cargo.toml | 6 +- wtx-ui/src/clap.rs | 32 +-- wtx-ui/src/embed_migrations.rs | 12 +- wtx-ui/src/main.rs | 4 +- wtx-ui/src/{sm.rs => schema_manager.rs} | 43 ++-- wtx/Cargo.toml | 19 +- .../database-client-postgres-tokio-rustls.rs | 19 +- wtx/examples/tls_stream/mod.rs | 37 ---- .../web-socket-client-cli-raw-tokio-rustls.rs | 15 +- ...web-socket-server-echo-raw-tokio-rustls.rs | 18 +- .../web-socket-server-pool-raw-tokio.rs | 22 +- wtx/src/client_api_framework/api.rs | 4 - wtx/src/client_api_framework/network/http.rs | 101 ++------- .../client_api_framework/network/transport.rs | 6 +- .../network/transport/h2.rs | 137 ++++++++++++ .../network/transport/reqwest.rs | 122 ----------- wtx/src/client_api_framework/pkg.rs | 4 - wtx/src/database.rs | 9 +- wtx/src/database/client/postgres.rs | 21 +- wtx/src/database/client/postgres/db_error.rs | 4 +- wtx/src/database/client/postgres/executor.rs | 94 +++++--- .../postgres/executor/authentication.rs | 28 ++- .../client/postgres/executor/fetch.rs | 5 +- .../client/postgres/executor/prepare.rs | 22 +- .../client/postgres/executor/simple_query.rs | 9 +- .../client/postgres/executor_buffer.rs | 10 + .../client/postgres/integration_tests.rs | 163 +++++++++----- wtx/src/database/client/postgres/message.rs | 11 +- wtx/src/database/client/postgres/protocol.rs | 2 +- wtx/src/database/client/postgres/record.rs | 65 ++++-- wtx/src/database/client/postgres/records.rs | 39 +++- .../database/client/postgres/statements.rs | 15 ++ .../client/postgres/transaction_manager.rs | 12 +- wtx/src/database/client/postgres/ty.rs | 3 +- wtx/src/database/client/postgres/tys.rs | 70 +++--- wtx/src/database/decode.rs | 29 +-- wtx/src/database/encode.rs | 30 +-- wtx/src/database/executor.rs | 91 ++++---- wtx/src/database/from_record.rs | 9 +- wtx/src/database/from_records.rs | 16 +- wtx/src/database/orm/crud.rs | 54 ++--- wtx/src/database/orm/misc.rs | 24 +-- wtx/src/database/record.rs | 23 +- wtx/src/database/record_values.rs | 58 ++--- wtx/src/database/{sm.rs => schema_manager.rs} | 18 +- .../{sm => schema_manager}/commands.rs | 6 +- .../{sm => schema_manager}/commands/clear.rs | 2 +- .../commands/migrate.rs | 6 +- .../commands/rollback.rs | 4 +- .../{sm => schema_manager}/commands/seed.rs | 4 +- .../commands/validate.rs | 4 +- .../{sm => schema_manager}/doc_tests.rs | 6 +- .../fixed_sql_commands.rs | 9 +- .../fixed_sql_commands/postgres.rs | 18 +- .../integration_tests.rs | 4 +- .../integration_tests/backend.rs | 2 +- .../integration_tests/db.rs | 0 .../integration_tests/db/postgres.rs | 14 +- .../integration_tests/generic.rs | 4 +- .../integration_tests/schema.rs | 2 +- .../integration_tests/schema/with_schema.rs | 5 +- .../schema/without_schema.rs | 5 +- .../database/{sm => schema_manager}/macros.rs | 0 .../{sm => schema_manager}/migration.rs | 0 .../migration/db_migration.rs | 7 +- .../migration/migration_common.rs | 2 +- .../migration/migration_group.rs | 4 +- .../migration/user_migration.rs | 16 +- .../migration_parser.rs | 4 +- .../database/{sm => schema_manager}/misc.rs | 10 +- .../{sm => schema_manager}/repeatability.rs | 0 .../{sm => schema_manager}/toml_parser.rs | 2 +- wtx/src/error.rs | 35 +-- wtx/src/http.rs | 2 + wtx/src/http/method.rs | 83 +++---- wtx/src/http/mime.rs | 35 +++ wtx/src/lib.rs | 2 + wtx/src/macros.rs | 54 +++-- wtx/src/misc.rs | 12 +- wtx/src/misc/partitioned_filled_buffer.rs | 4 + wtx/src/misc/poll_once.rs | 34 +++ wtx/src/misc/tokio_rustls.rs | 151 ++++++++++--- wtx/src/misc/traits.rs | 12 ++ wtx/src/misc/wrapper.rs | 30 --- wtx/src/pool_manager.rs | 15 ++ wtx/src/pool_manager/lock.rs | 67 ++++++ wtx/src/pool_manager/lock_guard.rs | 60 ++++++ wtx/src/pool_manager/resource_manager.rs | 120 +++++++++++ wtx/src/pool_manager/static_pool.rs | 139 ++++++++++++ wtx/tests/embed-migrations.rs | 4 +- wtx/tests/embedded_migrations/mod.rs | 12 +- 96 files changed, 1605 insertions(+), 1183 deletions(-) rename wtx-ui/src/{sm.rs => schema_manager.rs} (63%) delete mode 100644 wtx/examples/tls_stream/mod.rs create mode 100644 wtx/src/client_api_framework/network/transport/h2.rs delete mode 100644 wtx/src/client_api_framework/network/transport/reqwest.rs rename wtx/src/database/{sm.rs => schema_manager.rs} (94%) rename wtx/src/database/{sm => schema_manager}/commands.rs (90%) rename wtx/src/database/{sm => schema_manager}/commands/clear.rs (88%) rename wtx/src/database/{sm => schema_manager}/commands/migrate.rs (94%) rename wtx/src/database/{sm => schema_manager}/commands/rollback.rs (94%) rename wtx/src/database/{sm => schema_manager}/commands/seed.rs (85%) rename wtx/src/database/{sm => schema_manager}/commands/validate.rs (96%) rename wtx/src/database/{sm => schema_manager}/doc_tests.rs (78%) rename wtx/src/database/{sm => schema_manager}/fixed_sql_commands.rs (94%) rename wtx/src/database/{sm => schema_manager}/fixed_sql_commands/postgres.rs (93%) rename wtx/src/database/{sm => schema_manager}/integration_tests.rs (97%) rename wtx/src/database/{sm => schema_manager}/integration_tests/backend.rs (97%) rename wtx/src/database/{sm => schema_manager}/integration_tests/db.rs (100%) rename wtx/src/database/{sm => schema_manager}/integration_tests/db/postgres.rs (90%) rename wtx/src/database/{sm => schema_manager}/integration_tests/generic.rs (93%) rename wtx/src/database/{sm => schema_manager}/integration_tests/schema.rs (98%) rename wtx/src/database/{sm => schema_manager}/integration_tests/schema/with_schema.rs (88%) rename wtx/src/database/{sm => schema_manager}/integration_tests/schema/without_schema.rs (54%) rename wtx/src/database/{sm => schema_manager}/macros.rs (100%) rename wtx/src/database/{sm => schema_manager}/migration.rs (100%) rename wtx/src/database/{sm => schema_manager}/migration/db_migration.rs (90%) rename wtx/src/database/{sm => schema_manager}/migration/migration_common.rs (81%) rename wtx/src/database/{sm => schema_manager}/migration/migration_group.rs (85%) rename wtx/src/database/{sm => schema_manager}/migration/user_migration.rs (88%) rename wtx/src/database/{sm => schema_manager}/migration_parser.rs (98%) rename wtx/src/database/{sm => schema_manager}/misc.rs (97%) rename wtx/src/database/{sm => schema_manager}/repeatability.rs (100%) rename wtx/src/database/{sm => schema_manager}/toml_parser.rs (98%) create mode 100644 wtx/src/http/mime.rs create mode 100644 wtx/src/misc/poll_once.rs delete mode 100644 wtx/src/misc/wrapper.rs create mode 100644 wtx/src/pool_manager.rs create mode 100644 wtx/src/pool_manager/lock.rs create mode 100644 wtx/src/pool_manager/lock_guard.rs create mode 100644 wtx/src/pool_manager/resource_manager.rs create mode 100644 wtx/src/pool_manager/static_pool.rs diff --git a/.scripts/internal-tests.sh b/.scripts/internal-tests.sh index 3d61fde5..cbffc6e4 100755 --- a/.scripts/internal-tests.sh +++ b/.scripts/internal-tests.sh @@ -30,7 +30,6 @@ $rt test-with-features wtx cl-aux $rt test-with-features wtx client-api-framework $rt test-with-features wtx crypto-common $rt test-with-features wtx database -$rt test-with-features wtx deadpool $rt test-with-features wtx digest $rt test-with-features wtx embassy-net,_hack $rt test-with-features wtx embedded-tls @@ -39,6 +38,7 @@ $rt test-with-features wtx flate2 $rt test-with-features wtx futures $rt test-with-features wtx futures-lite $rt test-with-features wtx glommio +$rt test-with-features wtx h2 $rt test-with-features wtx hashbrown $rt test-with-features wtx hmac $rt test-with-features wtx http1 @@ -46,11 +46,11 @@ $rt test-with-features wtx httparse $rt test-with-features wtx md-5 $rt test-with-features wtx miniserde $rt test-with-features wtx orm +$rt test-with-features wtx pool-manager $rt test-with-features wtx postgres $rt test-with-features wtx proptest $rt test-with-features wtx protobuf $rt test-with-features wtx rand -$rt test-with-features wtx reqwest $rt test-with-features wtx ring $rt test-with-features wtx rkyv,_hack $rt test-with-features wtx rust_decimal @@ -64,8 +64,8 @@ $rt test-with-features wtx sha1 $rt test-with-features wtx sha2 $rt test-with-features wtx simd-json $rt test-with-features wtx simdutf8 -$rt test-with-features wtx sm -$rt test-with-features wtx sm-dev +$rt test-with-features wtx schema-manager +$rt test-with-features wtx schema-manager-dev $rt test-with-features wtx smol $rt test-with-features wtx std $rt test-with-features wtx test-strategy @@ -92,8 +92,8 @@ $rt test-generic wtx-macros $rt check-generic wtx-ui $rt test-with-features wtx-ui embed-migrations -$rt test-with-features wtx-ui sm -$rt test-with-features wtx-ui sm-dev +$rt test-with-features wtx-ui schema-manager +$rt test-with-features wtx-ui schema-manager-dev $rt test-with-features wtx-ui web-socket cargo check --bin autobahn-client --features "flate2,web-socket-handshake" @@ -106,4 +106,4 @@ cargo check --example web-socket-server-echo-raw-glommio --features "glommio,web cargo check --example web-socket-server-echo-raw-smol --features "smol,web-socket-handshake" cargo check --example web-socket-server-echo-raw-tokio --features "tokio,web-socket-handshake" cargo check --example web-socket-server-echo-raw-tokio-rustls --features "_tokio-rustls-server,web-socket-handshake" -cargo check --example web-socket-server-pool-raw-tokio --features "deadpool,web-socket-handshake" \ No newline at end of file +cargo check --example web-socket-server-pool-raw-tokio --features "pool-manager,web-socket-handshake" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 280f7bfa..f72cf575 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -707,16 +707,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -858,24 +848,6 @@ dependencies = [ "cipher", ] -[[package]] -name = "deadpool" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb84100978c1c7b37f09ed3ce3e5f843af02c2a2c431bae5b19230dad2c1b490" -dependencies = [ - "async-trait", - "deadpool-runtime", - "num_cpus", - "tokio", -] - -[[package]] -name = "deadpool-runtime" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49" - [[package]] name = "der" version = "0.7.8" @@ -1098,15 +1070,6 @@ version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1056f553da426e9c025a662efa48b52e62e0a3a7648aa2d15aeaaf7f0d329357" -[[package]] -name = "encoding_rs" -version = "0.8.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" -dependencies = [ - "cfg-if", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -1502,9 +1465,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.22" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +checksum = "e1d308f63daf4181410c242d34c11f928dcb3aa105852019e043c9d1f4e4368a" dependencies = [ "bytes", "fnv", @@ -1659,62 +1622,21 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" dependencies = [ "bytes", "fnv", "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - [[package]] name = "httparse" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "0.14.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.5.5", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "iana-time-zone" version = "0.1.58" @@ -1796,12 +1718,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "ipnet" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" - [[package]] name = "itertools" version = "0.12.0" @@ -2036,12 +1952,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "mini-internal" version = "0.1.34" @@ -2642,41 +2552,6 @@ dependencies = [ "bytecheck", ] -[[package]] -name = "reqwest" -version = "0.11.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "system-configuration", - "tokio", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", -] - [[package]] name = "ring" version = "0.17.7" @@ -2915,18 +2790,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "serde_yaml" version = "0.9.29" @@ -3322,27 +3185,6 @@ dependencies = [ "syn 2.0.42", ] -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "tap" version = "1.0.1" @@ -3535,12 +3377,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - [[package]] name = "tracing" version = "0.1.40" @@ -3600,12 +3436,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "trybuild" version = "1.0.86" @@ -3741,15 +3571,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -4032,16 +3853,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wtx" version = "0.12.0" @@ -4050,14 +3861,13 @@ dependencies = [ "arbitrary", "arrayvec", "async-std", - "async-trait", "atoi", "base64", "borsh", + "bytes", "chrono", "cl-aux", "crypto-common", - "deadpool", "digest", "embassy-net", "embedded-io-async", @@ -4067,15 +3877,16 @@ dependencies = [ "futures", "futures-lite 1.13.0", "glommio", + "h2", "hashbrown 0.14.3", "hmac", + "http", "httparse", "md-5", "miniserde", "proptest", "protobuf", "rand", - "reqwest", "ring", "rkyv", "rust_decimal", diff --git a/README.md b/README.md index 146f759f..ddccbc3a 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,17 @@ A collection of different transport implementations and related tools focused primarily on web technologies. +1. [Client API Framework](https://github.com/c410-f3r/wtx/tree/main/wtx/src/client_api_framework) +2. [Database Client](https://github.com/c410-f3r/wtx/tree/main/wtx/src/database/client) +3. [Database Object–Relational Mapping](https://github.com/c410-f3r/wtx/tree/main/wtx/src/database/orm) +4. [Database Schema Manager](https://github.com/c410-f3r/wtx/tree/main/wtx/src/database/schema_manager) +5. [HTTP2 Client/Server](https://github.com/c410-f3r/wtx/tree/main/wtx/src/http2) +5. [Pool Manager](https://github.com/c410-f3r/wtx/tree/main/wtx/src/pool_manager) +6. [WebSocket Client/Server](https://github.com/c410-f3r/wtx/tree/main/wtx/src/web_socket) + Embedded devices that have a heap allocator can use this `no_std` crate. -Documentation is available at . +A more resourceful documentation is available at . ## Benchmarks diff --git a/wtx-bench/src/postgres.rs b/wtx-bench/src/postgres.rs index c55b4340..7f129f54 100644 --- a/wtx-bench/src/postgres.rs +++ b/wtx-bench/src/postgres.rs @@ -218,7 +218,10 @@ async fn populate_db(rng: &mut StdRng, uri: &UriRef<'_>) { .unwrap(); } -async fn wtx_executor(rng: &mut StdRng, uri: &UriRef<'_>) -> Executor { +async fn wtx_executor( + rng: &mut StdRng, + uri: &UriRef<'_>, +) -> Executor { Executor::connect( &wtx::database::client::postgres::Config::from_uri(uri).unwrap(), ExecutorBuffer::with_default_params(rng), diff --git a/wtx-macros/src/pkg/data_format.rs b/wtx-macros/src/pkg/data_format.rs index c75537dd..bf6a4866 100644 --- a/wtx-macros/src/pkg/data_format.rs +++ b/wtx-macros/src/pkg/data_format.rs @@ -16,17 +16,17 @@ pub(crate) enum DataFormat { impl DataFormat { pub(crate) fn before_sending_defaults(&self, tg: &TransportGroup) -> TokenStream { macro_rules! http_method_and_mime_type { - ($method:ident, $mime_type:ident) => { + ($method:ident, $mime:ident) => { quote::quote!( - _ext_req_params.method = wtx::client_api_framework::network::HttpMethod::$method; - _ext_req_params.mime_type = Some(wtx::client_api_framework::network::HttpMimeType::$mime_type); + _ext_req_params.method = wtx::http::Method::$method; + _ext_req_params.mime = Some(wtx::http::Mime::$mime); ) }; } macro_rules! http_mime_type { - ($mime_type:ident) => { + ($mime:ident) => { quote::quote!( - _ext_req_params.mime_type = Some(wtx::client_api_framework::network::HttpMimeType::$mime_type); + _ext_req_params.mime = Some(wtx::http::Mime::$mime); ) }; } diff --git a/wtx-ui/Cargo.toml b/wtx-ui/Cargo.toml index 2efa9560..9034057f 100644 --- a/wtx-ui/Cargo.toml +++ b/wtx-ui/Cargo.toml @@ -6,9 +6,9 @@ wtx = { default-features = false, features = ["tokio"], path = "../wtx" } [features] default = [] -embed-migrations = ["clap", "tokio/fs", "wtx/sm", "wtx/std"] -sm = ["clap", "wtx/postgres", "wtx/sm", "wtx/std"] -sm-dev = ["dotenv", "sm", "wtx/_tracing-subscriber", "wtx/sm-dev"] +embed-migrations = ["clap", "tokio/fs", "wtx/schema-manager", "wtx/std"] +schema-manager = ["clap", "wtx/postgres", "wtx/schema-manager", "wtx/std"] +schema-manager-dev = ["dotenv", "schema-manager", "wtx/_tracing-subscriber", "wtx/schema-manager-dev"] web-socket = ["clap", "wtx/web-socket-handshake"] [package] diff --git a/wtx-ui/src/clap.rs b/wtx-ui/src/clap.rs index 6eb5aef7..df9bdc53 100644 --- a/wtx-ui/src/clap.rs +++ b/wtx-ui/src/clap.rs @@ -8,9 +8,9 @@ pub(crate) async fn init() -> wtx::Result<()> { Commands::EmbedMigrations(elem) => { crate::embed_migrations::embed_migrations(&elem.input, &elem.output).await?; } - #[cfg(feature = "sm")] - Commands::Sm(sm) => { - crate::sm::sm(&sm).await?; + #[cfg(feature = "schema-manager")] + Commands::SchemaManager(schema_manager) => { + crate::schema_manager::schema_manager(&schema_manager).await?; } #[cfg(feature = "web-socket")] Commands::Ws(elem) => match (elem.connect, elem.serve) { @@ -48,8 +48,8 @@ enum Commands { _Nothing, #[cfg(feature = "embed-migrations")] EmbedMigrations(EmbedMigrations), - #[cfg(feature = "sm")] - Sm(Sm), + #[cfg(feature = "schema-manager")] + SchemaManager(SchemaManager), #[cfg(feature = "web-socket")] Ws(Ws), } @@ -59,33 +59,33 @@ enum Commands { #[derive(Debug, clap::Args)] struct EmbedMigrations { /// Configuration file path - #[arg(default_value_t = wtx::database::sm::DEFAULT_CFG_FILE_NAME.into(), short = 'i', value_name = "Path")] + #[arg(default_value_t = wtx::database::schema_manager::DEFAULT_CFG_FILE_NAME.into(), short = 'i', value_name = "Path")] input: String, /// Rust file path #[arg(default_value = "embedded_migrations.rs", short = 'o', value_name = "Path")] output: String, } -/// Schema Management -#[cfg(feature = "sm")] +/// Schema Manager +#[cfg(feature = "schema-manager")] #[derive(Debug, clap::Args)] -pub(crate) struct Sm { +pub(crate) struct SchemaManager { /// Configuration file path. If not specified, defaults to "wtx.toml" in the current directory. #[arg(short = 'c')] pub(crate) toml: Option, #[command(subcommand)] - pub(crate) commands: SmCommands, + pub(crate) commands: SchemaManagerCommands, /// Number of files (migrations or seeds) that is going to be sent to the database in a /// single transaction. - #[arg(default_value_t = wtx::database::sm::DEFAULT_BATCH_SIZE, short = 'f')] + #[arg(default_value_t = wtx::database::schema_manager::DEFAULT_BATCH_SIZE, short = 'f')] pub(crate) files_num: usize, /// Seeds directory. If not specified, defaults to the optional directory specified in the /// configuration file. /// Returns an error if none of the options are available. - #[cfg(feature = "sm-dev")] + #[cfg(feature = "schema-manager-dev")] #[arg(short = 's')] pub(crate) seeds: Option, @@ -96,19 +96,19 @@ pub(crate) struct Sm { #[allow(unused_tuple_struct_fields)] #[derive(Debug, clap::Subcommand)] -pub(crate) enum SmCommands { +pub(crate) enum SchemaManagerCommands { /// Clean all database objects. For example, tables, triggers or procedures - #[cfg(feature = "sm-dev")] + #[cfg(feature = "schema-manager-dev")] Clean {}, /// Process local migrations that aren't in the database Migrate {}, /// Shortcut. - #[cfg(feature = "sm-dev")] + #[cfg(feature = "schema-manager-dev")] MigrateAndSeed {}, /// Returns database state to a point Rollback { versions: Vec }, /// Populates the database with data intended for testing - #[cfg(feature = "sm-dev")] + #[cfg(feature = "schema-manager-dev")] Seed {}, /// Checks if the database state is in sync with the local data Validate {}, diff --git a/wtx-ui/src/embed_migrations.rs b/wtx-ui/src/embed_migrations.rs index 755ad61d..0cb42a72 100644 --- a/wtx-ui/src/embed_migrations.rs +++ b/wtx-ui/src/embed_migrations.rs @@ -1,6 +1,6 @@ use std::{fmt::Write, path::Path}; use tokio::{fs::OpenOptions, io::AsyncWriteExt}; -use wtx::database::sm::misc::{group_and_migrations_from_path, parse_root_toml}; +use wtx::database::schema_manager::misc::{group_and_migrations_from_path, parse_root_toml}; pub(crate) async fn embed_migrations(input: &str, output: &str) -> wtx::Result<()> { let (mut migration_groups, _) = parse_root_toml(Path::new(input))?; @@ -9,7 +9,7 @@ pub(crate) async fn embed_migrations(input: &str, output: &str) -> wtx::Result<( migration_groups.sort(); buffer.push_str( - "#[rustfmt::skip]pub(crate) const GROUPS: wtx::database::sm::EmbeddedMigrationsTy = &[", + "#[rustfmt::skip]pub(crate) const GROUPS: wtx::database::schema_manager::EmbeddedMigrationsTy = &[", ); for mg_path in migration_groups { @@ -20,8 +20,8 @@ pub(crate) async fn embed_migrations(input: &str, output: &str) -> wtx::Result<( buffer.write_fmt(format_args!( concat!( "{{", - r#"const {mg_name}: &wtx::database::sm::MigrationGroup<&'static str> = &wtx::database::sm::MigrationGroup::new("{mg_name}",{mg_version});"#, - r#"const {mg_name}_MIGRATIONS: &[wtx::database::sm::UserMigrationRef<'static, 'static>] = &["# + r#"const {mg_name}: &wtx::database::schema_manager::MigrationGroup<&'static str> = &wtx::database::schema_manager::MigrationGroup::new("{mg_name}",{mg_version});"#, + r#"const {mg_name}_MIGRATIONS: &[wtx::database::schema_manager::UserMigrationRef<'static, 'static>] = &["# ), mg_name = mg_name, mg_version = mg_version @@ -37,7 +37,7 @@ pub(crate) async fn embed_migrations(input: &str, output: &str) -> wtx::Result<( let version = migration.version(); buffer.write_fmt(format_args!( - "wtx::database::sm::UserMigrationRef::from_all_parts({checksum},&[" + "wtx::database::schema_manager::UserMigrationRef::from_all_parts({checksum},&[" ))?; for db in dbs { buffer.push_str("wtx::database::DatabaseTy::"); @@ -48,7 +48,7 @@ pub(crate) async fn embed_migrations(input: &str, output: &str) -> wtx::Result<( match migration.repeatability() { None => buffer.push_str("None"), Some(elem) => buffer.write_fmt(format_args!( - "Some(wtx::database::sm::Repeatability::{})", + "Some(wtx::database::schema_manager::Repeatability::{})", elem.strings().ident ))?, } diff --git a/wtx-ui/src/main.rs b/wtx-ui/src/main.rs index 664fa695..6e71a8f8 100644 --- a/wtx-ui/src/main.rs +++ b/wtx-ui/src/main.rs @@ -4,8 +4,8 @@ mod clap; #[cfg(feature = "embed-migrations")] mod embed_migrations; -#[cfg(feature = "sm")] -mod sm; +#[cfg(feature = "schema-manager")] +mod schema_manager; #[cfg(feature = "web-socket")] mod web_socket; diff --git a/wtx-ui/src/sm.rs b/wtx-ui/src/schema_manager.rs similarity index 63% rename from wtx-ui/src/sm.rs rename to wtx-ui/src/schema_manager.rs index fc5624a3..ed7b01e9 100644 --- a/wtx-ui/src/sm.rs +++ b/wtx-ui/src/schema_manager.rs @@ -1,20 +1,20 @@ -use crate::clap::{Sm, SmCommands}; +use crate::clap::{SchemaManager, SchemaManagerCommands}; use std::{borrow::Cow, env::current_dir, path::Path}; use wtx::{ database::{ - sm::{Commands, DbMigration, SchemaManagement, DEFAULT_CFG_FILE_NAME}, + schema_manager::{Commands, DbMigration, SchemaManagement, DEFAULT_CFG_FILE_NAME}, Identifier, DEFAULT_URI_VAR, }, misc::UriRef, }; -pub(crate) async fn sm(sm: &Sm) -> wtx::Result<()> { - #[cfg(feature = "sm-dev")] +pub(crate) async fn schema_manager(sm: &SchemaManager) -> wtx::Result<()> { + #[cfg(feature = "schema-manager-dev")] { let err = std::io::ErrorKind::NotFound; let _path = dotenv::dotenv().map_err(|_err| wtx::Error::IoError(err.into()))?; } - #[cfg(feature = "sm-dev")] + #[cfg(feature = "schema-manager-dev")] wtx::misc::tracing_subscriber_init()?; let var = std::env::var(DEFAULT_URI_VAR)?; @@ -28,7 +28,7 @@ pub(crate) async fn sm(sm: &Sm) -> wtx::Result<()> { Ok(()) } -fn toml_file_path(sm: &Sm) -> wtx::Result> { +fn toml_file_path(sm: &SchemaManager) -> wtx::Result> { Ok(if let Some(el) = sm.toml.as_deref() { Cow::Borrowed(el) } else { @@ -39,7 +39,7 @@ fn toml_file_path(sm: &Sm) -> wtx::Result> { } #[inline] -async fn handle_commands(executor: E, sm: &Sm) -> wtx::Result<()> +async fn handle_commands(executor: E, sm: &SchemaManager) -> wtx::Result<()> where E: SchemaManagement, { @@ -49,35 +49,35 @@ where let mut commands = Commands::new(sm.files_num, executor); match &sm.commands { - #[cfg(feature = "sm-dev")] - SmCommands::Clean {} => { + #[cfg(feature = "schema-manager-dev")] + SchemaManagerCommands::Clean {} => { commands.clear((_buffer_cmd, _buffer_idents)).await?; } - SmCommands::Migrate {} => { + SchemaManagerCommands::Migrate {} => { commands .migrate_from_toml_path((_buffer_cmd, _buffer_db_migrations), &toml_file_path(sm)?) .await?; } - #[cfg(feature = "sm-dev")] - SmCommands::MigrateAndSeed {} => { + #[cfg(feature = "schema-manager-dev")] + SchemaManagerCommands::MigrateAndSeed {} => { let (migration_groups, seeds) = - wtx::database::sm::misc::parse_root_toml(&toml_file_path(sm)?)?; + wtx::database::schema_manager::misc::parse_root_toml(&toml_file_path(sm)?)?; commands .migrate_from_groups_paths((_buffer_cmd, _buffer_db_migrations), &migration_groups) .await?; commands.seed_from_dir(_buffer_cmd, seeds_file_path(sm, seeds.as_deref())?).await?; } - SmCommands::Rollback { versions: _versions } => { + SchemaManagerCommands::Rollback { versions: _versions } => { commands .rollback_from_toml((_buffer_cmd, _buffer_db_migrations), &toml_file_path(sm)?, &_versions) .await?; } - #[cfg(feature = "sm-dev")] - SmCommands::Seed {} => { - let (_, seeds) = wtx::database::sm::misc::parse_root_toml(&toml_file_path(sm)?)?; + #[cfg(feature = "schema-manager-dev")] + SchemaManagerCommands::Seed {} => { + let (_, seeds) = wtx::database::schema_manager::misc::parse_root_toml(&toml_file_path(sm)?)?; commands.seed_from_dir(_buffer_cmd, seeds_file_path(sm, seeds.as_deref())?).await?; } - SmCommands::Validate {} => { + SchemaManagerCommands::Validate {} => { commands .validate_from_toml((_buffer_cmd, _buffer_db_migrations), &toml_file_path(sm)?) .await?; @@ -86,8 +86,11 @@ where Ok(()) } -#[cfg(feature = "sm-dev")] -fn seeds_file_path<'a, 'b, 'c>(sm: &'a Sm, seeds_toml: Option<&'b Path>) -> wtx::Result<&'c Path> +#[cfg(feature = "schema-manager-dev")] +fn seeds_file_path<'a, 'b, 'c>( + sm: &'a SchemaManager, + seeds_toml: Option<&'b Path>, +) -> wtx::Result<&'c Path> where 'a: 'c, 'b: 'c, diff --git a/wtx/Cargo.toml b/wtx/Cargo.toml index 2a0cecb2..961fcb8d 100644 --- a/wtx/Cargo.toml +++ b/wtx/Cargo.toml @@ -46,21 +46,20 @@ required-features = ["_tokio-rustls-server", "web-socket-handshake"] [[example]] name = "web-socket-server-pool-raw-tokio" path = "examples/web-socket-server-pool-raw-tokio.rs" -required-features = ["deadpool", "web-socket-handshake"] +required-features = ["pool-manager", "web-socket-handshake"] [dependencies] ahash = { default-features = false, optional = true, version = "0.8" } arbitrary = { default-features = false, features = ["derive_arbitrary"], optional = true, version = "1.0" } arrayvec = { default-features = false, optional = true, version = "0.7" } async-std = { default-features = false, features = ["default"], optional = true, version = "1.0" } -async-trait = { default-features = false, optional = true, version = "0.1" } atoi = { default-features = false, optional = true, version = "2.0" } base64 = { default-features = false, features = ["alloc"], optional = true, version = "0.21" } borsh = { default-features = false, features = ["derive", "std"], optional = true, version = "1.0" } +bytes = { default-features = false, optional = true, version = "1.0" } chrono = { default-features = false, optional = true, version = "0.4" } cl-aux = { default-features = false, optional = true, features = ["alloc"], version = "4.0" } crypto-common = { default-features = false, optional = true, version = "0.1" } -deadpool = { default-features = false, features = ["managed", "unmanaged"], optional = true, version = "0.10" } digest = { default-features = false, features = ["mac"], optional = true, version = "0.10" } embassy-net = { default-features = false, features = ["tcp"], optional = true, version = "0.2" } embedded-io-async = { default-features = false, optional = true, version = "0.6" } @@ -70,15 +69,16 @@ flate2 = { default-features = false, features = ["zlib-ng"], optional = true, ve futures = { default-features = false, optional = true, version = "0.3" } futures-lite = { default-features = false, optional = true, version = "1.0" } glommio = { default-features = false, optional = true, version = "0.8" } +h2 = { default-features = false, optional = true, version = "0.4" } hashbrown = { default-features = false, features = ["ahash", "allocator-api2", "inline-more"], optional = true, version = "0.14" } hmac = { default-features = false, optional = true, version = "0.12" } +http = { default-features = false, optional = true, version = "1.0" } httparse = { default-features = false, optional = true, version = "1.0" } md-5 = { default-features = false, optional = true, version = "0.10" } miniserde = { default-features = false, optional = true, version = "0.1" } proptest = { default-features = false, features = ["alloc"], optional = true, version = "1.0" } protobuf = { default-features = false, optional = true, version = "3.0" } rand = { default-features = false, features = ["small_rng"], optional = true, version = "0.8" } -reqwest = { default-features = false, optional = true, version = "0.11" } ring = { default-features = false, optional = true, version = "0.17" } rkyv = { default-features = false, features = ["validation"], optional = true, version = "0.7" } rust_decimal = { default-features = false, optional = true, version = "1.0" } @@ -114,13 +114,14 @@ async-std = ["dep:async-std", "std"] borsh = ["dep:borsh", "std"] client-api-framework = ["cl-aux"] database = ["arrayvec"] -deadpool = ["dep:async-trait", "dep:deadpool"] default = [] embedded-tls = ["dep:embedded-io-async", "dep:embedded-tls"] glommio = ["futures-lite", "dep:glommio", "std"] +h2 = ["dep:bytes", "dep:h2", "dep:http"] http1 = ["httparse"] miniserde = ["dep:miniserde", "std"] orm = ["database", "dep:smallvec"] +pool-manager = [] postgres = ["ahash", "base64", "crypto-common", "database", "digest", "hashbrown", "md-5", "hmac", "sha2"] protobuf = ["dep:protobuf", "std"] serde = ["arrayvec?/serde", "cl-aux?/serde", "dep:serde"] @@ -128,12 +129,12 @@ serde_json = ["serde", "dep:serde_json", "std"] serde_yaml = ["serde", "dep:serde_yaml", "std"] serde-xml-rs = ["serde", "dep:serde-xml-rs", "std"] simd-json = ["serde", "dep:simd-json", "std"] -sm = ["database", "chrono"] -sm-dev = ["sm"] +schema-manager = ["database", "chrono"] +schema-manager-dev = ["schema-manager"] smol = ["dep:smol", "std"] std = ["arrayvec?/std", "cl-aux?/std", "miniserde?/std", "serde?/std", "serde_json?/std"] tokio = ["std", "dep:tokio"] -tokio-rustls = ["ring", "tokio", "dep:tokio-rustls"] +tokio-rustls = ["ring", "rustls-pki-types", "tokio", "dep:tokio-rustls"] web-socket = [] web-socket-handshake = ["base64", "http1", "sha1", "web-socket"] @@ -142,7 +143,7 @@ _bench = [] _hack = ["embassy-net?/medium-ip", "embassy-net?/proto-ipv4", "rkyv?/size_32", "simd-json?/allow-non-simd"] _integration-tests = [] _proptest = ["proptest", "std", "test-strategy"] -_tokio-rustls-client = ["rustls-pemfile", "rustls-pki-types", "tokio-rustls/tls12", "webpki-roots"] +_tokio-rustls-client = ["rustls-pemfile", "tokio-rustls/tls12", "webpki-roots"] _tokio-rustls-server = ["rustls-pemfile", "tokio-rustls"] _tracing-subscriber = ["tracing", "dep:tracing-subscriber", "dep:tracing-tree"] diff --git a/wtx/examples/database-client-postgres-tokio-rustls.rs b/wtx/examples/database-client-postgres-tokio-rustls.rs index 1f261a5f..b8bf490b 100644 --- a/wtx/examples/database-client-postgres-tokio-rustls.rs +++ b/wtx/examples/database-client-postgres-tokio-rustls.rs @@ -9,7 +9,7 @@ use wtx::{ client::postgres::{Config, Executor, ExecutorBuffer}, Executor as _, Record, Records, TransactionManager, }, - misc::{tls_stream_from_stream, UriRef}, + misc::{TokioRustlsConnector, UriRef}, rng::StdRng, }; @@ -23,15 +23,11 @@ async fn main() { let initial_stream = TcpStream::connect(uri.host()).await.unwrap(); let mut exec = Executor::connect_encrypted(&config, eb, initial_stream, &mut rng, |stream| async { - Ok( - tls_stream_from_stream( - uri.hostname(), - Some(include_bytes!("../../.certs/root-ca.crt")), - stream, - ) + TokioRustlsConnector::from_webpki_roots() + .push_certs(include_bytes!("../../.certs/root-ca.crt")) + .unwrap() + .with_generic_stream(uri.hostname(), stream) .await - .unwrap(), - ) }) .await .unwrap(); @@ -43,10 +39,7 @@ async fn main() { .unwrap(); let _ = tm .executor() - .execute_with_stmt::( - "INSERT INTO foo VALUES ($1, $2), ($3, $4)", - (1u32, "one", 2u32, "two"), - ) + .execute_with_stmt("INSERT INTO foo VALUES ($1, $2), ($3, $4)", (1u32, "one", 2u32, "two")) .await .unwrap(); tm.commit().await.unwrap(); diff --git a/wtx/examples/tls_stream/mod.rs b/wtx/examples/tls_stream/mod.rs deleted file mode 100644 index 7d7c0fb0..00000000 --- a/wtx/examples/tls_stream/mod.rs +++ /dev/null @@ -1,37 +0,0 @@ -use rustls_pemfile::certs; -use rustls_pki_types::ServerName; -use std::sync::Arc; -use tokio::net::TcpStream; -use tokio_rustls::{ - client::TlsStream, - rustls::{ClientConfig, RootCertStore}, - TlsConnector, -}; -use webpki_roots::TLS_SERVER_ROOTS; - -static ROOT_CA: &[u8] = include_bytes!("../../../.certs/root-ca.crt"); - -pub(crate) async fn tls_stream_from_hosts(host: &str, hostname: &str) -> TlsStream { - let stream = TcpStream::connect(host).await.unwrap(); - _tls_connector() - .connect(ServerName::try_from(hostname.to_string()).unwrap(), stream) - .await - .unwrap() -} - -pub(crate) async fn tls_stream_from_hostname_and_stream(hostname: &str, stream: TcpStream) -> TlsStream { - _tls_connector() - .connect(ServerName::try_from(hostname.to_string()).unwrap(), stream) - .await - .unwrap() -} - -// You probably shouldn't use self-signed root authorities in a production environment. -fn _tls_connector() -> TlsConnector { - let mut root_store = RootCertStore::empty(); - root_store.extend(TLS_SERVER_ROOTS.iter().cloned()); - let certs: Vec<_> = certs(&mut &*ROOT_CA).collect::>().unwrap(); - let _ = root_store.add_parsable_certificates(certs); - let config = ClientConfig::builder().with_root_certificates(root_store).with_no_client_auth(); - TlsConnector::from(Arc::new(config)) -} diff --git a/wtx/examples/web-socket-client-cli-raw-tokio-rustls.rs b/wtx/examples/web-socket-client-cli-raw-tokio-rustls.rs index 07a9dcee..cc084281 100644 --- a/wtx/examples/web-socket-client-cli-raw-tokio-rustls.rs +++ b/wtx/examples/web-socket-client-cli-raw-tokio-rustls.rs @@ -5,7 +5,7 @@ mod common; use tokio::io::{AsyncBufReadExt, BufReader}; use wtx::{ - misc::{tls_stream_from_host, UriRef}, + misc::{TokioRustlsConnector, UriRef}, rng::StdRng, web_socket::{ handshake::{WebSocketConnect, WebSocketConnectRaw}, @@ -23,13 +23,12 @@ async fn main() { fb, headers_buffer: &mut <_>::default(), rng: StdRng::default(), - stream: tls_stream_from_host( - uri.host(), - uri.hostname(), - Some(include_bytes!("../../.certs/root-ca.crt")), - ) - .await - .unwrap(), + stream: TokioRustlsConnector::from_webpki_roots() + .push_certs(include_bytes!("../../.certs/root-ca.crt")) + .unwrap() + .with_tcp_stream(uri.host(), uri.hostname()) + .await + .unwrap(), uri: &uri, wsb: WebSocketBuffer::default(), } diff --git a/wtx/examples/web-socket-server-echo-raw-tokio-rustls.rs b/wtx/examples/web-socket-server-echo-raw-tokio-rustls.rs index 4fc696dc..2259f248 100644 --- a/wtx/examples/web-socket-server-echo-raw-tokio-rustls.rs +++ b/wtx/examples/web-socket-server-echo-raw-tokio-rustls.rs @@ -3,10 +3,8 @@ #[path = "./common/mod.rs"] mod common; -use rustls_pemfile::{certs, pkcs8_private_keys}; -use std::sync::Arc; use tokio::net::TcpListener; -use tokio_rustls::{rustls::ServerConfig, TlsAcceptor}; +use wtx::misc::TokioRustlsAcceptor; static CERT: &[u8] = include_bytes!("../../.certs/cert.pem"); static KEY: &[u8] = include_bytes!("../../.certs/key.pem"); @@ -14,21 +12,13 @@ static KEY: &[u8] = include_bytes!("../../.certs/key.pem"); #[tokio::main] async fn main() { let listener = TcpListener::bind(common::_host_from_args()).await.unwrap(); - let tls_acceptor = tls_acceptor().unwrap(); + let acceptor = TokioRustlsAcceptor::default().with_cert_chain_and_priv_key(CERT, KEY).unwrap(); loop { let (stream, _) = listener.accept().await.unwrap(); - let local_tls_acceptor = tls_acceptor.clone(); + let local_acceptor = acceptor.clone(); let _jh = tokio::spawn(async move { - let tls_stream = local_tls_acceptor.accept(stream).await.unwrap(); + let tls_stream = local_acceptor.accept(stream).await.unwrap(); common::_accept_conn_and_echo_frames((), &mut <_>::default(), tls_stream).await.unwrap(); }); } } - -// You probably shouldn't use self-signed certificates in a production environment. -fn tls_acceptor() -> wtx::Result { - let key = pkcs8_private_keys(&mut &*KEY).next().unwrap().map(Into::into).unwrap(); - let certs: Vec<_> = certs(&mut &*CERT).collect::>().unwrap(); - let config = ServerConfig::builder().with_no_client_auth().with_single_cert(certs, key).unwrap(); - Ok(TlsAcceptor::from(Arc::new(config))) -} diff --git a/wtx/examples/web-socket-server-pool-raw-tokio.rs b/wtx/examples/web-socket-server-pool-raw-tokio.rs index 82645f46..b0694482 100644 --- a/wtx/examples/web-socket-server-pool-raw-tokio.rs +++ b/wtx/examples/web-socket-server-pool-raw-tokio.rs @@ -1,26 +1,23 @@ -//! Uses a pool of resources to avoid having to heap-allocate bytes for every new connection. This -//! approach also imposes an upper bound on the number of concurrent processing requests. +//! Uses a pool of resources to avoid having to heap-allocate for every new connection. //! -//! Semantically speaking, the WebSocket code only accepts a connection and then immediately +//! Semantically speaking, this WebSocket code only accepts a connection and then immediately //! closes it. #[path = "./common/mod.rs"] mod common; -use deadpool::unmanaged::{Object, Pool}; use std::sync::OnceLock; -use tokio::net::TcpListener; +use tokio::{net::TcpListener, sync::Mutex}; use wtx::{ + pool_manager::{ResourceManager, StaticPool, WebSocketRM}, rng::StaticRng, web_socket::{ handshake::{WebSocketAccept, WebSocketAcceptRaw}, - FrameBufferVec, FrameMutVec, OpCode, WebSocketBuffer, + FrameMutVec, OpCode, }, }; -const POOL_LEN: usize = 32; - -static POOL: OnceLock> = OnceLock::new(); +type Pool = StaticPool::Resource>>, WebSocketRM, 8>; #[tokio::main] async fn main() { @@ -28,7 +25,7 @@ async fn main() { loop { let (stream, _) = listener.accept().await.unwrap(); let _jh = tokio::spawn(async move { - let (fb, wsb) = &mut *pool_elem().await; + let (fb, wsb) = &mut *pool().get().await.unwrap(); let mut ws = WebSocketAcceptRaw { compression: (), rng: StaticRng::default(), stream, wsb } .accept(|_| true) .await @@ -38,6 +35,7 @@ async fn main() { } } -async fn pool_elem() -> Object<(FrameBufferVec, WebSocketBuffer)> { - POOL.get_or_init(|| Pool::from((0..POOL_LEN).map(|_| <_>::default()))).get().await.unwrap() +fn pool() -> &'static Pool { + static POOL: OnceLock = OnceLock::new(); + POOL.get_or_init(|| StaticPool::new(WebSocketRM).unwrap()) } diff --git a/wtx/src/client_api_framework/api.rs b/wtx/src/client_api_framework/api.rs index 2152d149..b0917273 100644 --- a/wtx/src/client_api_framework/api.rs +++ b/wtx/src/client_api_framework/api.rs @@ -3,10 +3,6 @@ use core::{fmt::Display, future::Future}; /// Api definitions group different packages into a common namespace and define custom additional /// logical through hooks. -#[allow( - // Downstream make use of async functionalities - clippy::unused_async -)] pub trait Api: AsyncBounds { /// Any custom error structure that can be constructed from [crate::Error]. type Error: Display + From; diff --git a/wtx/src/client_api_framework/network/http.rs b/wtx/src/client_api_framework/network/http.rs index 69f865ee..93e93775 100644 --- a/wtx/src/client_api_framework/network/http.rs +++ b/wtx/src/client_api_framework/network/http.rs @@ -2,10 +2,13 @@ mod status_code; -use core::fmt::{Arguments, Write}; - -use crate::{client_api_framework::network::transport::TransportParams, misc::UriString}; +use crate::{ + client_api_framework::network::transport::TransportParams, + http::{Method, Mime}, + misc::UriString, +}; use alloc::{string::String, vec::Vec}; +use core::fmt::{Arguments, Write}; pub use status_code::*; #[derive(Debug)] @@ -19,8 +22,8 @@ impl HttpParams { Self( HttpReqParams { headers: HttpHeaders::default(), - method: HttpMethod::Get, - mime_type: None, + method: Method::Get, + mime: None, uri: UriString::new(url.into()), user_agent: None, }, @@ -56,8 +59,8 @@ impl TransportParams for HttpParams { #[inline] fn reset(&mut self) { self.0.headers.clear(); - self.0.method = HttpMethod::Get; - self.0.mime_type = None; + self.0.method = Method::Get; + self.0.mime = None; self.0.uri.retain_with_initial_len(); self.0.user_agent = None; self.1.headers.clear(); @@ -65,86 +68,6 @@ impl TransportParams for HttpParams { } } -create_enum! { - /// Contains variants for a number of common HTTP methods such as GET, POST, etc. - #[derive(Clone, Copy, Debug)] - pub enum HttpMethod { - /// DELETE - Delete = (0, "delete"), - /// GET - Get = (1, "get"), - /// PATCH - Patch = (2, "patch"), - /// POST - Post = (3, "post"), - /// PUT - Put = (4, "put"), - } -} - -#[cfg(feature = "serde_json")] -mod serde_json { - use crate::client_api_framework::network::HttpMethod; - use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; - - impl<'de> Deserialize<'de> for HttpMethod { - #[inline] - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = <&str>::deserialize(deserializer)?; - Self::try_from(s).map_err(|err| de::Error::custom(err)) - } - } - - impl Serialize for HttpMethod { - #[inline] - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(self.strings().custom) - } - } -} - -/// Used to specify the data type that is going to be sent to a counterpart. -#[derive(Debug)] -pub enum HttpMimeType { - /// Opaque bytes - Bytes, - /// Anything - Custom(&'static str), - /// JSON - Json, - /// JSON:API - JsonApi, - /// Protocol buffer - Protobuf, - /// Plain text - Text, - /// XML - Xml, - /// YAML - Yaml, -} - -impl HttpMimeType { - pub(crate) fn _as_str(&self) -> &'static str { - match self { - HttpMimeType::Bytes => "application/octet-stream", - HttpMimeType::Custom(el) => el, - HttpMimeType::Json => "application/json", - HttpMimeType::JsonApi => "application/vnd.api+json", - HttpMimeType::Protobuf => "application/vnd.google.protobuf", - HttpMimeType::Text => "text/plain", - HttpMimeType::Xml => "application/xml", - HttpMimeType::Yaml => "application/yaml", - } - } -} - /// Characteristic string that lets servers and network peers identify a client. #[derive(Clone, Copy, Debug)] pub enum HttpUserAgent { @@ -166,9 +89,9 @@ pub struct HttpReqParams { /// Http headers. pub headers: HttpHeaders, /// Http method. - pub method: HttpMethod, + pub method: Method, /// MIME type. - pub mime_type: Option, + pub mime: Option, /// URL. pub uri: UriString, /// User agent. diff --git a/wtx/src/client_api_framework/network/transport.rs b/wtx/src/client_api_framework/network/transport.rs index df5a172a..ad7c7f3d 100644 --- a/wtx/src/client_api_framework/network/transport.rs +++ b/wtx/src/client_api_framework/network/transport.rs @@ -1,9 +1,9 @@ //! Implementations of the [Transport] trait. mod bi_transport; +#[cfg(feature = "h2")] +mod h2; mod mock; -#[cfg(feature = "reqwest")] -mod reqwest; #[cfg(feature = "std")] mod std; mod transport_params; @@ -24,6 +24,8 @@ use crate::{ pub use bi_transport::*; use cl_aux::DynContigColl; use core::{borrow::Borrow, future::Future, ops::Range}; +#[cfg(feature = "h2")] +pub use h2::H2; pub use mock::*; pub use transport_params::*; diff --git a/wtx/src/client_api_framework/network/transport/h2.rs b/wtx/src/client_api_framework/network/transport/h2.rs new file mode 100644 index 00000000..1e8d82eb --- /dev/null +++ b/wtx/src/client_api_framework/network/transport/h2.rs @@ -0,0 +1,137 @@ +use crate::{ + client_api_framework::{ + misc::{manage_after_sending_related, manage_before_sending_related}, + network::{ + transport::{Transport, TransportParams}, + HttpParams, TransportGroup, + }, + pkg::{Package, PkgsAux}, + Api, + }, + misc::{from_utf8_basic_rslt, AsyncBounds}, +}; +use bytes::Bytes; +use core::ops::Range; +use h2::client::SendRequest; +use http::{ + header::{CONTENT_TYPE, USER_AGENT}, + request::Builder, + HeaderValue, Request, +}; +use tokio::io::{AsyncRead, AsyncWrite}; + +/// Hyper +#[derive(Debug)] +pub struct H2 { + sender: SendRequest, +} + +impl H2 { + /// Performas a handshake and then returns an instance. + pub async fn new(io: T) -> crate::Result + where + T: AsyncRead + AsyncWrite + Send + Unpin + 'static, + { + let (sender, conn) = h2::client::Builder::new().handshake(io).await.unwrap(); + let _jh = tokio::task::spawn(async move { + if let Err(err) = conn.await { + eprintln!("{err}"); + } + }); + Ok(Self { sender }) + } +} + +impl Transport for H2 +where + DRSR: AsyncBounds, +{ + const GROUP: TransportGroup = TransportGroup::HTTP; + type Params = HttpParams; + + #[inline] + async fn send( + &mut self, + pkg: &mut P, + pkgs_aux: &mut PkgsAux, + ) -> Result<(), A::Error> + where + A: Api, + P: AsyncBounds + Package, + { + response(self, pkg, pkgs_aux).await?; + Ok(()) + } + + #[inline] + async fn send_and_retrieve( + &mut self, + pkg: &mut P, + pkgs_aux: &mut PkgsAux, + ) -> Result, A::Error> + where + A: Api, + P: AsyncBounds + Package, + { + response(self, pkg, pkgs_aux).await?; + Ok(0..pkgs_aux.byte_buffer.len()) + } +} + +async fn response( + h2: &mut H2, + pkg: &mut P, + pkgs_aux: &mut PkgsAux, +) -> Result<(), A::Error> +where + A: Api, + DRSR: AsyncBounds, + P: Package, +{ + fn manage_data(mut rb: Builder, pkgs_aux: &mut PkgsAux) -> Builder { + if let Some(data_format) = &pkgs_aux.tp.ext_req_params().mime { + rb = rb.header(CONTENT_TYPE, HeaderValue::from_static(data_format._as_str())); + } + rb + } + + let mut rb = Request::builder().uri(pkgs_aux.tp.ext_req_params().uri.uri()); + pkgs_aux.byte_buffer.clear(); + manage_before_sending_related(pkg, pkgs_aux, &mut *h2).await?; + rb = match pkgs_aux.tp.ext_req_params().method { + crate::http::Method::Connect => rb.method(http::Method::CONNECT), + crate::http::Method::Delete => rb.method(http::Method::DELETE), + crate::http::Method::Get => rb.method(http::Method::GET), + crate::http::Method::Head => rb.method(http::Method::HEAD), + crate::http::Method::Options => rb.method(http::Method::OPTIONS), + crate::http::Method::Patch => manage_data(rb.method(http::Method::PATCH), pkgs_aux), + crate::http::Method::Post => manage_data(rb.method(http::Method::POST), pkgs_aux), + crate::http::Method::Put => manage_data(rb.method(http::Method::PUT), pkgs_aux), + crate::http::Method::Trace => rb.method(http::Method::TRACE), + }; + for (key, value) in pkgs_aux.tp.ext_req_params().headers.iter() { + rb = rb.header(key, value); + } + if let Some(elem) = pkgs_aux.tp.ext_req_params().user_agent { + rb = rb.header(USER_AGENT, HeaderValue::from_static(elem._as_str())); + } + let (res_fut, mut stream) = h2.sender.send_request(rb.body(()).unwrap(), true).unwrap(); + stream.send_data(pkgs_aux.byte_buffer.clone().into(), true).unwrap(); + let mut res = res_fut.await.unwrap(); + pkgs_aux.byte_buffer.clear(); + let body = res.body_mut(); + while let Some(chunk) = body.data().await { + pkgs_aux.byte_buffer.extend(chunk.unwrap()); + } + for (key, value) in res.headers() { + pkgs_aux + .tp + .ext_res_params_mut() + .headers + .push_str(key.as_str(), from_utf8_basic_rslt(value.as_bytes()).map_err(Into::into)?)?; + } + pkgs_aux.tp.ext_res_params_mut().status_code = <_>::try_from(Into::::into(res.status()))?; + manage_after_sending_related(pkg, pkgs_aux).await?; + pkgs_aux.tp.reset(); + Ok(()) +} diff --git a/wtx/src/client_api_framework/network/transport/reqwest.rs b/wtx/src/client_api_framework/network/transport/reqwest.rs deleted file mode 100644 index 3a9e06f5..00000000 --- a/wtx/src/client_api_framework/network/transport/reqwest.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::{ - client_api_framework::{ - misc::{manage_after_sending_related, manage_before_sending_related}, - network::{ - http::HttpMethod, - transport::{Transport, TransportParams}, - HttpParams, TransportGroup, - }, - pkg::{Package, PkgsAux}, - Api, - }, - misc::{from_utf8_basic_rslt, AsyncBounds}, -}; -use core::ops::Range; -use reqwest::{ - header::{HeaderValue, CONTENT_TYPE, USER_AGENT}, - Client, RequestBuilder, -}; - -/// ```rust,no_run -/// # async fn fun() -> wtx::Result<()> { -/// use wtx::client_api_framework::{ -/// network::{transport::Transport, HttpParams}, -/// pkg::PkgsAux, -/// }; -/// let _ = reqwest::Client::new() -/// .send_retrieve_and_decode_contained( -/// &mut (), -/// &mut PkgsAux::from_minimum((), (), HttpParams::from_uri("URI").into()), -/// ) -/// .await?; -/// # Ok(()) } -/// ``` -impl Transport for Client -where - DRSR: AsyncBounds, -{ - const GROUP: TransportGroup = TransportGroup::HTTP; - type Params = HttpParams; - - #[inline] - async fn send( - &mut self, - pkg: &mut P, - pkgs_aux: &mut PkgsAux, - ) -> Result<(), A::Error> - where - A: Api, - P: AsyncBounds + Package, - { - let _res = response(self, pkg, pkgs_aux).await?; - Ok(()) - } - - #[inline] - async fn send_and_retrieve( - &mut self, - pkg: &mut P, - pkgs_aux: &mut PkgsAux, - ) -> Result, A::Error> - where - A: Api, - P: AsyncBounds + Package, - { - let res = response(self, pkg, pkgs_aux).await?; - let received_bytes = res.bytes().await.map_err(Into::into)?; - pkgs_aux.byte_buffer.extend(received_bytes.into_iter()); - Ok(0..pkgs_aux.byte_buffer.len()) - } -} - -async fn response( - client: &mut Client, - pkg: &mut P, - pkgs_aux: &mut PkgsAux, -) -> Result -where - A: Api, - DRSR: AsyncBounds, - P: Package, -{ - fn manage_data( - mut rb: RequestBuilder, - pkgs_aux: &mut PkgsAux, - ) -> RequestBuilder { - if let Some(data_format) = &pkgs_aux.tp.ext_req_params().mime_type { - rb = rb.header(CONTENT_TYPE, HeaderValue::from_static(data_format._as_str())); - } - rb = rb.body(pkgs_aux.byte_buffer.clone()); - rb - } - pkgs_aux.byte_buffer.clear(); - manage_before_sending_related(pkg, pkgs_aux, &mut *client).await?; - let mut rb = match pkgs_aux.tp.ext_req_params().method { - HttpMethod::Delete => client.delete(pkgs_aux.tp.ext_req_params().uri.uri()), - HttpMethod::Get => client.get(pkgs_aux.tp.ext_req_params().uri.uri()), - HttpMethod::Patch => { - manage_data(client.patch(pkgs_aux.tp.ext_req_params().uri.uri()), pkgs_aux) - } - HttpMethod::Post => manage_data(client.post(pkgs_aux.tp.ext_req_params().uri.uri()), pkgs_aux), - HttpMethod::Put => manage_data(client.put(pkgs_aux.tp.ext_req_params().uri.uri()), pkgs_aux), - }; - pkgs_aux.byte_buffer.clear(); - for (key, value) in pkgs_aux.tp.ext_req_params().headers.iter() { - rb = rb.header(key, value); - } - if let Some(elem) = pkgs_aux.tp.ext_req_params().user_agent { - rb = rb.header(USER_AGENT, HeaderValue::from_static(elem._as_str())); - } - let res = rb.send().await.map_err(Into::into)?; - for (key, value) in res.headers() { - pkgs_aux - .tp - .ext_res_params_mut() - .headers - .push_str(key.as_str(), from_utf8_basic_rslt(value.as_bytes()).map_err(Into::into)?)?; - } - pkgs_aux.tp.ext_res_params_mut().status_code = <_>::try_from(Into::::into(res.status()))?; - manage_after_sending_related(pkg, pkgs_aux).await?; - pkgs_aux.tp.reset(); - Ok(res) -} diff --git a/wtx/src/client_api_framework/pkg.rs b/wtx/src/client_api_framework/pkg.rs index 213d3ee8..ff2b2b24 100644 --- a/wtx/src/client_api_framework/pkg.rs +++ b/wtx/src/client_api_framework/pkg.rs @@ -27,10 +27,6 @@ pub use pkgs_aux::*; /// `A`: Associated API. /// `DRSR`: DeserializeR/SerializeR /// `TP`: Transport Parameters -#[allow( - // Downstream make use of async functionalities - clippy::unused_async -)] pub trait Package where A: Api, diff --git a/wtx/src/database.rs b/wtx/src/database.rs index 36747865..41aaa07c 100644 --- a/wtx/src/database.rs +++ b/wtx/src/database.rs @@ -1,4 +1,4 @@ -//! Client connection, schema management and ORM (Object–Relational Mapping). +//! Client connection, schema manager and ORM (Object–Relational Mapping). pub mod client; mod database_ty; @@ -12,8 +12,8 @@ pub mod orm; mod record; mod record_values; mod records; -#[cfg(feature = "sm")] -pub mod sm; +#[cfg(feature = "schema-manager")] +pub mod schema_manager; mod stmt; mod transaction_manager; mod value; @@ -47,6 +47,8 @@ pub trait Database { /// See [DatabaseTy]. const TY: DatabaseTy; + /// See [crate::Error]. + type Error: From; /// See [Record]. type Record<'rec>: Record; /// See [Records]. @@ -58,6 +60,7 @@ pub trait Database { impl Database for () { const TY: DatabaseTy = DatabaseTy::Unit; + type Error = crate::Error; type Record<'rec> = (); type Records<'recs> = (); type Value<'value> = (); diff --git a/wtx/src/database/client/postgres.rs b/wtx/src/database/client/postgres.rs index 87378000..d0d48db1 100644 --- a/wtx/src/database/client/postgres.rs +++ b/wtx/src/database/client/postgres.rs @@ -20,6 +20,8 @@ mod ty; mod tys; mod value; +use core::marker::PhantomData; + use crate::database::{Database, DatabaseTy}; pub(crate) use authentication::Authentication; pub use config::Config; @@ -40,12 +42,23 @@ pub(crate) type Oid = u32; /// Postgres #[derive(Debug)] -pub struct Postgres; +pub struct Postgres(PhantomData); + +impl Default for Postgres { + #[inline] + fn default() -> Self { + Self(PhantomData) + } +} -impl Database for Postgres { +impl Database for Postgres +where + E: From, +{ const TY: DatabaseTy = DatabaseTy::Postgres; - type Record<'rec> = Record<'rec>; - type Records<'recs> = Records<'recs>; + type Error = E; + type Record<'rec> = Record<'rec, E>; + type Records<'recs> = Records<'recs, E>; type Value<'value> = Value<'value>; } diff --git a/wtx/src/database/client/postgres/db_error.rs b/wtx/src/database/client/postgres/db_error.rs index 1dc560e3..b3306ef0 100644 --- a/wtx/src/database/client/postgres/db_error.rs +++ b/wtx/src/database/client/postgres/db_error.rs @@ -1,5 +1,5 @@ use crate::database::client::postgres::SqlState; -use alloc::string::String; +use alloc::boxed::Box; use core::{ fmt::{Debug, Formatter}, ops::Range, @@ -45,7 +45,7 @@ create_enum! { /// A Postgres error or notice. #[derive(Eq, PartialEq)] pub struct DbError { - buffer: String, + buffer: Box, code: SqlState, column: Option>, constraint: Option>, diff --git a/wtx/src/database/client/postgres/executor.rs b/wtx/src/database/client/postgres/executor.rs index 478dfd44..78ba561f 100644 --- a/wtx/src/database/client/postgres/executor.rs +++ b/wtx/src/database/client/postgres/executor.rs @@ -15,18 +15,18 @@ use crate::{ misc::{FilledBufferWriter, Stream, TlsStream}, rng::Rng, }; -use core::{borrow::BorrowMut, future::Future}; +use core::{borrow::BorrowMut, future::Future, marker::PhantomData}; /// Executor #[derive(Debug)] -pub struct Executor { +pub struct Executor { pub(crate) eb: EB, - pub(crate) process_id: i32, - pub(crate) secret_key: i32, + pub(crate) is_closed: bool, + pub(crate) phantom: PhantomData, pub(crate) stream: S, } -impl Executor +impl Executor where EB: BorrowMut, S: Stream, @@ -77,6 +77,12 @@ where .await } + /// Mutable buffer reference + #[inline] + pub fn eb_mut(&mut self) -> &mut ExecutorBuffer { + self.eb.borrow_mut() + } + #[inline] async fn do_connect( config: &Config<'_>, @@ -88,7 +94,7 @@ where where RNG: Rng, { - let mut this = Self { eb, process_id: 0, secret_key: 0, stream }; + let mut this = Self { eb, is_closed: false, phantom: PhantomData, stream }; this.send_initial_conn_msg(config).await?; this.manage_authentication(config, rng, tls_server_end_point).await?; this.read_after_authentication_data().await?; @@ -103,13 +109,14 @@ where } } -impl crate::database::Executor for Executor +impl crate::database::Executor for Executor where + E: From, EB: BorrowMut, S: Stream, { - type Database = Postgres; - type TransactionManager<'tm> = TransactionManager<'tm, EB, S> + type Database = Postgres; + type TransactionManager<'tm> = TransactionManager<'tm, E, EB, S> where Self: 'tm; @@ -120,18 +127,30 @@ where } #[inline] - async fn execute_with_stmt(&mut self, stmt_id: SI, rv: RV) -> Result + async fn execute_with_stmt( + &mut self, + stmt_id: SI, + rv: RV, + ) -> Result::Error> where - E: From, - RV: RecordValues, + RV: RecordValues, SI: StmtId, { self.eb.borrow_mut().clear(); let ExecutorBufferPartsMut { nb, stmts, .. } = self.eb.borrow_mut().parts_mut(); - let _ = Self::do_prepare_send_and_await(nb, rv, stmt_id, stmts, &mut self.stream, &[]).await?; + let _ = Self::do_prepare_send_and_await( + &mut self.is_closed, + nb, + rv, + stmt_id, + stmts, + &mut self.stream, + &[], + ) + .await?; let mut rows = 0; loop { - let msg = Self::fetch_msg_from_stream(nb, &mut self.stream).await?; + let msg = Self::fetch_msg_from_stream(&mut self.is_closed, nb, &mut self.stream).await?; match msg.ty { MessageTy::CommandComplete(local_rows) => { rows = local_rows; @@ -145,23 +164,30 @@ where } #[inline] - async fn fetch_with_stmt( + async fn fetch_with_stmt( &mut self, stmt_id: SI, rv: RV, ) -> Result<::Record<'_>, E> where - E: From, - RV: RecordValues, + RV: RecordValues, SI: StmtId, { self.eb.borrow_mut().clear(); let ExecutorBufferPartsMut { nb, stmts, vb, .. } = self.eb.borrow_mut().parts_mut(); - let stmt = - Self::do_prepare_send_and_await(nb, rv, stmt_id, stmts, &mut self.stream, &[]).await?; + let stmt = Self::do_prepare_send_and_await( + &mut self.is_closed, + nb, + rv, + stmt_id, + stmts, + &mut self.stream, + &[], + ) + .await?; let mut data_row_msg_range = None; loop { - let msg = Self::fetch_msg_from_stream(nb, &mut self.stream).await?; + let msg = Self::fetch_msg_from_stream(&mut self.is_closed, nb, &mut self.stream).await?; match msg.ty { MessageTy::DataRow(len, _) => { data_row_msg_range = Some((len, nb._current_range())); @@ -182,25 +208,32 @@ where } #[inline] - async fn fetch_many_with_stmt( + async fn fetch_many_with_stmt( &mut self, stmt_id: SI, rv: RV, mut cb: impl FnMut(::Record<'_>) -> Result<(), E>, ) -> Result<::Records<'_>, E> where - E: From, - RV: RecordValues, + RV: RecordValues, SI: StmtId, { self.eb.borrow_mut().clear(); let ExecutorBufferPartsMut { nb, rb, stmts, vb, .. } = self.eb.borrow_mut().parts_mut(); - let stmt = - Self::do_prepare_send_and_await(nb, rv, stmt_id, stmts, &mut self.stream, &[]).await?; + let stmt = Self::do_prepare_send_and_await( + &mut self.is_closed, + nb, + rv, + stmt_id, + stmts, + &mut self.stream, + &[], + ) + .await?; let begin = nb._current_end_idx(); let begin_data = nb._current_end_idx().wrapping_add(7); loop { - let msg = Self::fetch_msg_from_stream(nb, &mut self.stream).await?; + let msg = Self::fetch_msg_from_stream(&mut self.is_closed, nb, &mut self.stream).await?; match msg.ty { MessageTy::DataRow(len, _) => { let bytes = nb._buffer().get(begin_data..nb._current_end_idx()).unwrap_or_default(); @@ -223,18 +256,23 @@ where ._buffer() .get(begin_data.wrapping_add(4)..nb._current_end_idx()) .unwrap_or_default(), + phantom: PhantomData, records_values_offsets: rb, stmt, values_bytes_offsets: vb, }) } + #[inline] + fn is_closed(&self) -> bool { + self.is_closed + } + #[inline] async fn prepare(&mut self, cmd: &str) -> crate::Result { self.eb.borrow_mut().clear(); let ExecutorBufferPartsMut { nb, stmts, .. } = self.eb.borrow_mut().parts_mut(); - let (id, _, _) = Self::do_prepare(nb, cmd, stmts, &mut self.stream, &[]).await?; - Ok(id) + Ok(Self::do_prepare(&mut self.is_closed, nb, cmd, stmts, &mut self.stream, &[]).await?.0) } #[inline] diff --git a/wtx/src/database/client/postgres/executor/authentication.rs b/wtx/src/database/client/postgres/executor/authentication.rs index e082cfde..2543f4d2 100644 --- a/wtx/src/database/client/postgres/executor/authentication.rs +++ b/wtx/src/database/client/postgres/executor/authentication.rs @@ -17,7 +17,7 @@ use hmac::{Hmac, Mac}; use md5::{Digest, Md5}; use sha2::Sha256; -impl Executor +impl Executor where EB: BorrowMut, S: Stream, @@ -38,7 +38,7 @@ where RNG: Rng, { let ExecutorBufferPartsMut { nb, .. } = self.eb.borrow_mut().parts_mut(); - let msg0 = Self::fetch_msg_from_stream(nb, &mut self.stream).await?; + let msg0 = Self::fetch_msg_from_stream(&mut self.is_closed, nb, &mut self.stream).await?; match msg0.ty { MessageTy::Authentication(Authentication::Md5Password(salt)) => { let hashed = { @@ -72,13 +72,21 @@ where if !has_sasl_plus { return Err(crate::Error::UnknownAuthenticationMethod); } - Self::sasl_authenticate(config, nb, rng, &mut self.stream, tls_server_end_point).await?; + Self::sasl_authenticate( + config, + &mut self.is_closed, + nb, + rng, + &mut self.stream, + tls_server_end_point, + ) + .await?; } _ => { return Err(crate::Error::UnexpectedDatabaseMessage { received: msg0.tag }); } } - let msg1 = Self::fetch_msg_from_stream(nb, &mut self.stream).await?; + let msg1 = Self::fetch_msg_from_stream(&mut self.is_closed, nb, &mut self.stream).await?; if let MessageTy::Authentication(Authentication::Ok) = msg1.ty { Ok(()) } else { @@ -89,12 +97,9 @@ where pub(crate) async fn read_after_authentication_data(&mut self) -> crate::Result<()> { loop { let ExecutorBufferPartsMut { nb, params, .. } = self.eb.borrow_mut().parts_mut(); - let msg = Self::fetch_msg_from_stream(nb, &mut self.stream).await?; + let msg = Self::fetch_msg_from_stream(&mut self.is_closed, nb, &mut self.stream).await?; match msg.ty { - MessageTy::BackendKeyData(process_id, secret_key) => { - self.process_id = process_id; - self.secret_key = secret_key; - } + MessageTy::BackendKeyData(_, _) => {} MessageTy::ParameterStatus(name, value) => { params.insert( params.partition_point(|(local_name, _)| local_name.as_bytes() < name), @@ -111,6 +116,7 @@ where async fn sasl_authenticate( config: &Config<'_>, + is_closed: &mut bool, nb: &mut PartitionedFilledBuffer, rng: &mut RNG, stream: &mut S, @@ -129,7 +135,7 @@ where } let (mut auth_data, response_nonce, salted_password) = { - let msg = Self::fetch_msg_from_stream(&mut *nb, stream).await?; + let msg = Self::fetch_msg_from_stream(is_closed, &mut *nb, stream).await?; let MessageTy::Authentication(Authentication::SaslContinue { iterations, nonce, @@ -162,7 +168,7 @@ where } { - let msg = Self::fetch_msg_from_stream(&mut *nb, stream).await?; + let msg = Self::fetch_msg_from_stream(is_closed, &mut *nb, stream).await?; let MessageTy::Authentication(Authentication::SaslFinal(verifier_slice)) = msg.ty else { return Err(crate::Error::UnexpectedDatabaseMessage { received: msg.tag }); }; diff --git a/wtx/src/database/client/postgres/executor/fetch.rs b/wtx/src/database/client/postgres/executor/fetch.rs index 24dcd2e8..f8a676f2 100644 --- a/wtx/src/database/client/postgres/executor/fetch.rs +++ b/wtx/src/database/client/postgres/executor/fetch.rs @@ -4,17 +4,18 @@ use crate::{ }; use core::borrow::BorrowMut; -impl Executor +impl Executor where EB: BorrowMut, S: Stream, { pub(crate) async fn fetch_msg_from_stream<'nb>( + is_closed: &mut bool, nb: &'nb mut PartitionedFilledBuffer, stream: &mut S, ) -> crate::Result> { let tag = Self::fetch_representative_msg_from_stream(nb, stream).await?; - Ok(Message { tag, ty: MessageTy::try_from(nb._current())? }) + Ok(Message { tag, ty: MessageTy::try_from((is_closed, nb._current()))? }) } async fn fetch_one_header_from_stream( diff --git a/wtx/src/database/client/postgres/executor/prepare.rs b/wtx/src/database/client/postgres/executor/prepare.rs index 99849fb6..5a2a59e3 100644 --- a/wtx/src/database/client/postgres/executor/prepare.rs +++ b/wtx/src/database/client/postgres/executor/prepare.rs @@ -21,12 +21,14 @@ use crate::{ use arrayvec::ArrayString; use core::borrow::BorrowMut; -impl Executor +impl Executor where + E: From, EB: BorrowMut, S: Stream, { - pub(crate) async fn do_prepare_send_and_await<'stmts, E, SI, RV>( + pub(crate) async fn do_prepare_send_and_await<'stmts, SI, RV>( + is_closed: &mut bool, nb: &mut PartitionedFilledBuffer, rv: RV, stmt_id: SI, @@ -35,18 +37,17 @@ where tys: &[Ty], ) -> Result, E> where - E: From, - RV: RecordValues, + RV: RecordValues>, SI: StmtId, { - let (_, id_str, stmt) = Self::do_prepare(nb, stmt_id, stmts, stream, tys).await?; + let (_, id_str, stmt) = Self::do_prepare(is_closed, nb, stmt_id, stmts, stream, tys).await?; let mut fbw = FilledBufferWriter::from(&mut *nb); bind(&mut fbw, "", rv, &id_str)?; execute(&mut fbw, 0, "")?; sync(&mut fbw)?; stream.write_all(fbw._curr_bytes()).await?; - let bind_msg = Self::fetch_msg_from_stream(nb, stream).await?; + let bind_msg = Self::fetch_msg_from_stream(is_closed, nb, stream).await?; let MessageTy::BindComplete = bind_msg.ty else { return Err(crate::Error::UnexpectedDatabaseMessage { received: bind_msg.tag }.into()); }; @@ -54,6 +55,7 @@ where } pub(crate) async fn do_prepare<'stmts, SI>( + is_closed: &mut bool, nb: &mut PartitionedFilledBuffer, stmt_id: SI, stmts: &'stmts mut Statements, @@ -77,12 +79,12 @@ where sync(&mut fbw)?; stream.write_all(fbw._curr_bytes()).await?; - let msg0 = Self::fetch_msg_from_stream(nb, stream).await?; + let msg0 = Self::fetch_msg_from_stream(is_closed, nb, stream).await?; let MessageTy::ParseComplete = msg0.ty else { return Err(crate::Error::UnexpectedDatabaseMessage { received: msg0.tag }); }; - let msg1 = Self::fetch_msg_from_stream(nb, stream).await?; + let msg1 = Self::fetch_msg_from_stream(is_closed, nb, stream).await?; let MessageTy::ParameterDescription(mut pd) = msg1.ty else { return Err(crate::Error::UnexpectedDatabaseMessage { received: msg1.tag }); }; @@ -91,7 +93,7 @@ where pd = sub_data; } - let msg2 = Self::fetch_msg_from_stream(nb, stream).await?; + let msg2 = Self::fetch_msg_from_stream(is_closed, nb, stream).await?; match msg2.ty { MessageTy::RowDescription(mut rd) => { while !rd.is_empty() { @@ -107,7 +109,7 @@ where _ => return Err(crate::Error::UnexpectedDatabaseMessage { received: msg2.tag }), } - let msg3 = Self::fetch_msg_from_stream(nb, stream).await?; + let msg3 = Self::fetch_msg_from_stream(is_closed, nb, stream).await?; let MessageTy::ReadyForQuery = msg3.ty else { return Err(crate::Error::UnexpectedDatabaseMessage { received: msg3.tag }); }; diff --git a/wtx/src/database/client/postgres/executor/simple_query.rs b/wtx/src/database/client/postgres/executor/simple_query.rs index efdc986f..f517fbe1 100644 --- a/wtx/src/database/client/postgres/executor/simple_query.rs +++ b/wtx/src/database/client/postgres/executor/simple_query.rs @@ -4,7 +4,7 @@ use crate::{ }; use core::borrow::BorrowMut; -impl Executor +impl Executor where EB: BorrowMut, S: Stream, @@ -18,7 +18,12 @@ where query(cmd.as_bytes(), &mut fbw)?; self.stream.write_all(fbw._curr_bytes()).await?; loop { - let msg = Self::fetch_msg_from_stream(&mut self.eb.borrow_mut().nb, &mut self.stream).await?; + let msg = Self::fetch_msg_from_stream( + &mut self.is_closed, + &mut self.eb.borrow_mut().nb, + &mut self.stream, + ) + .await?; match msg.ty { MessageTy::CommandComplete(n) => cb(n), MessageTy::EmptyQueryResponse => { diff --git a/wtx/src/database/client/postgres/executor_buffer.rs b/wtx/src/database/client/postgres/executor_buffer.rs index 9a758ab4..31241b89 100644 --- a/wtx/src/database/client/postgres/executor_buffer.rs +++ b/wtx/src/database/client/postgres/executor_buffer.rs @@ -60,6 +60,16 @@ impl ExecutorBuffer { } } + pub(crate) fn _empty() -> Self { + Self { + nb: PartitionedFilledBuffer::_empty(), + params: Vec::new(), + rb: Vec::new(), + stmts: Statements::_empty(), + vb: Vec::new(), + } + } + /// Should be used in running instances. pub(crate) fn clear(&mut self) { let Self { nb, params: _, rb, stmts: _, vb } = self; diff --git a/wtx/src/database/client/postgres/integration_tests.rs b/wtx/src/database/client/postgres/integration_tests.rs index 51ad489e..bb49c071 100644 --- a/wtx/src/database/client/postgres/integration_tests.rs +++ b/wtx/src/database/client/postgres/integration_tests.rs @@ -1,9 +1,9 @@ use crate::{ database::{ - client::postgres::{Config, Executor, ExecutorBuffer}, - Executor as _, Record, Records as _, + client::postgres::{Config, Executor, ExecutorBuffer, Postgres, Value}, + Decode, Encode, Executor as _, Record, Records as _, }, - misc::{tls_stream_from_stream, UriRef}, + misc::{FilledBufferWriter, UriRef}, rng::StaticRng, }; use tokio::net::TcpStream; @@ -12,7 +12,7 @@ type Err = crate::Error; #[tokio::test] async fn conn_md5() { - let mut _executor = executor().await; + let mut _executor = executor::().await; } #[cfg(feature = "_tokio-rustls-client")] @@ -21,20 +21,19 @@ async fn conn_scram() { let uri = "postgres://wtx_scram:wtx@localhost:5433/wtx"; let uri = UriRef::new(&uri); let mut rng = StaticRng::default(); - let _executor = Executor::connect_encrypted( + let _executor = Executor::::connect_encrypted( &Config::from_uri(&uri).unwrap(), ExecutorBuffer::with_default_params(&mut rng), TcpStream::connect(uri.host()).await.unwrap(), &mut rng, |stream| async { Ok( - tls_stream_from_stream( - uri.hostname(), - Some(include_bytes!("../../../../../.certs/root-ca.crt")), - stream, - ) - .await - .unwrap(), + crate::misc::TokioRustlsConnector::from_webpki_roots() + .push_certs(include_bytes!("../../../../../.certs/root-ca.crt")) + .unwrap() + .with_generic_stream(uri.hostname(), stream) + .await + .unwrap(), ) }, ) @@ -42,9 +41,75 @@ async fn conn_scram() { .unwrap(); } +#[tokio::test] +async fn custom_domain() { + struct CustomDomain { + a: u8, + b: f64, + c: String, + } + + impl Decode<'_, Postgres> for CustomDomain { + fn decode(input: &Value<'_>) -> Result { + let a = <_ as Decode>>::decode(input)?; + let b = <_ as Decode>>::decode(input)?; + let c = <_ as Decode>>::decode(input)?; + Ok(Self { a, b, c }) + } + } + + impl Encode> for CustomDomain { + fn encode(&self, buffer: &mut FilledBufferWriter<'_>) -> Result<(), crate::Error> { + <_ as Encode>>::encode(&self.a, buffer)?; + <_ as Encode>>::encode(&self.b, buffer)?; + <_ as Encode>>::encode(&self.c, buffer)?; + Ok(()) + } + } + + let mut exec = executor::().await; + exec + .execute( + "DROP TYPE IF EXISTS custom_domain CASCADE; DROP TABLE IF EXISTS custom_domain_table", + |_| {}, + ) + .await + .unwrap(); + exec.execute("CREATE DOMAIN custom_domain AS VARCHAR(64)", |_| {}).await.unwrap(); + exec + .execute("CREATE TABLE custom_domain_table (id INT, domain custom_domain)", |_| {}) + .await + .unwrap(); + let _ = exec + .execute_with_stmt( + "INSERT INTO custom_domain_table VALUES ($1, $2)", + (1, CustomDomain { a: 2, b: 3.0, c: String::from("456") }), + ) + .await + .unwrap(); +} + +#[tokio::test] +async fn custom_error() { + #[derive(Debug)] + enum CustomError { + Wtx { _err: crate::Error }, + } + + impl From for CustomError { + fn from(from: crate::Error) -> Self { + Self::Wtx { _err: from } + } + } + + let mut exec = executor::().await; + let _ = exec.fetch_with_stmt("SELECT 1 WHERE 0=$1", (0,)).await.unwrap(); + let _ = exec.fetch_with_stmt("SELECT 1 WHERE 0=0", ()).await.unwrap(); +} + #[tokio::test] async fn execute_with_stmt() { - let mut exec = executor().await; + let mut exec = executor::().await; assert_eq!(exec.execute_with_stmt("", ()).await.unwrap(), 0); assert_eq!( @@ -61,7 +126,7 @@ async fn execute_with_stmt() { #[tokio::test] async fn multiple_notifications() { - let mut exec = executor().await; + let mut exec = executor::().await; let _ = exec .execute_with_stmt( "CREATE TABLE IF NOT EXISTS multiple_notifications_test (id SERIAL PRIMARY KEY, body TEXT)", @@ -75,23 +140,22 @@ async fn multiple_notifications() { #[tokio::test] async fn record() { - let mut exec = executor().await; + let mut exec = executor::().await; let _0c_0p = exec.fetch_with_stmt("", ()).await; assert!(matches!(_0c_0p.unwrap_err(), Err::NoRecord)); - let _0c_1p = exec.fetch_with_stmt::("SELECT 1 WHERE 0=$1", (1,)).await; + let _0c_1p = exec.fetch_with_stmt("SELECT 1 WHERE 0=$1", (1,)).await; assert!(matches!(_0c_1p.unwrap_err(), Err::NoRecord)); - let _0c_2p = exec.fetch_with_stmt::("SELECT 1 WHERE 0=$1 AND 1=$2", (1, 2)).await; + let _0c_2p = exec.fetch_with_stmt("SELECT 1 WHERE 0=$1 AND 1=$2", (1, 2)).await; assert!(matches!(_0c_2p.unwrap_err(), Err::NoRecord)); let _1c_0p = exec.fetch_with_stmt("SELECT 1", ()).await.unwrap(); assert_eq!(_1c_0p.len(), 1); assert_eq!(_1c_0p.decode::<_, u32>(0).unwrap(), 1); - let _1c_1p = exec.fetch_with_stmt::("SELECT 1 WHERE 0=$1", (0,)).await.unwrap(); + let _1c_1p = exec.fetch_with_stmt("SELECT 1 WHERE 0=$1", (0,)).await.unwrap(); assert_eq!(_1c_1p.len(), 1); assert_eq!(_1c_1p.decode::<_, u32>(0).unwrap(), 1); - let _1c_2p = - exec.fetch_with_stmt::("SELECT 1 WHERE 0=$1 AND 1=$2", (0, 1)).await.unwrap(); + let _1c_2p = exec.fetch_with_stmt("SELECT 1 WHERE 0=$1 AND 1=$2", (0, 1)).await.unwrap(); assert_eq!(_1c_2p.len(), 1); assert_eq!(_1c_2p.decode::<_, u32>(0).unwrap(), 1); @@ -99,12 +163,11 @@ async fn record() { assert_eq!(_2c_0p.len(), 2); assert_eq!(_2c_0p.decode::<_, u32>(0).unwrap(), 1); assert_eq!(_2c_0p.decode::<_, u32>(1).unwrap(), 2); - let _2c_1p = exec.fetch_with_stmt::("SELECT 1,2 WHERE 0=$1", (0,)).await.unwrap(); + let _2c_1p = exec.fetch_with_stmt("SELECT 1,2 WHERE 0=$1", (0,)).await.unwrap(); assert_eq!(_2c_1p.len(), 2); assert_eq!(_2c_1p.decode::<_, u32>(0).unwrap(), 1); assert_eq!(_2c_1p.decode::<_, u32>(1).unwrap(), 2); - let _2c_2p = - exec.fetch_with_stmt::("SELECT 1,2 WHERE 0=$1 AND 1=$2", (0, 1)).await.unwrap(); + let _2c_2p = exec.fetch_with_stmt("SELECT 1,2 WHERE 0=$1 AND 1=$2", (0, 1)).await.unwrap(); assert_eq!(_2c_2p.len(), 2); assert_eq!(_2c_2p.decode::<_, u32>(0).unwrap(), 1); assert_eq!(_2c_2p.decode::<_, u32>(1).unwrap(), 2); @@ -112,19 +175,16 @@ async fn record() { #[tokio::test] async fn records() { - let mut exec = executor().await; + let mut exec = executor::().await; // 0 rows, 0 columns let _0r_0c_0p = exec.fetch_many_with_stmt("", (), |_| Ok(())).await.unwrap(); assert_eq!(_0r_0c_0p.len(), 0); - let _0r_0c_1p = - exec.fetch_many_with_stmt::("SELECT 1 WHERE 0=$1", (1,), |_| Ok(())).await.unwrap(); + let _0r_0c_1p = exec.fetch_many_with_stmt("SELECT 1 WHERE 0=$1", (1,), |_| Ok(())).await.unwrap(); assert_eq!(_0r_0c_1p.len(), 0); - let _0r_0c_2p = exec - .fetch_many_with_stmt::("SELECT 1 WHERE 0=$1 AND 1=$2", (1, 2), |_| Ok(())) - .await - .unwrap(); + let _0r_0c_2p = + exec.fetch_many_with_stmt("SELECT 1 WHERE 0=$1 AND 1=$2", (1, 2), |_| Ok(())).await.unwrap(); assert_eq!(_0r_0c_2p.len(), 0); // 1 row, 1 column @@ -133,15 +193,12 @@ async fn records() { assert_eq!(_1r_1c_0p.len(), 1); assert_eq!(_1r_1c_0p.get(0).unwrap().decode::<_, u32>(0).unwrap(), 1); assert_eq!(_1r_1c_0p.get(0).unwrap().len(), 1); - let _1r_1c_1p = - exec.fetch_many_with_stmt::("SELECT 1 WHERE 0=$1", (0,), |_| Ok(())).await.unwrap(); + let _1r_1c_1p = exec.fetch_many_with_stmt("SELECT 1 WHERE 0=$1", (0,), |_| Ok(())).await.unwrap(); assert_eq!(_1r_1c_1p.len(), 1); assert_eq!(_1r_1c_1p.get(0).unwrap().decode::<_, u32>(0).unwrap(), 1); assert_eq!(_1r_1c_1p.get(0).unwrap().len(), 1); - let _1r_1c_2p = exec - .fetch_many_with_stmt::("SELECT 1 WHERE 0=$1 AND 1=$2", (0, 1), |_| Ok(())) - .await - .unwrap(); + let _1r_1c_2p = + exec.fetch_many_with_stmt("SELECT 1 WHERE 0=$1 AND 1=$2", (0, 1), |_| Ok(())).await.unwrap(); assert_eq!(_1r_1c_2p.len(), 1); assert_eq!(_1r_1c_2p.get(0).unwrap().decode::<_, u32>(0).unwrap(), 1); assert_eq!(_1r_1c_2p.get(0).unwrap().len(), 1); @@ -152,17 +209,13 @@ async fn records() { assert_eq!(_1r_2c_0p.len(), 1); assert_eq!(_1r_2c_0p.get(0).unwrap().decode::<_, u32>(0).unwrap(), 1); assert_eq!(_1r_2c_0p.get(0).unwrap().decode::<_, u32>(1).unwrap(), 2); - let _1r_2c_1p = exec - .fetch_many_with_stmt::("SELECT 1,2 WHERE 0=$1", (0,), |_| Ok(())) - .await - .unwrap(); + let _1r_2c_1p = + exec.fetch_many_with_stmt("SELECT 1,2 WHERE 0=$1", (0,), |_| Ok(())).await.unwrap(); assert_eq!(_1r_2c_1p.len(), 1); assert_eq!(_1r_2c_1p.get(0).unwrap().decode::<_, u32>(0).unwrap(), 1); assert_eq!(_1r_2c_1p.get(0).unwrap().decode::<_, u32>(1).unwrap(), 2); - let _1r_2c_2p = exec - .fetch_many_with_stmt::("SELECT 1,2 WHERE 0=$1 AND 1=$2", (0, 1), |_| Ok(())) - .await - .unwrap(); + let _1r_2c_2p = + exec.fetch_many_with_stmt("SELECT 1,2 WHERE 0=$1 AND 1=$2", (0, 1), |_| Ok(())).await.unwrap(); assert_eq!(_1r_2c_2p.len(), 1); assert_eq!(_1r_2c_2p.get(0).unwrap().decode::<_, u32>(0).unwrap(), 1); assert_eq!(_1r_2c_2p.get(0).unwrap().decode::<_, u32>(1).unwrap(), 2); @@ -179,11 +232,7 @@ async fn records() { assert_eq!(_2r_1c_0p.get(1).unwrap().len(), 1); assert_eq!(_2r_1c_0p.get(1).unwrap().decode::<_, u32>(0).unwrap(), 2); let _2r_1c_1p = exec - .fetch_many_with_stmt::( - "SELECT * FROM (VALUES (1), (2)) AS t (foo) WHERE 0=$1", - (0,), - |_| Ok(()), - ) + .fetch_many_with_stmt("SELECT * FROM (VALUES (1), (2)) AS t (foo) WHERE 0=$1", (0,), |_| Ok(())) .await .unwrap(); assert_eq!(_2r_1c_1p.len(), 2); @@ -192,7 +241,7 @@ async fn records() { assert_eq!(_2r_1c_1p.get(1).unwrap().len(), 1); assert_eq!(_2r_1c_1p.get(1).unwrap().decode::<_, u32>(0).unwrap(), 2); let _2r_1c_2p = exec - .fetch_many_with_stmt::( + .fetch_many_with_stmt( "SELECT * FROM (VALUES (1), (2)) AS t (foo) WHERE 0=$1 AND 1=$2", (0, 1), |_| Ok(()), @@ -219,7 +268,7 @@ async fn records() { assert_eq!(_2r_2c_0p.get(1).unwrap().decode::<_, u32>(0).unwrap(), 3); assert_eq!(_2r_2c_0p.get(1).unwrap().decode::<_, u32>(1).unwrap(), 4); let _2r_2c_1p = exec - .fetch_many_with_stmt::( + .fetch_many_with_stmt( "SELECT * FROM (VALUES (1,2), (3,4)) AS t (foo,bar) WHERE 0=$1", (0,), |_| Ok(()), @@ -234,7 +283,7 @@ async fn records() { assert_eq!(_2r_2c_1p.get(1).unwrap().decode::<_, u32>(0).unwrap(), 3); assert_eq!(_2r_2c_1p.get(1).unwrap().decode::<_, u32>(1).unwrap(), 4); let _2r_2c_2p = exec - .fetch_many_with_stmt::( + .fetch_many_with_stmt( "SELECT * FROM (VALUES (1,2), (3,4)) AS t (foo,bar) WHERE 0=$1 AND 1=$2", (0, 1), |_| Ok(()), @@ -252,19 +301,19 @@ async fn records() { #[tokio::test] async fn records_after_prepare() { - let mut exec = executor().await; + let mut exec = executor::().await; let _ = exec.prepare("SELECT 1").await.unwrap(); let _ = exec.fetch_many_with_stmt("SELECT 1", (), |_| Ok(())).await.unwrap(); } #[tokio::test] async fn reuses_cached_statement() { - let mut exec = executor().await; - let _record = exec.fetch_with_stmt::("SELECT 1 WHERE 0=$1", (0,)).await.unwrap(); - let _record = exec.fetch_with_stmt::("SELECT 1 WHERE 0=$1", (0,)).await.unwrap(); + let mut exec = executor::().await; + let _record = exec.fetch_with_stmt("SELECT 1 WHERE 0=$1", (0,)).await.unwrap(); + let _record = exec.fetch_with_stmt("SELECT 1 WHERE 0=$1", (0,)).await.unwrap(); } -async fn executor() -> Executor { +async fn executor() -> Executor { let uri = UriRef::new("postgres://wtx_md5:wtx@localhost:5432/wtx"); let mut rng = StaticRng::default(); Executor::connect( diff --git a/wtx/src/database/client/postgres/message.rs b/wtx/src/database/client/postgres/message.rs index 4f7b179c..860c5d3c 100644 --- a/wtx/src/database/client/postgres/message.rs +++ b/wtx/src/database/client/postgres/message.rs @@ -19,7 +19,7 @@ pub(crate) struct Message<'bytes> { pub(crate) enum MessageTy<'bytes> { /// See [Authentication]. Authentication(Authentication<'bytes>), - /// Data that the frontend must use to issue a cancelation request. + /// Data that the frontend must use to issue a cancellation request. BackendKeyData(i32, i32), /// Bind request was successful. BindComplete, @@ -59,12 +59,12 @@ pub(crate) enum MessageTy<'bytes> { RowDescription(&'bytes [u8]), } -impl<'bytes> TryFrom<&'bytes [u8]> for MessageTy<'bytes> { +impl<'bytes> TryFrom<(&mut bool, &'bytes [u8])> for MessageTy<'bytes> { type Error = crate::Error; #[inline] - fn try_from(from: &'bytes [u8]) -> Result { - let rslt = match from { + fn try_from(from: (&mut bool, &'bytes [u8])) -> Result { + let rslt = match from.1 { [b'1', ..] => Self::ParseComplete, [b'2', ..] => Self::BindComplete, [b'3', ..] => Self::CloseComplete, @@ -87,7 +87,8 @@ impl<'bytes> TryFrom<&'bytes [u8]> for MessageTy<'bytes> { } [b'D', _, _, _, _, a, b, rest @ ..] => Self::DataRow(u16::from_be_bytes([*a, *b]), rest), [b'E', _, _, _, _, rest @ ..] => { - return Err(DbError::try_from(from_utf8_basic_rslt(rest)?)?.into()) + *from.0 = true; + return Err(DbError::try_from(from_utf8_basic_rslt(rest)?)?.into()); } [b'G', ..] => Self::CopyInResponse, [b'H', ..] => Self::CopyOutResponse, diff --git a/wtx/src/database/client/postgres/protocol.rs b/wtx/src/database/client/postgres/protocol.rs index 2dd08d98..81be78d2 100644 --- a/wtx/src/database/client/postgres/protocol.rs +++ b/wtx/src/database/client/postgres/protocol.rs @@ -18,7 +18,7 @@ pub(crate) fn bind( ) -> Result<(), E> where E: From, - RV: RecordValues, + RV: RecordValues>, { write(fbw, true, Some(b'B'), |local_fbw| { local_fbw._extend_from_slices_each_c(&[portal.as_bytes(), stmt_str.as_bytes()]); diff --git a/wtx/src/database/client/postgres/record.rs b/wtx/src/database/client/postgres/record.rs index 4e35fac5..f1d33269 100644 --- a/wtx/src/database/client/postgres/record.rs +++ b/wtx/src/database/client/postgres/record.rs @@ -3,18 +3,19 @@ use crate::database::{ Database, ValueIdent, }; use alloc::vec::Vec; -use core::ops::Range; +use core::{marker::PhantomData, ops::Range}; /// Record -#[derive(Debug, Eq, PartialEq)] -pub struct Record<'exec> { +#[derive(Debug)] +pub struct Record<'exec, E> { pub(crate) bytes: &'exec [u8], pub(crate) initial_value_offset: usize, + pub(crate) phantom: PhantomData, pub(crate) stmt: Statement<'exec>, pub(crate) values_bytes_offsets: &'exec [(bool, Range)], } -impl<'exec> Record<'exec> { +impl<'exec, E> Record<'exec, E> { pub(crate) fn parse( mut bytes: &'exec [u8], bytes_range: Range, @@ -43,7 +44,13 @@ impl<'exec> Record<'exec> { fun(&mut curr_value_offset, [*a, *b, *c, *d])?; } _ => { - return Ok(Self { bytes, initial_value_offset: 0, stmt, values_bytes_offsets }); + return Ok(Self { + bytes, + initial_value_offset: 0, + phantom: PhantomData, + stmt, + values_bytes_offsets, + }); } } @@ -59,6 +66,7 @@ impl<'exec> Record<'exec> { Ok(Self { bytes, initial_value_offset, + phantom: PhantomData, stmt, values_bytes_offsets: values_bytes_offsets .get(values_bytes_offsets_start..) @@ -67,8 +75,11 @@ impl<'exec> Record<'exec> { } } -impl<'exec> crate::database::Record for Record<'exec> { - type Database = Postgres; +impl<'exec, E> crate::database::Record for Record<'exec, E> +where + E: From, +{ + type Database = Postgres; #[inline] fn len(&self) -> usize { @@ -78,7 +89,7 @@ impl<'exec> crate::database::Record for Record<'exec> { #[inline] fn value<'this, CI>(&'this self, ci: CI) -> Option<::Value<'this>> where - CI: ValueIdent>, + CI: ValueIdent>, { let (is_null, range) = self.values_bytes_offsets.get(ci.idx(self)?)?; if *is_null { @@ -91,28 +102,40 @@ impl<'exec> crate::database::Record for Record<'exec> { } } -impl<'exec> ValueIdent> for str { +impl<'exec, E> ValueIdent> for str { #[inline] - fn idx(&self, input: &Record<'exec>) -> Option { + fn idx(&self, input: &Record<'exec, E>) -> Option { input.stmt.columns.iter().position(|column| column.name.as_str() == self) } } +impl<'exec, E> PartialEq for Record<'exec, E> { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.bytes == other.bytes + && self.initial_value_offset == other.initial_value_offset + && self.phantom == other.phantom + && self.stmt == other.stmt + && self.values_bytes_offsets == other.values_bytes_offsets + } +} + #[cfg(feature = "arrayvec")] mod arrayvec { use crate::{ - database::{FromRecord, Record}, + database::{client::postgres::Postgres, FromRecord, Record}, misc::from_utf8_basic_rslt, }; use arrayvec::ArrayString; - impl<'exec, E, const N: usize> FromRecord> - for ArrayString + impl FromRecord> for ArrayString where E: From, { #[inline] - fn from_record(record: crate::database::client::postgres::Record<'exec>) -> Result { + fn from_record( + record: &crate::database::client::postgres::record::Record<'_, E>, + ) -> Result { Ok( from_utf8_basic_rslt(record.value(0).ok_or(crate::Error::NoInnerValue("Record"))?.bytes()) .map_err(From::from)? @@ -130,21 +153,29 @@ mod tests { Record as _, }; use alloc::vec; + use core::marker::PhantomData; #[test] fn returns_correct_values() { let bytes = &[0, 0, 0, 1, 1, 0, 0, 0, 2, 2, 3, 0, 0, 0, 1, 4]; let mut values_bytes_offsets = vec![]; let stmt = Statement::new(&[], &[]); - let record = - Record::parse(bytes, 0..bytes.len(), stmt.clone(), &mut values_bytes_offsets, 3).unwrap(); + let record = Record::::parse( + bytes, + 0..bytes.len(), + stmt.clone(), + &mut values_bytes_offsets, + 3, + ) + .unwrap(); assert_eq!( record, Record { bytes: &[1, 0, 0, 0, 2, 2, 3, 0, 0, 0, 1, 4], initial_value_offset: 0, stmt, - values_bytes_offsets: &[(false, 0..1), (false, 5..7), (false, 11..12)] + values_bytes_offsets: &[(false, 0usize..1usize), (false, 5..7), (false, 11..12)], + phantom: PhantomData } ); assert_eq!(record.len(), 3); diff --git a/wtx/src/database/client/postgres/records.rs b/wtx/src/database/client/postgres/records.rs index 3a680009..7082092e 100644 --- a/wtx/src/database/client/postgres/records.rs +++ b/wtx/src/database/client/postgres/records.rs @@ -1,10 +1,11 @@ use crate::database::client::postgres::{statements::Statement, Postgres, Record}; -use core::ops::Range; +use core::{marker::PhantomData, ops::Range}; /// Records -#[derive(Debug, Default, Eq, PartialEq)] -pub struct Records<'exec> { +#[derive(Debug)] +pub struct Records<'exec, E> { pub(crate) bytes: &'exec [u8], + pub(crate) phantom: PhantomData, /// Each element represents a record and an offset of `values_bytes_offsets`. pub(crate) records_values_offsets: &'exec [usize], pub(crate) stmt: Statement<'exec>, @@ -12,11 +13,14 @@ pub struct Records<'exec> { pub(crate) values_bytes_offsets: &'exec [(bool, Range)], } -impl<'exec> crate::database::Records for Records<'exec> { - type Database = Postgres; +impl<'exec, E> crate::database::Records for Records<'exec, E> +where + E: From, +{ + type Database = Postgres; #[inline] - fn get(&self, record_idx: usize) -> Option> { + fn get(&self, record_idx: usize) -> Option> { let slice = self.records_values_offsets.get(..record_idx.wrapping_add(1))?; let (record_bytes_range, record_values_bytes_offsets) = match slice { [] => return None, @@ -40,11 +44,12 @@ impl<'exec> crate::database::Records for Records<'exec> { initial_value_offset, stmt: self.stmt.clone(), values_bytes_offsets: record_values_bytes_offsets, + phantom: PhantomData, }) } #[inline] - fn iter(&self) -> impl Iterator> { + fn iter(&self) -> impl Iterator> { (0..self.len()).filter_map(|idx| self.get(idx)) } @@ -54,8 +59,23 @@ impl<'exec> crate::database::Records for Records<'exec> { } } +impl<'exec, E> Default for Records<'exec, E> { + #[inline] + fn default() -> Self { + Self { + bytes: <_>::default(), + phantom: PhantomData, + records_values_offsets: <_>::default(), + stmt: <_>::default(), + values_bytes_offsets: <_>::default(), + } + } +} + #[cfg(test)] mod tests { + use core::marker::PhantomData; + use crate::database::{ client::postgres::{statements::Statement, Record, Records}, Record as _, Records as _, @@ -72,6 +92,7 @@ mod tests { Record { bytes: &[1, 2, 0, 0, 0, 2, 3, 4], initial_value_offset: 0, + phantom: PhantomData::, stmt: Statement::new(&[], &[]), values_bytes_offsets: &[(false, 0..2), (false, 6..8)] } @@ -82,6 +103,7 @@ mod tests { Record { bytes: &[5, 6, 7, 8], initial_value_offset: 17, + phantom: PhantomData::, stmt: Statement::new(&[], &[]), values_bytes_offsets: &[(false, 17..21)] } @@ -90,6 +112,7 @@ mod tests { let records = Records { bytes: &bytes[4..], + phantom: PhantomData::, records_values_offsets: &records_values_offsets, stmt: Statement::new(&[], &[]), values_bytes_offsets: &values_bytes_offsets, @@ -105,6 +128,7 @@ mod tests { &Record { bytes: &[1, 2, 0, 0, 0, 2, 3, 4], initial_value_offset: 0, + phantom: PhantomData::, stmt: Statement::new(&[], &[]), values_bytes_offsets: &[(false, 0..2), (false, 6..8)] } @@ -118,6 +142,7 @@ mod tests { &Record { bytes: &[5, 6, 7, 8], initial_value_offset: 17, + phantom: PhantomData::, stmt: Statement::new(&[], &[]), values_bytes_offsets: &[(false, 17..21)] } diff --git a/wtx/src/database/client/postgres/statements.rs b/wtx/src/database/client/postgres/statements.rs index 3ff4a61f..57dd359d 100644 --- a/wtx/src/database/client/postgres/statements.rs +++ b/wtx/src/database/client/postgres/statements.rs @@ -59,6 +59,21 @@ impl Statements { } } + pub(crate) fn _empty() -> Self { + Self { + columns: VecDeque::new(), + columns_start: 0, + info: VecDeque::new(), + info_by_cmd_hash: HashMap::new(), + info_by_cmd_hash_start: 0, + hasher: RandomState::with_seeds(0, 0, 0, 0), + max_stmts: 0, + num_of_elements_to_remove_when_full: 0, + params: VecDeque::new(), + params_start: 0, + } + } + pub(crate) fn with_default_params(rng: &mut RNG) -> Self where RNG: Rng, diff --git a/wtx/src/database/client/postgres/transaction_manager.rs b/wtx/src/database/client/postgres/transaction_manager.rs index d1926aef..0ecb1bed 100644 --- a/wtx/src/database/client/postgres/transaction_manager.rs +++ b/wtx/src/database/client/postgres/transaction_manager.rs @@ -6,22 +6,22 @@ use core::borrow::BorrowMut; /// Transaction Manager #[derive(Debug)] -pub struct TransactionManager<'exec, EB, S> { - executor: &'exec mut Executor, +pub struct TransactionManager<'exec, E, EB, S> { + executor: &'exec mut Executor, } -impl<'exec, EB, S> TransactionManager<'exec, EB, S> { - pub(crate) fn new(executor: &'exec mut Executor) -> Self { +impl<'exec, E, EB, S> TransactionManager<'exec, E, EB, S> { + pub(crate) fn new(executor: &'exec mut Executor) -> Self { Self { executor } } } -impl<'exec, EB, S> crate::database::TransactionManager for TransactionManager<'exec, EB, S> +impl<'exec, E, EB, S> crate::database::TransactionManager for TransactionManager<'exec, E, EB, S> where EB: BorrowMut, S: Stream, { - type Executor = Executor; + type Executor = Executor; #[inline] async fn begin(&mut self) -> crate::Result<()> { diff --git a/wtx/src/database/client/postgres/ty.rs b/wtx/src/database/client/postgres/ty.rs index bf024501..526f2191 100644 --- a/wtx/src/database/client/postgres/ty.rs +++ b/wtx/src/database/client/postgres/ty.rs @@ -186,6 +186,7 @@ create_enum! { TsmultiRangeArray = (6152, "_tsmultirange"), TstzmultiRangeArray = (6153, "_tstzmultirange"), DatemultiRangeArray = (6155, "_datemultirange"), - Int8multiRangeArray = (6157, "_int8multirange") + Int8multiRangeArray = (6157, "_int8multirange"), + @Custom(6158..=u32::MAX) = (6158, "Custom") } } diff --git a/wtx/src/database/client/postgres/tys.rs b/wtx/src/database/client/postgres/tys.rs index 7335deaf..0b670e92 100644 --- a/wtx/src/database/client/postgres/tys.rs +++ b/wtx/src/database/client/postgres/tys.rs @@ -6,9 +6,9 @@ macro_rules! test { let mut vec = &mut alloc::vec::Vec::new(); let mut fbw = FilledBufferWriter::new(0, &mut vec); let instance: $ty = $instance; - Encode::<_, crate::Error>::encode(&instance, &mut fbw).unwrap(); + Encode::>::encode(&instance, &mut fbw).unwrap(); let decoded: $ty = - Decode::::decode(Value::new(fbw._curr_bytes())).unwrap(); + Decode::>::decode(&Value::new(fbw._curr_bytes())).unwrap(); assert_eq!(instance, decoded); } }; @@ -25,19 +25,17 @@ mod arrayvec { }; use arrayvec::ArrayString; - impl Decode for ArrayString + impl Decode<'_, Postgres> for ArrayString where E: From, { - type Value<'value> = Value<'value>; - #[inline] - fn decode(input: Self::Value<'_>) -> Result { + fn decode(input: &Value<'_>) -> Result { Ok(from_utf8_basic_rslt(input.bytes()).map_err(Into::into)?.try_into().map_err(Into::into)?) } } - impl Encode for ArrayString + impl Encode> for ArrayString where E: From, { @@ -62,14 +60,12 @@ mod chrono { }; use chrono::{DateTime, Duration, NaiveDate, NaiveDateTime, TimeZone, Utc}; - impl Decode for DateTime + impl Decode<'_, Postgres> for DateTime where E: From, { - type Value<'value> = Value<'value>; - #[inline] - fn decode(input: Self::Value<'_>) -> Result { + fn decode(input: &Value<'_>) -> Result { let rslt = || { let &[a, b, c, d, e, f, g, h] = input.bytes() else { return None; @@ -81,7 +77,7 @@ mod chrono { } } - impl Encode for DateTime + impl Encode> for DateTime where E: From, { @@ -116,19 +112,17 @@ mod collections { }; use alloc::string::String; - impl<'exec, E> Decode for &'exec [u8] + impl<'exec, E> Decode<'exec, Postgres> for &'exec [u8] where E: From, { - type Value<'value> = Value<'exec>; - #[inline] - fn decode(input: Self::Value<'_>) -> Result { + fn decode(input: &Value<'exec>) -> Result { Ok(input.bytes()) } } - impl Encode for &[u8] + impl Encode> for &[u8] where E: From, { @@ -141,19 +135,17 @@ mod collections { test!(bytes, &[u8], &[1, 2, 3, 4]); - impl<'exec, E> Decode for &'exec str + impl<'exec, E> Decode<'exec, Postgres> for &'exec str where E: From, { - type Value<'value> = Value<'exec>; - #[inline] - fn decode(input: Self::Value<'_>) -> Result { + fn decode(input: &Value<'exec>) -> Result { Ok(from_utf8_basic_rslt(input.bytes()).map_err(crate::Error::from)?) } } - impl Encode for &str + impl Encode> for &str where E: From, { @@ -166,19 +158,17 @@ mod collections { test!(str, &str, "1234"); - impl Decode for String + impl Decode<'_, Postgres> for String where E: From, { - type Value<'value> = Value<'value>; - #[inline] - fn decode(input: Self::Value<'_>) -> Result { + fn decode(input: &Value<'_>) -> Result { Ok(from_utf8_basic_rslt(input.bytes()).map_err(crate::Error::from)?.into()) } } - impl Encode for String + impl Encode> for String where E: From, { @@ -202,14 +192,12 @@ mod primitives { }; use core::mem; - impl Decode for bool + impl Decode<'_, Postgres> for bool where E: From, { - type Value<'value> = Value<'value>; - #[inline] - fn decode(input: Self::Value<'_>) -> Result { + fn decode(input: &Value<'_>) -> Result { let &[byte] = input.bytes() else { return Err( crate::Error::UnexpectedBufferSize { @@ -223,7 +211,7 @@ mod primitives { } } - impl Encode for bool + impl Encode> for bool where E: From, { @@ -241,23 +229,21 @@ mod primitives { ($instance:expr, [$($elem:ident),+], $signed:ident, $unsigned:ident) => { impl_primitive_from_array!($instance, [$($elem),+], $signed); - impl Decode for $unsigned + impl Decode<'_, Postgres> for $unsigned where E: From, { - type Value<'value> = Value<'value>; - #[inline] - fn decode(input: Self::Value<'_>) -> Result { + fn decode(input: &Value<'_>) -> Result { Ok( - <$signed as Decode::>::decode(input)? + <$signed as Decode::>>::decode(input)? .try_into() .map_err(|_err| crate::Error::InvalidPostgresUint)? ) } } - impl Encode for $unsigned + impl Encode> for $unsigned where E: From, { @@ -277,14 +263,12 @@ mod primitives { macro_rules! impl_primitive_from_array { ($instance:expr, [$($elem:ident),+], $ty:ident) => { - impl Decode for $ty + impl Decode<'_, Postgres> for $ty where E: From, { - type Value<'value> = Value<'value>; - #[inline] - fn decode(input: Self::Value<'_>) -> Result { + fn decode(input: &Value<'_>) -> Result { if let &[$($elem),+] = input.bytes() { return Ok(<$ty>::from_be_bytes([$($elem),+])); } @@ -295,7 +279,7 @@ mod primitives { } } - impl Encode for $ty + impl Encode> for $ty where E: From, { diff --git a/wtx/src/database/decode.rs b/wtx/src/database/decode.rs index aa27cc52..e910f4c6 100644 --- a/wtx/src/database/decode.rs +++ b/wtx/src/database/decode.rs @@ -1,40 +1,31 @@ -use crate::database::Value; +use crate::database::Database; /// Similar to `TryFrom`. Avoids problems with coherence and has an additional `E` type. -pub trait Decode: Sized +pub trait Decode<'de, D>: Sized where - E: From, + D: Database, { - /// See [Value]. - type Value<'value>: Value; - /// Performs the conversion. - fn decode(input: Self::Value<'_>) -> Result; + fn decode(input: &D::Value<'de>) -> Result; } -impl Decode<(), crate::Error> for &str { - type Value<'value> = (); - +impl<'de> Decode<'de, ()> for &str { #[inline] - fn decode(_: Self::Value<'_>) -> Result { + fn decode(_: &()) -> Result { Ok("") } } -impl Decode<(), crate::Error> for u32 { - type Value<'value> = (); - +impl<'de> Decode<'de, ()> for u32 { #[inline] - fn decode(_: Self::Value<'_>) -> Result { + fn decode(_: &()) -> Result { Ok(0) } } -impl Decode<(), crate::Error> for u64 { - type Value<'value> = (); - +impl<'de> Decode<'de, ()> for u64 { #[inline] - fn decode(_: Self::Value<'_>) -> Result { + fn decode(_: &()) -> Result { Ok(0) } } diff --git a/wtx/src/database/encode.rs b/wtx/src/database/encode.rs index dda94dcc..3422a2d1 100644 --- a/wtx/src/database/encode.rs +++ b/wtx/src/database/encode.rs @@ -1,12 +1,12 @@ -use crate::misc::FilledBufferWriter; +use crate::{database::Database, misc::FilledBufferWriter}; /// Encodes a type into a byte representation. -pub trait Encode +pub trait Encode where - E: From, + D: Database, { /// Performs the conversion. - fn encode(&self, buffer: &mut FilledBufferWriter<'_>) -> Result<(), E>; + fn encode(&self, buffer: &mut FilledBufferWriter<'_>) -> Result<(), D::Error>; /// In rust terms, is the element `Option::None`? #[inline] @@ -15,34 +15,34 @@ where } } -impl Encode for &T +impl Encode for &T where - E: From, - T: Encode, + D: Database, + T: Encode, { #[inline] - fn encode(&self, buffer: &mut FilledBufferWriter<'_>) -> Result<(), E> { + fn encode(&self, buffer: &mut FilledBufferWriter<'_>) -> Result<(), D::Error> { (**self).encode(buffer) } } -impl Encode for &dyn Encode +impl Encode for &dyn Encode where - E: From, + D: Database, { #[inline] - fn encode(&self, buffer: &mut FilledBufferWriter<'_>) -> Result<(), E> { + fn encode(&self, buffer: &mut FilledBufferWriter<'_>) -> Result<(), D::Error> { (**self).encode(buffer) } } -impl Encode for Option +impl Encode for Option where - E: From, - T: Encode, + D: Database, + T: Encode, { #[inline] - fn encode(&self, buffer: &mut FilledBufferWriter<'_>) -> Result<(), E> { + fn encode(&self, buffer: &mut FilledBufferWriter<'_>) -> Result<(), D::Error> { match self { None => Ok(()), Some(elem) => elem.encode(buffer), diff --git a/wtx/src/database/executor.rs b/wtx/src/database/executor.rs index 308d7d38..8096de60 100644 --- a/wtx/src/database/executor.rs +++ b/wtx/src/database/executor.rs @@ -12,7 +12,6 @@ pub trait Executor { type TransactionManager<'tm>: TransactionManager where Self: 'tm; - /// Executes severals commands returning the number of affected records on each `cb` call. /// /// Commands are not cached or inspected for potential vulnerabilities. @@ -20,41 +19,47 @@ pub trait Executor { /// Executes a **single** statement automatically binding the values of `rv` to the referenced /// `stmt_id` and then returns the number of affected records. - fn execute_with_stmt( + fn execute_with_stmt( &mut self, stmt_id: SI, rv: RV, - ) -> impl Future> + ) -> impl Future::Error>> where - E: From, - RV: RecordValues, + RV: RecordValues, SI: StmtId; /// Executes a **single** statement automatically binding the values of `rv` to the referenced /// `stmt_id` and then returns a **single** record. - fn fetch_with_stmt( + fn fetch_with_stmt( &mut self, stmt_id: SI, sv: RV, - ) -> impl Future::Record<'_>, E>> + ) -> impl Future< + Output = Result<::Record<'_>, ::Error>, + > where - E: From, - RV: RecordValues, + RV: RecordValues, SI: StmtId; /// Executes a **single** statement automatically binding the values of `rv` to the referenced /// `stmt_id` and then returns a **set** of records. - fn fetch_many_with_stmt( + fn fetch_many_with_stmt( &mut self, stmt_id: SI, sv: RV, - cb: impl FnMut(::Record<'_>) -> Result<(), E>, - ) -> impl Future::Records<'_>, E>> + cb: impl FnMut( + ::Record<'_>, + ) -> Result<(), ::Error>, + ) -> impl Future< + Output = Result<::Records<'_>, ::Error>, + > where - E: From, - RV: RecordValues, + RV: RecordValues, SI: StmtId; + /// Somethings the backend can discontinue the connection. + fn is_closed(&self) -> bool; + /// Caches the passed command to create a statement, which speeds up subsequent calls that match /// the same `cmd`. /// @@ -63,33 +68,35 @@ pub trait Executor { /// Retrieves a record and maps it to `T`. See [FromRecord]. #[inline] - fn simple_entity(&mut self, cmd: &str, sv: SV) -> impl Future> + fn simple_entity( + &mut self, + cmd: &str, + sv: SV, + ) -> impl Future::Error>> where - E: From, - T: for<'rec> FromRecord::Record<'rec>>, - SV: RecordValues, + T: for<'rec> FromRecord, + SV: RecordValues, { - async move { T::from_record(self.fetch_with_stmt(cmd, sv).await?) } + async move { T::from_record(&self.fetch_with_stmt(cmd, sv).await?) } } /// Retrieves a set of records and maps them to the corresponding `T`. See [FromRecord]. #[inline] - fn simple_entities( + fn simple_entities( &mut self, cmd: &str, results: &mut Vec, sv: SV, - ) -> impl Future> + ) -> impl Future::Error>> where - E: From, - SV: RecordValues, - T: for<'rec> FromRecord::Record<'rec>>, + SV: RecordValues, + T: for<'rec> FromRecord, { async move { let _records = self .fetch_many_with_stmt(cmd, sv, |record| { - results.push(T::from_record(record)?); - Ok::<_, E>(()) + results.push(T::from_record(&record)?); + Ok(()) }) .await?; Ok(()) @@ -111,44 +118,52 @@ impl Executor for () { } #[inline] - async fn execute_with_stmt(&mut self, _: SI, _: RV) -> Result + async fn execute_with_stmt( + &mut self, + _: SI, + _: RV, + ) -> Result::Error> where - E: From, - RV: RecordValues, + RV: RecordValues, SI: StmtId, { Ok(0) } #[inline] - async fn fetch_with_stmt( + async fn fetch_with_stmt( &mut self, _: SI, _: RV, - ) -> Result<::Record<'_>, E> + ) -> Result<::Record<'_>, ::Error> where - E: From, - RV: RecordValues, + RV: RecordValues, SI: StmtId, { Ok(()) } #[inline] - async fn fetch_many_with_stmt( + async fn fetch_many_with_stmt( &mut self, _: SI, _: RV, - _: impl FnMut(::Record<'_>) -> Result<(), E>, - ) -> Result<(), E> + _: impl FnMut( + ::Record<'_>, + ) -> Result<(), ::Error>, + ) -> Result<(), ::Error> where - E: From, - RV: RecordValues, + RV: RecordValues, SI: StmtId, { Ok(()) } + #[inline] + fn is_closed(&self) -> bool { + true + } + #[inline] async fn prepare(&mut self, _: &str) -> crate::Result { Ok(0) diff --git a/wtx/src/database/from_record.rs b/wtx/src/database/from_record.rs index 3302c9b6..07b8b27b 100644 --- a/wtx/src/database/from_record.rs +++ b/wtx/src/database/from_record.rs @@ -1,12 +1,11 @@ -use crate::database::Record; +use crate::database::Database; /// An element that can be represented from a single database record. In most cases it means /// a database table without relationships. -pub trait FromRecord: Sized +pub trait FromRecord: Sized where - E: From, - R: Record, + D: Database, { /// Fallible entry-point that maps the element. - fn from_record(record: R) -> Result; + fn from_record(record: &D::Record<'_>) -> Result; } diff --git a/wtx/src/database/from_records.rs b/wtx/src/database/from_records.rs index 87e4fc8f..f5debeab 100644 --- a/wtx/src/database/from_records.rs +++ b/wtx/src/database/from_records.rs @@ -3,17 +3,15 @@ use alloc::string::String; /// An element that can be represented from one or more database row, in other words, tables /// with relationships. -pub trait FromRecords: Sized { - /// See [Database]. - type Database: Database; - /// Error. - type Error: From; - +pub trait FromRecords: Sized +where + D: Database, +{ /// Constructs a single instance based on an arbitrary number of rows. fn from_records( buffer_cmd: &mut String, - curr_record: &::Record<'_>, - records: &::Records<'_>, + curr_record: &D::Record<'_>, + records: &D::Records<'_>, table_suffix: TableSuffix, - ) -> Result<(usize, Self), Self::Error>; + ) -> Result<(usize, Self), D::Error>; } diff --git a/wtx/src/database/orm/crud.rs b/wtx/src/database/orm/crud.rs index 309b874c..8e77a02e 100644 --- a/wtx/src/database/orm/crud.rs +++ b/wtx/src/database/orm/crud.rs @@ -19,7 +19,7 @@ pub trait Crud: Executor { table_params: &mut TableParams<'entity, T>, ) -> impl Future> where - T: Table<'entity>, + T: Table<'entity, Error = ::Error>, T::Associations: SqlWriter, { async move { @@ -29,7 +29,7 @@ pub trait Crud: Executor { buffer_cmd, &mut None, )?; - let _ = self.execute_with_stmt(buffer_cmd.as_str(), ()).await.map_err(Into::into)?; + let _ = self.execute_with_stmt(buffer_cmd.as_str(), ()).await?; Ok(()) } } @@ -42,13 +42,13 @@ pub trait Crud: Executor { table_params: &mut TableParams<'entity, T>, ) -> impl Future> where - T: Table<'entity>, + T: Table<'entity, Error = ::Error>, T::Associations: SqlWriter, { async move { table_params.update_all_table_fields(table); table_params.write_delete(&mut <_>::default(), buffer_cmd)?; - let _ = self.execute_with_stmt(buffer_cmd.as_str(), ()).await.map_err(Into::into)?; + let _ = self.execute_with_stmt(buffer_cmd.as_str(), ()).await?; Ok(()) } } @@ -61,15 +61,10 @@ pub trait Crud: Executor { tp: &TableParams<'entity, T>, ) -> impl Future>::Error>> where - T: - FromRecords>::Error> + Table<'entity>, + T: FromRecords + Table<'entity, Error = ::Error>, T::Associations: SqlWriter>::Error>, - str: for<'rec> ValueIdent<<::Database as Database>::Record<'rec>>, - u64: for<'value> Decode< - ::Database, - crate::Error, - Value<'value> = <::Database as Database>::Value<'value>, - >, + str: for<'rec> ValueIdent<::Record<'rec>>, + u64: for<'value> Decode<'value, Self::Database>, { async move { tp.write_select(buffer_cmd, SelectOrderBy::Ascending, SelectLimit::All, &mut |_| Ok(()))?; @@ -91,15 +86,10 @@ pub trait Crud: Executor { where_str: &str, ) -> impl Future>::Error>> where - T: - FromRecords>::Error> + Table<'entity>, + T: FromRecords + Table<'entity, Error = ::Error>, T::Associations: SqlWriter>::Error>, - str: for<'rec> ValueIdent<<::Database as Database>::Record<'rec>>, - u64: for<'value> Decode< - ::Database, - crate::Error, - Value<'value> = <::Database as Database>::Value<'value>, - >, + str: for<'rec> ValueIdent<::Record<'rec>>, + u64: for<'value> Decode<'value, Self::Database>, { async move { tp.write_select(buffer_cmd, order_by, select_limit, &mut |b| { @@ -117,12 +107,11 @@ pub trait Crud: Executor { fn read_by_id<'entity, T>( &mut self, buffer_cmd: &mut String, - id: &T::PrimaryKeyValue, + id: T::PrimaryKeyValue, tp: &TableParams<'entity, T>, ) -> impl Future>::Error>> where - T: - FromRecords>::Error> + Table<'entity>, + T: FromRecords + Table<'entity, Error = ::Error>, T::Associations: SqlWriter>::Error>, { async move { @@ -151,13 +140,13 @@ pub trait Crud: Executor { table_params: &mut TableParams<'entity, T>, ) -> impl Future> where - T: Table<'entity>, + T: Table<'entity, Error = ::Error>, T::Associations: SqlWriter, { async move { table_params.update_all_table_fields(table); table_params.write_update(&mut <_>::default(), buffer_cmd)?; - let _ = self.execute_with_stmt(buffer_cmd.as_str(), ()).await.map_err(Into::into)?; + let _ = self.execute_with_stmt(buffer_cmd.as_str(), ()).await?; Ok(()) } } @@ -169,20 +158,17 @@ impl Crud for T where T: Executor {} /// /// One entity can constructed by more than one row. #[inline] -fn collect_entities_tables<'entity, T>( +fn collect_entities_tables<'entity, D, T>( buffer_cmd: &mut String, - records: &::Records<'_>, + records: &D::Records<'_>, results: &mut Vec, tp: &TableParams<'entity, T>, ) -> Result<(), >::Error> where - T: FromRecords>::Error> + Table<'entity>, - str: for<'rec> ValueIdent<<::Database as Database>::Record<'rec>>, - u64: for<'value> Decode< - ::Database, - crate::Error, - Value<'value> = <::Database as Database>::Value<'value>, - >, + D: Database, + T: FromRecords + Table<'entity, Error = D::Error>, + str: for<'rec> ValueIdent>, + u64: for<'value> Decode<'value, D>, { let mut curr_record_idx: usize = 0; loop { diff --git a/wtx/src/database/orm/misc.rs b/wtx/src/database/orm/misc.rs index 15b70481..f241b348 100644 --- a/wtx/src/database/orm/misc.rs +++ b/wtx/src/database/orm/misc.rs @@ -11,23 +11,19 @@ pub(crate) use fx_hasher::FxHasher; /// Seeks all rows that equals `T`'s primary key and suffix. Can be `T` itself or any other /// associated/related entity. #[inline] -pub fn seek_related_entities<'entity, E, T>( +pub fn seek_related_entities<'entity, D, T>( buffer_cmd: &mut String, curr_record_idx: usize, - records: &::Records<'_>, + records: &D::Records<'_>, ts: TableSuffix, ts_related: TableSuffix, - mut cb: impl FnMut(T) -> Result<(), E>, -) -> Result + mut cb: impl FnMut(T) -> Result<(), D::Error>, +) -> Result where - E: From, - T: FromRecords + Table<'entity, Error = E>, - str: for<'rec> ValueIdent<<::Database as Database>::Record<'rec>>, - u64: for<'value> Decode< - ::Database, - crate::Error, - Value<'value> = <::Database as Database>::Value<'value>, - >, + D: Database, + T: FromRecords + Table<'entity, Error = D::Error>, + str: for<'rec> ValueIdent>, + u64: for<'value> Decode<'value, D>, { let first_record = if let Some(elem) = records.get(curr_record_idx) { elem @@ -38,7 +34,7 @@ where let first_rslt = T::from_records(buffer_cmd, &first_record, records, ts_related); let (mut counter, mut previous) = if let Ok((skip, entity)) = first_rslt { write_column_alias(buffer_cmd, T::TABLE_NAME, ts, T::PRIMARY_KEY_NAME)?; - let previous = first_record.decode(buffer_cmd.as_str()).map_err(Into::into)?; + let previous = first_record.decode(buffer_cmd.as_str())?; buffer_cmd.clear(); cb(entity)?; (skip, previous) @@ -61,7 +57,7 @@ where let (skip, entity) = T::from_records(buffer_cmd, &record, records, ts_related)?; write_column_alias(buffer_cmd, T::TABLE_NAME, ts, T::PRIMARY_KEY_NAME)?; - let curr = record.decode::<_, u64>(buffer_cmd.as_str()).map_err(Into::into)?; + let curr = record.decode::<_, u64>(buffer_cmd.as_str())?; buffer_cmd.clear(); if previous == curr { cb(entity)?; diff --git a/wtx/src/database/record.rs b/wtx/src/database/record.rs index c7023708..4ec4fb81 100644 --- a/wtx/src/database/record.rs +++ b/wtx/src/database/record.rs @@ -7,31 +7,26 @@ pub trait Record { /// Tries to retrieve and decode a value. #[inline] - fn decode<'this, CI, D>(&'this self, ci: CI) -> crate::Result + fn decode<'this, CI, D>(&'this self, ci: CI) -> Result::Error> where CI: ValueIdent<::Record<'this>>, - D: Decode< - Self::Database, - crate::Error, - Value<'this> = ::Value<'this>, - >, + D: Decode<'this, Self::Database>, { - D::decode(self.value(ci).ok_or(crate::Error::AbsentFieldDataInDecoding)?) + D::decode(&self.value(ci).ok_or(crate::Error::AbsentFieldDataInDecoding)?) } /// Tries to retrieve and decode an optional value. #[inline] - fn decode_opt<'this, CI, D>(&'this self, ci: CI) -> crate::Result> + fn decode_opt<'this, CI, D>( + &'this self, + ci: CI, + ) -> Result, ::Error> where CI: ValueIdent<::Record<'this>>, - D: Decode< - Self::Database, - crate::Error, - Value<'this> = ::Value<'this>, - >, + D: Decode<'this, Self::Database>, { match self.value(ci) { - Some(elem) => Ok(Some(D::decode(elem)?)), + Some(elem) => Ok(Some(D::decode(&elem)?)), None => Ok(None), } } diff --git a/wtx/src/database/record_values.rs b/wtx/src/database/record_values.rs index 4662fb7e..40257dbe 100644 --- a/wtx/src/database/record_values.rs +++ b/wtx/src/database/record_values.rs @@ -1,9 +1,12 @@ -use crate::{database::Encode, misc::FilledBufferWriter}; +use crate::{ + database::{Database, Encode}, + misc::FilledBufferWriter, +}; /// Values that can passed to a record as parameters. For example, in a query. -pub trait RecordValues +pub trait RecordValues where - E: From, + D: Database, { /// Converts the inner values into a byte representation. fn encode_values( @@ -12,13 +15,16 @@ where fbw: &mut FilledBufferWriter<'_>, prefix_cb: impl FnMut(&mut A, &mut FilledBufferWriter<'_>) -> usize, suffix_cb: impl FnMut(&mut A, &mut FilledBufferWriter<'_>, bool) -> usize, - ) -> Result; + ) -> Result; /// The number of values fn len(&self) -> usize; } -impl RecordValues for () { +impl RecordValues for () +where + D: Database, +{ #[inline] fn encode_values( self, @@ -26,7 +32,7 @@ impl RecordValues for () { _: &mut FilledBufferWriter<'_>, _: impl FnMut(&mut A, &mut FilledBufferWriter<'_>) -> usize, _: impl FnMut(&mut A, &mut FilledBufferWriter<'_>, bool) -> usize, - ) -> Result { + ) -> Result { Ok(0) } @@ -36,10 +42,10 @@ impl RecordValues for () { } } -impl RecordValues for &T +impl RecordValues for &T where - E: From, - T: Encode, + D: Database, + T: Encode, { #[inline] fn encode_values( @@ -48,7 +54,7 @@ where fbw: &mut FilledBufferWriter<'_>, mut prefix_cb: impl FnMut(&mut A, &mut FilledBufferWriter<'_>) -> usize, mut suffix_cb: impl FnMut(&mut A, &mut FilledBufferWriter<'_>, bool) -> usize, - ) -> Result { + ) -> Result { let mut n: usize = 0; encode(aux, self, fbw, &mut n, &mut prefix_cb, &mut suffix_cb)?; Ok(n) @@ -60,10 +66,10 @@ where } } -impl RecordValues for &[T] +impl RecordValues for &[T] where - E: From, - T: Encode, + D: Database, + T: Encode, { #[inline] fn encode_values( @@ -72,7 +78,7 @@ where fbw: &mut FilledBufferWriter<'_>, mut prefix_cb: impl FnMut(&mut A, &mut FilledBufferWriter<'_>) -> usize, mut suffix_cb: impl FnMut(&mut A, &mut FilledBufferWriter<'_>, bool) -> usize, - ) -> Result { + ) -> Result { let mut n: usize = 0; for elem in self { encode(aux, elem, fbw, &mut n, &mut prefix_cb, &mut suffix_cb)?; @@ -86,11 +92,11 @@ where } } -impl RecordValues for &mut I +impl RecordValues for &mut I where - E: From, + D: Database, I: ExactSizeIterator, - T: Encode, + T: Encode, { #[inline] fn encode_values( @@ -99,7 +105,7 @@ where fbw: &mut FilledBufferWriter<'_>, mut prefix_cb: impl FnMut(&mut A, &mut FilledBufferWriter<'_>) -> usize, mut suffix_cb: impl FnMut(&mut A, &mut FilledBufferWriter<'_>, bool) -> usize, - ) -> Result { + ) -> Result { let mut n: usize = 0; for elem in self { encode(aux, &elem, fbw, &mut n, &mut prefix_cb, &mut suffix_cb)?; @@ -120,10 +126,10 @@ macro_rules! tuple_impls { } )+) => { $( - impl RecordValues for ($( $T, )+) + impl RecordValues for ($( $T, )+) where - ERR: From, - $($T: Encode,)+ + DB: Database, + $($T: Encode,)+ { #[inline] fn encode_values( @@ -132,7 +138,7 @@ macro_rules! tuple_impls { fbw: &mut FilledBufferWriter<'_>, mut prefix_cb: impl FnMut(&mut AUX, &mut FilledBufferWriter<'_>) -> usize, mut suffix_cb: impl FnMut(&mut AUX, &mut FilledBufferWriter<'_>, bool) -> usize, - ) -> Result { + ) -> Result { let mut n: usize = 0; $( encode(aux, &self.$idx, fbw, &mut n, &mut prefix_cb, &mut suffix_cb)?; )+ Ok(n) @@ -318,17 +324,17 @@ tuple_impls! { } } -fn encode( +fn encode( aux: &mut A, elem: &T, fbw: &mut FilledBufferWriter<'_>, n: &mut usize, prefix_cb: &mut impl FnMut(&mut A, &mut FilledBufferWriter<'_>) -> usize, suffix_cb: &mut impl FnMut(&mut A, &mut FilledBufferWriter<'_>, bool) -> usize, -) -> Result<(), E> +) -> Result<(), D::Error> where - E: From, - T: Encode, + D: Database, + T: Encode, { *n = n.wrapping_add(prefix_cb(aux, fbw)); let before = fbw._len(); diff --git a/wtx/src/database/sm.rs b/wtx/src/database/schema_manager.rs similarity index 94% rename from wtx/src/database/sm.rs rename to wtx/src/database/schema_manager.rs index 5c019e3e..f77e447a 100644 --- a/wtx/src/database/sm.rs +++ b/wtx/src/database/schema_manager.rs @@ -1,4 +1,4 @@ -//! Schema Management +//! Schema Manager #[macro_use] mod macros; @@ -16,7 +16,7 @@ pub mod toml_parser; pub use commands::*; pub use repeatability::Repeatability; -#[cfg(all(feature = "_integration-tests", feature = "sm-dev", test))] +#[cfg(all(feature = "_integration-tests", feature = "schema-manager-dev", test))] mod integration_tests; use crate::database::{executor::Executor, DatabaseTy, Identifier}; use alloc::{string::String, vec::Vec}; @@ -161,7 +161,7 @@ mod postgres { use crate::{ database::{ client::postgres::{Executor, ExecutorBuffer}, - sm::{ + schema_manager::{ fixed_sql_commands::{ _delete_migrations, _insert_migrations, _migrations_by_mg_version_query, postgres::{_clear, _table_names, _CREATE_MIGRATION_TABLES}, @@ -174,7 +174,7 @@ mod postgres { }; use core::borrow::BorrowMut; - impl SchemaManagement for Executor + impl SchemaManagement for Executor where EB: BorrowMut, STREAM: Stream, @@ -228,14 +228,8 @@ mod postgres { where S: AsRef, { - _migrations_by_mg_version_query::( - buffer_cmd, - self, - mg.version(), - results, - _WTX_SCHEMA_PREFIX, - ) - .await + _migrations_by_mg_version_query(buffer_cmd, self, mg.version(), results, _WTX_SCHEMA_PREFIX) + .await } #[inline] diff --git a/wtx/src/database/sm/commands.rs b/wtx/src/database/schema_manager/commands.rs similarity index 90% rename from wtx/src/database/sm/commands.rs rename to wtx/src/database/schema_manager/commands.rs index 27dced6a..2ff1c8de 100644 --- a/wtx/src/database/sm/commands.rs +++ b/wtx/src/database/schema_manager/commands.rs @@ -1,14 +1,14 @@ -#[cfg(feature = "sm-dev")] +#[cfg(feature = "schema-manager-dev")] mod clear; mod migrate; mod rollback; -#[cfg(feature = "sm-dev")] +#[cfg(feature = "schema-manager-dev")] mod seed; mod validate; use crate::database::{ executor::Executor, - sm::{UserMigration, DEFAULT_BATCH_SIZE}, + schema_manager::{UserMigration, DEFAULT_BATCH_SIZE}, Database, DatabaseTy, }; diff --git a/wtx/src/database/sm/commands/clear.rs b/wtx/src/database/schema_manager/commands/clear.rs similarity index 88% rename from wtx/src/database/sm/commands/clear.rs rename to wtx/src/database/schema_manager/commands/clear.rs index e6b68d51..f88f833f 100644 --- a/wtx/src/database/sm/commands/clear.rs +++ b/wtx/src/database/schema_manager/commands/clear.rs @@ -1,5 +1,5 @@ use crate::database::{ - sm::{Commands, SchemaManagement}, + schema_manager::{Commands, SchemaManagement}, Identifier, }; use alloc::{string::String, vec::Vec}; diff --git a/wtx/src/database/sm/commands/migrate.rs b/wtx/src/database/schema_manager/commands/migrate.rs similarity index 94% rename from wtx/src/database/sm/commands/migrate.rs rename to wtx/src/database/schema_manager/commands/migrate.rs index 2056b6a1..e5727959 100644 --- a/wtx/src/database/sm/commands/migrate.rs +++ b/wtx/src/database/schema_manager/commands/migrate.rs @@ -1,11 +1,11 @@ use crate::database::{ - sm::{Commands, DbMigration, MigrationGroup, SchemaManagement, UserMigration}, + schema_manager::{Commands, DbMigration, MigrationGroup, SchemaManagement, UserMigration}, DatabaseTy, }; use alloc::{string::String, vec::Vec}; #[cfg(feature = "std")] use { - crate::database::sm::misc::{group_and_migrations_from_path, parse_root_toml}, + crate::database::schema_manager::misc::{group_and_migrations_from_path, parse_root_toml}, std::path::{Path, PathBuf}, }; @@ -90,7 +90,7 @@ where migration_groups: &[PathBuf], ) -> crate::Result<()> { self.executor.create_wtx_tables().await?; - crate::database::sm::misc::is_sorted_and_unique(migration_groups)?; + crate::database::schema_manager::misc::is_sorted_and_unique(migration_groups)?; for mg in migration_groups { self.do_migrate_from_dir((buffer_cmd, buffer_db_migrations), mg).await?; } diff --git a/wtx/src/database/sm/commands/rollback.rs b/wtx/src/database/schema_manager/commands/rollback.rs similarity index 94% rename from wtx/src/database/sm/commands/rollback.rs rename to wtx/src/database/schema_manager/commands/rollback.rs index 83a44727..51e41a5a 100644 --- a/wtx/src/database/sm/commands/rollback.rs +++ b/wtx/src/database/schema_manager/commands/rollback.rs @@ -1,11 +1,11 @@ use crate::database::{ - sm::{Commands, DbMigration, MigrationGroup, SchemaManagement, UserMigration}, + schema_manager::{Commands, DbMigration, MigrationGroup, SchemaManagement, UserMigration}, DatabaseTy, TransactionManager, }; use alloc::{string::String, vec::Vec}; #[cfg(feature = "std")] use { - crate::database::sm::misc::{group_and_migrations_from_path, parse_root_toml}, + crate::database::schema_manager::misc::{group_and_migrations_from_path, parse_root_toml}, std::path::Path, }; diff --git a/wtx/src/database/sm/commands/seed.rs b/wtx/src/database/schema_manager/commands/seed.rs similarity index 85% rename from wtx/src/database/sm/commands/seed.rs rename to wtx/src/database/schema_manager/commands/seed.rs index dd3993dd..51536eda 100644 --- a/wtx/src/database/sm/commands/seed.rs +++ b/wtx/src/database/schema_manager/commands/seed.rs @@ -1,4 +1,4 @@ -use crate::database::{executor::Executor, sm::Commands, TransactionManager}; +use crate::database::{executor::Executor, schema_manager::Commands, TransactionManager}; use alloc::string::String; #[cfg(feature = "std")] use std::{fs::read_to_string, path::Path}; @@ -30,7 +30,7 @@ where #[cfg(feature = "std")] #[inline] pub async fn seed_from_dir(&mut self, buffer_cmd: &mut String, dir: &Path) -> crate::Result<()> { - let iter = crate::database::sm::misc::files(dir)?.filter_map(|el_rslt| { + let iter = crate::database::schema_manager::misc::files(dir)?.filter_map(|el_rslt| { let el = el_rslt.ok()?; read_to_string(el.path()).ok() }); diff --git a/wtx/src/database/sm/commands/validate.rs b/wtx/src/database/schema_manager/commands/validate.rs similarity index 96% rename from wtx/src/database/sm/commands/validate.rs rename to wtx/src/database/schema_manager/commands/validate.rs index 379bb8a0..a7bfc678 100644 --- a/wtx/src/database/sm/commands/validate.rs +++ b/wtx/src/database/schema_manager/commands/validate.rs @@ -1,5 +1,5 @@ use crate::database::{ - sm::{ + schema_manager::{ misc::is_migration_divergent, Commands, DbMigration, MigrationGroup, Repeatability, SchemaManagement, UserMigration, }, @@ -8,7 +8,7 @@ use crate::database::{ use alloc::{string::String, vec::Vec}; #[cfg(feature = "std")] use { - crate::database::sm::misc::{group_and_migrations_from_path, parse_root_toml}, + crate::database::schema_manager::misc::{group_and_migrations_from_path, parse_root_toml}, std::path::Path, }; diff --git a/wtx/src/database/sm/doc_tests.rs b/wtx/src/database/schema_manager/doc_tests.rs similarity index 78% rename from wtx/src/database/sm/doc_tests.rs rename to wtx/src/database/schema_manager/doc_tests.rs index 908e1795..8703871d 100644 --- a/wtx/src/database/sm/doc_tests.rs +++ b/wtx/src/database/schema_manager/doc_tests.rs @@ -7,10 +7,10 @@ //! Instances mostly used for documentation tests -use crate::database::sm::{MigrationGroup, UserMigrationRef}; +use crate::database::schema_manager::{MigrationGroup, UserMigrationRef}; /// ```rust -/// let _ = wtx::database::sm::UserMigrationRef::from_user_parts( +/// let _ = wtx::database::schema_manager::UserMigrationRef::from_user_parts( /// &[], /// "create_author", /// None, @@ -37,7 +37,7 @@ pub fn migration() -> UserMigrationRef<'static, 'static> { } /// ```rust -/// let _ = wtx::database::sm::MigrationGroup::new("initial", 1); +/// let _ = wtx::database::schema_manager::MigrationGroup::new("initial", 1); /// ``` #[inline] pub fn migration_group() -> MigrationGroup<&'static str> { diff --git a/wtx/src/database/sm/fixed_sql_commands.rs b/wtx/src/database/schema_manager/fixed_sql_commands.rs similarity index 94% rename from wtx/src/database/sm/fixed_sql_commands.rs rename to wtx/src/database/schema_manager/fixed_sql_commands.rs index 3e10e394..45bb66f9 100644 --- a/wtx/src/database/sm/fixed_sql_commands.rs +++ b/wtx/src/database/schema_manager/fixed_sql_commands.rs @@ -30,7 +30,7 @@ pub(crate) mod postgres; use crate::database::{ executor::Executor, - sm::{DbMigration, MigrationGroup, UserMigration}, + schema_manager::{DbMigration, MigrationGroup, UserMigration}, Database, DatabaseTy, FromRecord, TransactionManager, }; use alloc::{string::String, vec::Vec}; @@ -114,7 +114,7 @@ where } #[inline] -pub(crate) async fn _migrations_by_mg_version_query( +pub(crate) async fn _migrations_by_mg_version_query( buffer_cmd: &mut String, executor: &mut E, mg_version: i32, @@ -122,8 +122,9 @@ pub(crate) async fn _migrations_by_mg_version_query( schema_prefix: &str, ) -> crate::Result<()> where - E: Executor, - DbMigration: for<'rec> FromRecord::Record<'rec>>, + D: Database, + E: Executor, + DbMigration: for<'rec> FromRecord, { buffer_cmd.write_fmt(format_args!( "SELECT \ diff --git a/wtx/src/database/sm/fixed_sql_commands/postgres.rs b/wtx/src/database/schema_manager/fixed_sql_commands/postgres.rs similarity index 93% rename from wtx/src/database/sm/fixed_sql_commands/postgres.rs rename to wtx/src/database/schema_manager/fixed_sql_commands/postgres.rs index 7e5c21c0..c9af8bb1 100644 --- a/wtx/src/database/sm/fixed_sql_commands/postgres.rs +++ b/wtx/src/database/schema_manager/fixed_sql_commands/postgres.rs @@ -22,7 +22,7 @@ pub(crate) async fn _clear( executor: &mut E, ) -> crate::Result<()> where - E: Executor, + E: Executor>, { _schemas(executor, buffer_idents).await?; _push_drop((buffer_cmd, buffer_idents), "SCHEMA")?; @@ -62,7 +62,7 @@ pub(crate) async fn _domains( results: &mut Vec, ) -> crate::Result<()> where - E: Executor, + E: Executor>, { executor .simple_entities( @@ -86,7 +86,7 @@ where #[inline] pub(crate) async fn _enums(executor: &mut E, results: &mut Vec) -> crate::Result<()> where - E: Executor, + E: Executor>, { executor .simple_entities( @@ -111,7 +111,7 @@ pub(crate) async fn _pg_proc( prokind: char, ) -> crate::Result<()> where - E: Executor, + E: Executor>, { let before = buffer_cmd.len(); buffer_cmd.write_fmt(format_args!( @@ -140,7 +140,7 @@ pub(crate) async fn _sequences( results: &mut Vec, ) -> crate::Result<()> where - E: Executor, + E: Executor>, { executor .simple_entities( @@ -163,7 +163,7 @@ pub(crate) async fn _schemas( identifiers: &mut Vec, ) -> crate::Result<()> where - E: Executor, + E: Executor>, { executor .simple_entities( @@ -189,7 +189,7 @@ pub(crate) async fn _table_names( schema: &str, ) -> crate::Result<()> where - E: Executor, + E: Executor>, { let before = buffer_cmd.len(); buffer_cmd.write_fmt(format_args!( @@ -220,7 +220,7 @@ where #[inline] pub(crate) async fn _types(executor: &mut E, results: &mut Vec) -> crate::Result<()> where - E: Executor, + E: Executor>, { executor .simple_entities( @@ -250,7 +250,7 @@ where #[inline] pub(crate) async fn _views(executor: &mut E, results: &mut Vec) -> crate::Result<()> where - E: Executor, + E: Executor>, { executor .simple_entities( diff --git a/wtx/src/database/sm/integration_tests.rs b/wtx/src/database/schema_manager/integration_tests.rs similarity index 97% rename from wtx/src/database/sm/integration_tests.rs rename to wtx/src/database/schema_manager/integration_tests.rs index 9933fd2e..5eb1d371 100644 --- a/wtx/src/database/sm/integration_tests.rs +++ b/wtx/src/database/schema_manager/integration_tests.rs @@ -7,7 +7,7 @@ mod schema; use crate::{ database::{ - sm::{ + schema_manager::{ doc_tests::{migration, migration_group}, Commands, DbMigration, MigrationGroup, SchemaManagement, }, @@ -22,7 +22,7 @@ macro_rules! create_integration_test { ($executor:expr, $buffer:expr, $aux:expr, $($fun:path),*) => {{ $({ let (_buffer_cmd, _, _buffer_idents) = $buffer; - let mut commands = crate::database::sm::Commands::with_executor($executor); + let mut commands = crate::database::schema_manager::Commands::with_executor($executor); commands.clear((_buffer_cmd, _buffer_idents)).await.unwrap(); $fun($buffer, &mut commands, $aux).await; })* diff --git a/wtx/src/database/sm/integration_tests/backend.rs b/wtx/src/database/schema_manager/integration_tests/backend.rs similarity index 97% rename from wtx/src/database/sm/integration_tests/backend.rs rename to wtx/src/database/schema_manager/integration_tests/backend.rs index 71c34d21..d7fa1069 100644 --- a/wtx/src/database/sm/integration_tests/backend.rs +++ b/wtx/src/database/schema_manager/integration_tests/backend.rs @@ -1,5 +1,5 @@ use crate::database::{ - sm::{ + schema_manager::{ integration_tests::{AuxTestParams, _migrate_doc_test}, Commands, DbMigration, SchemaManagement, }, diff --git a/wtx/src/database/sm/integration_tests/db.rs b/wtx/src/database/schema_manager/integration_tests/db.rs similarity index 100% rename from wtx/src/database/sm/integration_tests/db.rs rename to wtx/src/database/schema_manager/integration_tests/db.rs diff --git a/wtx/src/database/sm/integration_tests/db/postgres.rs b/wtx/src/database/schema_manager/integration_tests/db/postgres.rs similarity index 90% rename from wtx/src/database/sm/integration_tests/db/postgres.rs rename to wtx/src/database/schema_manager/integration_tests/db/postgres.rs index 705293c3..800eae3c 100644 --- a/wtx/src/database/sm/integration_tests/db/postgres.rs +++ b/wtx/src/database/schema_manager/integration_tests/db/postgres.rs @@ -1,18 +1,18 @@ -#[cfg(feature = "sm-dev")] +#[cfg(feature = "schema-manager-dev")] use crate::database::{ - client::postgres::Postgres, client::postgres::Record, sm::fixed_sql_commands::postgres, - sm::integration_tests, sm::Commands, sm::DbMigration, sm::SchemaManagement, FromRecord, - Identifier, + client::postgres::Postgres, schema_manager::fixed_sql_commands::postgres, + schema_manager::integration_tests, schema_manager::Commands, schema_manager::DbMigration, + schema_manager::SchemaManagement, FromRecord, Identifier, }; -#[cfg(feature = "sm-dev")] +#[cfg(feature = "schema-manager-dev")] pub(crate) async fn _clean_drops_all_objs( (buffer_cmd, _, buffer_idents): (&mut String, &mut Vec, &mut Vec), c: &mut Commands, _: integration_tests::AuxTestParams, ) where - E: SchemaManagement, - for<'rec> Identifier: FromRecord>, + E: SchemaManagement>, + Identifier: FromRecord>, { integration_tests::create_foo_table(buffer_cmd, c, "public.").await; let _ = c.executor.execute("CREATE SCHEMA bar", |_| {}).await.unwrap(); diff --git a/wtx/src/database/sm/integration_tests/generic.rs b/wtx/src/database/schema_manager/integration_tests/generic.rs similarity index 93% rename from wtx/src/database/sm/integration_tests/generic.rs rename to wtx/src/database/schema_manager/integration_tests/generic.rs index fcbb3e98..c68a2337 100644 --- a/wtx/src/database/sm/integration_tests/generic.rs +++ b/wtx/src/database/schema_manager/integration_tests/generic.rs @@ -1,5 +1,7 @@ use crate::database::{ - sm::{integration_tests::AuxTestParams, Commands, DbMigration, MigrationGroup, SchemaManagement}, + schema_manager::{ + integration_tests::AuxTestParams, Commands, DbMigration, MigrationGroup, SchemaManagement, + }, Identifier, }; use std::path::Path; diff --git a/wtx/src/database/sm/integration_tests/schema.rs b/wtx/src/database/schema_manager/integration_tests/schema.rs similarity index 98% rename from wtx/src/database/sm/integration_tests/schema.rs rename to wtx/src/database/schema_manager/integration_tests/schema.rs index c5888d8a..0d7d6a08 100644 --- a/wtx/src/database/sm/integration_tests/schema.rs +++ b/wtx/src/database/schema_manager/integration_tests/schema.rs @@ -1,7 +1,7 @@ pub(crate) mod with_schema; pub(crate) mod without_schema; -use crate::database::sm::{ +use crate::database::schema_manager::{ integration_tests::AuxTestParams, Commands, MigrationGroup, SchemaManagement, }; use std::path::Path; diff --git a/wtx/src/database/sm/integration_tests/schema/with_schema.rs b/wtx/src/database/schema_manager/integration_tests/schema/with_schema.rs similarity index 88% rename from wtx/src/database/sm/integration_tests/schema/with_schema.rs rename to wtx/src/database/schema_manager/integration_tests/schema/with_schema.rs index fc492a2a..6a914319 100644 --- a/wtx/src/database/sm/integration_tests/schema/with_schema.rs +++ b/wtx/src/database/schema_manager/integration_tests/schema/with_schema.rs @@ -1,5 +1,5 @@ use crate::database::{ - sm::{ + schema_manager::{ integration_tests::{AuxTestParams, _migrate_doc_test}, Commands, DbMigration, SchemaManagement, }, @@ -33,5 +33,6 @@ pub(crate) async fn migrate_works( ) where E: SchemaManagement, { - crate::database::sm::integration_tests::schema::migrate_works(buffer_cmd, c, aux, 2).await + crate::database::schema_manager::integration_tests::schema::migrate_works(buffer_cmd, c, aux, 2) + .await } diff --git a/wtx/src/database/sm/integration_tests/schema/without_schema.rs b/wtx/src/database/schema_manager/integration_tests/schema/without_schema.rs similarity index 54% rename from wtx/src/database/sm/integration_tests/schema/without_schema.rs rename to wtx/src/database/schema_manager/integration_tests/schema/without_schema.rs index ba9d1872..9c2b9a60 100644 --- a/wtx/src/database/sm/integration_tests/schema/without_schema.rs +++ b/wtx/src/database/schema_manager/integration_tests/schema/without_schema.rs @@ -1,5 +1,5 @@ use crate::database::{ - sm::{integration_tests::AuxTestParams, Commands, DbMigration, SchemaManagement}, + schema_manager::{integration_tests::AuxTestParams, Commands, DbMigration, SchemaManagement}, Identifier, }; @@ -10,5 +10,6 @@ pub(crate) async fn _migrate_works( ) where E: SchemaManagement, { - crate::database::sm::integration_tests::schema::migrate_works(buffer_cmd, c, aux, 6).await + crate::database::schema_manager::integration_tests::schema::migrate_works(buffer_cmd, c, aux, 6) + .await } diff --git a/wtx/src/database/sm/macros.rs b/wtx/src/database/schema_manager/macros.rs similarity index 100% rename from wtx/src/database/sm/macros.rs rename to wtx/src/database/schema_manager/macros.rs diff --git a/wtx/src/database/sm/migration.rs b/wtx/src/database/schema_manager/migration.rs similarity index 100% rename from wtx/src/database/sm/migration.rs rename to wtx/src/database/schema_manager/migration.rs diff --git a/wtx/src/database/sm/migration/db_migration.rs b/wtx/src/database/schema_manager/migration/db_migration.rs similarity index 90% rename from wtx/src/database/sm/migration/db_migration.rs rename to wtx/src/database/schema_manager/migration/db_migration.rs index 349d5f00..b5023655 100644 --- a/wtx/src/database/sm/migration/db_migration.rs +++ b/wtx/src/database/schema_manager/migration/db_migration.rs @@ -1,6 +1,6 @@ use crate::{ database::{ - sm::{MigrationCommon, MigrationGroup, Repeatability}, + schema_manager::{MigrationCommon, MigrationGroup, Repeatability}, DatabaseTy, Identifier, }, misc::_atoi, @@ -56,13 +56,12 @@ impl DbMigration { } #[cfg(feature = "postgres")] -impl crate::database::FromRecord> - for DbMigration +impl crate::database::FromRecord> for DbMigration where E: From, { #[inline] - fn from_record(from: crate::database::client::postgres::Record<'_>) -> Result { + fn from_record(from: &crate::database::client::postgres::Record<'_, E>) -> Result { use crate::database::Record as _; Ok(Self { common: MigrationCommon { diff --git a/wtx/src/database/sm/migration/migration_common.rs b/wtx/src/database/schema_manager/migration/migration_common.rs similarity index 81% rename from wtx/src/database/sm/migration/migration_common.rs rename to wtx/src/database/schema_manager/migration/migration_common.rs index 8a5ddb1f..746564ae 100644 --- a/wtx/src/database/sm/migration/migration_common.rs +++ b/wtx/src/database/schema_manager/migration/migration_common.rs @@ -1,4 +1,4 @@ -use crate::database::sm::Repeatability; +use crate::database::schema_manager::Repeatability; #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub(crate) struct MigrationCommon { diff --git a/wtx/src/database/sm/migration/migration_group.rs b/wtx/src/database/schema_manager/migration/migration_group.rs similarity index 85% rename from wtx/src/database/sm/migration/migration_group.rs rename to wtx/src/database/schema_manager/migration/migration_group.rs index 13e78dd5..44ae8878 100644 --- a/wtx/src/database/sm/migration/migration_group.rs +++ b/wtx/src/database/schema_manager/migration/migration_group.rs @@ -24,7 +24,7 @@ where /// # Example /// /// ```rust - /// use wtx::database::sm::doc_tests::migration_group; + /// use wtx::database::schema_manager::doc_tests::migration_group; /// assert_eq!(migration_group().name(), "initial"); /// ``` #[inline] @@ -37,7 +37,7 @@ where /// # Example /// /// ```rust - /// use wtx::database::sm::doc_tests::migration_group; + /// use wtx::database::schema_manager::doc_tests::migration_group; /// assert_eq!(migration_group().version(), 1); /// ``` #[inline] diff --git a/wtx/src/database/sm/migration/user_migration.rs b/wtx/src/database/schema_manager/migration/user_migration.rs similarity index 88% rename from wtx/src/database/sm/migration/user_migration.rs rename to wtx/src/database/schema_manager/migration/user_migration.rs index c39186af..aadbc1d4 100644 --- a/wtx/src/database/sm/migration/user_migration.rs +++ b/wtx/src/database/schema_manager/migration/user_migration.rs @@ -1,5 +1,5 @@ use crate::database::{ - sm::{ + schema_manager::{ migration::MigrationCommon, misc::{calc_checksum, is_sorted_and_unique}, Repeatability, @@ -76,7 +76,7 @@ where /// # Example /// /// ```rust - /// use wtx::database::sm::doc_tests::migration; + /// use wtx::database::schema_manager::doc_tests::migration; /// assert_eq!(migration().checksum(), 9297329847391907999) /// ``` #[inline] @@ -91,7 +91,7 @@ where /// # Example /// /// ```rust - /// use wtx::database::sm::doc_tests::migration; + /// use wtx::database::schema_manager::doc_tests::migration; /// assert_eq!(migration().dbs(), []) /// ``` #[inline] @@ -104,7 +104,7 @@ where /// # Example /// /// ```rust - /// use wtx::database::sm::doc_tests::migration; + /// use wtx::database::schema_manager::doc_tests::migration; /// assert_eq!(migration().name(), "create_author") /// ``` #[inline] @@ -117,7 +117,7 @@ where /// # Example /// /// ```rust - /// use wtx::database::sm::doc_tests::migration; + /// use wtx::database::schema_manager::doc_tests::migration; /// assert_eq!(migration().repeatability(), None) /// ``` #[inline] @@ -130,7 +130,7 @@ where /// # Example /// /// ```rust - /// use wtx::database::sm::doc_tests::migration; + /// use wtx::database::schema_manager::doc_tests::migration; /// assert_eq!(migration().sql_down(), "DROP TABLE author"); /// ``` #[inline] @@ -143,7 +143,7 @@ where /// # Example /// /// ```rust - /// use wtx::database::sm::doc_tests::migration; + /// use wtx::database::schema_manager::doc_tests::migration; /// let mg = assert_eq!( /// migration().sql_up(), /// "CREATE TABLE author (id INT NOT NULL PRIMARY KEY, name VARCHAR(50) NOT NULL)" @@ -158,7 +158,7 @@ where /// # Example /// /// ```rust - /// use wtx::database::sm::doc_tests::migration; + /// use wtx::database::schema_manager::doc_tests::migration; /// assert_eq!(migration().version(), 1) /// ``` #[inline] diff --git a/wtx/src/database/sm/migration_parser.rs b/wtx/src/database/schema_manager/migration_parser.rs similarity index 98% rename from wtx/src/database/sm/migration_parser.rs rename to wtx/src/database/schema_manager/migration_parser.rs index f21e4b80..eefeda0c 100644 --- a/wtx/src/database/sm/migration_parser.rs +++ b/wtx/src/database/schema_manager/migration_parser.rs @@ -1,7 +1,7 @@ //! Migration file parser use crate::database::{ - sm::{ + schema_manager::{ toml_parser::{toml, Expr}, Repeatability, }, @@ -156,7 +156,7 @@ where #[cfg(test)] mod tests { use crate::database::{ - sm::{migration_parser::parse_unified_migration, Repeatability}, + schema_manager::{migration_parser::parse_unified_migration, Repeatability}, DatabaseTy, }; diff --git a/wtx/src/database/sm/misc.rs b/wtx/src/database/schema_manager/misc.rs similarity index 97% rename from wtx/src/database/sm/misc.rs rename to wtx/src/database/schema_manager/misc.rs index 0fe8b15f..e5e1a05c 100644 --- a/wtx/src/database/sm/misc.rs +++ b/wtx/src/database/schema_manager/misc.rs @@ -1,4 +1,4 @@ -//! Schema management utilities +//! Miscellaneous #[cfg(feature = "std")] macro_rules! opt_to_inv_mig { @@ -8,13 +8,13 @@ macro_rules! opt_to_inv_mig { } use crate::database::{ - sm::migration::{DbMigration, UserMigration}, + schema_manager::migration::{DbMigration, UserMigration}, DatabaseTy, }; use core::hash::{Hash, Hasher}; #[cfg(feature = "std")] use { - crate::database::sm::{ + crate::database::schema_manager::{ toml_parser::{toml, Expr, EXPR_ARRAY_MAX_LEN}, MigrationGroup, Repeatability, UserMigrationOwned, }, @@ -56,7 +56,9 @@ pub fn group_and_migrations_from_path( where F: FnMut(&PathBuf, &PathBuf) -> Ordering, { - use crate::database::sm::migration_parser::{parse_migration_toml, parse_unified_migration}; + use crate::database::schema_manager::migration_parser::{ + parse_migration_toml, parse_unified_migration, + }; fn group_and_migrations_from_path( path: &Path, diff --git a/wtx/src/database/sm/repeatability.rs b/wtx/src/database/schema_manager/repeatability.rs similarity index 100% rename from wtx/src/database/sm/repeatability.rs rename to wtx/src/database/schema_manager/repeatability.rs diff --git a/wtx/src/database/sm/toml_parser.rs b/wtx/src/database/schema_manager/toml_parser.rs similarity index 98% rename from wtx/src/database/sm/toml_parser.rs rename to wtx/src/database/schema_manager/toml_parser.rs index 069a2440..e8f8cb22 100644 --- a/wtx/src/database/sm/toml_parser.rs +++ b/wtx/src/database/schema_manager/toml_parser.rs @@ -139,7 +139,7 @@ fn try_parse_and_push_toml_expr_string( #[cfg(test)] mod tests { - use crate::database::sm::toml_parser::{toml, Expr}; + use crate::database::schema_manager::toml_parser::{toml, Expr}; use arrayvec::ArrayVec; #[test] diff --git a/wtx/src/error.rs b/wtx/src/error.rs index 330f0288..3971755b 100644 --- a/wtx/src/error.rs +++ b/wtx/src/error.rs @@ -38,10 +38,6 @@ pub enum Error { DecodeError(base64::DecodeError), #[cfg(feature = "base64")] DecodeSliceError(base64::DecodeSliceError), - #[cfg(feature = "deadpool")] - DeadPoolManagedPoolError(Box>), - #[cfg(feature = "deadpool")] - DeadPoolUnmanagedPoolError(deadpool::unmanaged::PoolError), #[cfg(feature = "embassy-net")] EmbassyNet(embassy_net::tcp::Error), #[cfg(feature = "base64")] @@ -243,6 +239,9 @@ pub enum Error { /// Stream does not support TLS channels. StreamDoesNotSupportTlsChannels, + // ***** Internal - PM ***** + StaticPoolMustHaveCapacityForAtLeastOneElement, + // ***** Internal - WebSocket ***** // /// The requested received in a handshake on a server is not valid. @@ -349,34 +348,6 @@ impl From for Error { } } -#[cfg(feature = "deadpool")] -impl From> for Error { - #[inline] - fn from(from: deadpool::managed::PoolError) -> Self { - use deadpool::managed::{HookError, PoolError}; - let elem = match from { - PoolError::Timeout(elem) => PoolError::Timeout(elem), - PoolError::Backend(_) => PoolError::Backend(()), - PoolError::Closed => PoolError::Closed, - PoolError::NoRuntimeSpecified => PoolError::NoRuntimeSpecified, - PoolError::PostCreateHook(elem) => PoolError::PostCreateHook(match elem { - HookError::Message(elem) => HookError::Message(elem), - HookError::StaticMessage(elem) => HookError::StaticMessage(elem), - HookError::Backend(_) => HookError::Backend(()), - }), - }; - Self::DeadPoolManagedPoolError(elem.into()) - } -} - -#[cfg(feature = "deadpool")] -impl From for Error { - #[inline] - fn from(from: deadpool::unmanaged::PoolError) -> Self { - Self::DeadPoolUnmanagedPoolError(from) - } -} - #[cfg(feature = "embassy-net")] impl From for Error { #[inline] diff --git a/wtx/src/http.rs b/wtx/src/http.rs index 1f25de27..89b3255e 100644 --- a/wtx/src/http.rs +++ b/wtx/src/http.rs @@ -3,6 +3,7 @@ mod expected_header; mod header; mod method; +mod mime; mod request; mod response; mod status_code; @@ -11,6 +12,7 @@ mod version; pub use expected_header::ExpectedHeader; pub use header::Header; pub use method::Method; +pub use mime::Mime; pub use request::Request; pub use response::Response; pub use status_code::StatusCode; diff --git a/wtx/src/http/method.rs b/wtx/src/http/method.rs index d44dc359..0f190b12 100644 --- a/wtx/src/http/method.rs +++ b/wtx/src/http/method.rs @@ -1,42 +1,51 @@ -/// HTTP method -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Method { - /// Connect - Connect, - /// Delete - Delete, - /// Get - Get, - /// Head - Head, - /// Options - Options, - /// Patch - Patch, - /// Post - Post, - /// Put - Put, - /// Trace - Trace, +create_enum! { + /// HTTP method + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + pub enum Method { + /// Connect + Connect = (0, "connect"), + /// Delete + Delete = (1, "delete"), + /// Get + Get = (2, "get"), + /// Head + Head = (3, "head"), + /// Options + Options = (4, "options"), + /// Patch + Patch = (5, "patch"), + /// Post + Post = (6, "post"), + /// Put + Put = (7, "put"), + /// Trace + Trace = (8, "trace"), + } } -impl TryFrom<&[u8]> for Method { - type Error = crate::Error; +#[cfg(feature = "serde")] +mod serde { + use crate::http::Method; + use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + + impl<'de> Deserialize<'de> for Method { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = <&str>::deserialize(deserializer)?; + Self::try_from(s).map_err(|err| de::Error::custom(err)) + } + } - #[inline] - fn try_from(from: &[u8]) -> Result { - Ok(match from { - b"CONNECT" => Self::Connect, - b"DELETE" => Self::Delete, - b"GET" => Self::Get, - b"HEAD" => Self::Head, - b"OPTIONS" => Self::Options, - b"PATCH" => Self::Patch, - b"POST" => Self::Post, - b"PUT" => Self::Put, - b"TRACE" => Self::Trace, - _ => return Err(crate::Error::UnexpectedHttpMethod), - }) + impl Serialize for Method { + #[inline] + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.strings().custom) + } } } diff --git a/wtx/src/http/mime.rs b/wtx/src/http/mime.rs new file mode 100644 index 00000000..12760759 --- /dev/null +++ b/wtx/src/http/mime.rs @@ -0,0 +1,35 @@ +/// Used to specify the data type that is going to be sent to a counterpart. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Mime { + /// Opaque bytes + Bytes, + /// Anything + Custom(&'static str), + /// JSON + Json, + /// JSON:API + JsonApi, + /// Protocol buffer + Protobuf, + /// Plain text + Text, + /// XML + Xml, + /// YAML + Yaml, +} + +impl Mime { + pub(crate) fn _as_str(&self) -> &'static str { + match self { + Self::Bytes => "application/octet-stream", + Self::Custom(el) => el, + Self::Json => "application/json", + Self::JsonApi => "application/vnd.api+json", + Self::Protobuf => "application/vnd.google.protobuf", + Self::Text => "text/plain", + Self::Xml => "application/xml", + Self::Yaml => "application/yaml", + } + } +} diff --git a/wtx/src/lib.rs b/wtx/src/lib.rs index 983c8f00..4422fd4e 100644 --- a/wtx/src/lib.rs +++ b/wtx/src/lib.rs @@ -21,6 +21,8 @@ pub mod http; #[cfg(feature = "http1")] mod http1; pub mod misc; +#[cfg(feature = "pool-manager")] +pub mod pool_manager; pub mod rng; #[cfg(feature = "web-socket")] pub mod web_socket; diff --git a/wtx/src/macros.rs b/wtx/src/macros.rs index 59e865ad..7afc93c7 100644 --- a/wtx/src/macros.rs +++ b/wtx/src/macros.rs @@ -1,13 +1,23 @@ macro_rules! create_enum { ( - $(#[$mac:meta])* + $(#[$container_mac:meta])* $v:vis enum $enum_ident:ident<$n:ty> { - $($(#[$doc:meta])* $variant_ident:ident = ($variant_n:literal $(, $variant_str:literal)?)),* $(,)? + $( + $(#[$variant_mac_fixed:meta])* + $variant_ident_fixed:ident = ($variant_n_fixed:literal $(, $variant_str_fixed:literal)?) + ),* $(,)? + + $( + @ + $(#[$variant_mac_range:meta])* + $variant_ident_range:ident($variant_ident_value:pat) = ($variant_n_range:literal $(, $variant_str_range:literal)?) + ),* } ) => { - $(#[$mac])* + $(#[$container_mac])* $v enum $enum_ident { - $($(#[$doc])* $variant_ident,)* + $($(#[$variant_mac_fixed])* $variant_ident_fixed,)* + $($(#[$variant_mac_range])* $variant_ident_range($n),)* } impl $enum_ident { @@ -16,7 +26,11 @@ macro_rules! create_enum { pub const fn len() -> usize { let mut len: usize = 0; $({ - let _ = $variant_n; + let _ = $variant_n_fixed; + len = len.wrapping_add(1); + })* + $({ + let _ = $variant_n_range; len = len.wrapping_add(1); })* len @@ -27,15 +41,27 @@ macro_rules! create_enum { pub const fn strings(&self) -> crate::misc::EnumVarStrings { match self { $( - $enum_ident::$variant_ident => crate::misc::EnumVarStrings { + $enum_ident::$variant_ident_fixed => crate::misc::EnumVarStrings { + custom: { + #[allow(unused_assignments, unused_mut)] + let mut rslt = ""; + $(rslt = $variant_str_fixed;)? + rslt + }, + ident: stringify!($variant_ident_fixed), + number: stringify!($variant_n_fixed), + }, + )* + $( + $enum_ident::$variant_ident_range(_) => crate::misc::EnumVarStrings { custom: { #[allow(unused_assignments, unused_mut)] let mut rslt = ""; - $(rslt = $variant_str;)? + $(rslt = $variant_str_range;)? rslt }, - ident: stringify!($variant_ident), - number: stringify!($variant_n), + ident: stringify!($variant_ident_range), + number: stringify!($variant_n_range), }, )* } @@ -57,7 +83,8 @@ macro_rules! create_enum { #[inline] fn from(from: $enum_ident) -> Self { match from { - $($enum_ident::$variant_ident => $variant_n,)* + $($enum_ident::$variant_ident_fixed => $variant_n_fixed,)* + $($enum_ident::$variant_ident_range(elem) => elem,)* } } } @@ -68,7 +95,8 @@ macro_rules! create_enum { #[inline] fn try_from(from: $n) -> crate::Result { let rslt = match from { - $($variant_n => Self::$variant_ident,)* + $($variant_n_fixed => Self::$variant_ident_fixed,)* + $($variant_ident_value => Self::$variant_ident_range(from),)* _ => return Err(crate::Error::UnexpectedUint { received: from.into() }), }; Ok(rslt) @@ -82,8 +110,8 @@ macro_rules! create_enum { fn try_from(from: &str) -> crate::Result { let rslt = match from { $( - stringify!($variant_ident) | stringify!($variant_n) $(| $variant_str)? => { - Self::$variant_ident + stringify!($variant_ident_fixed) | stringify!($variant_n_fixed) $(| $variant_str_fixed)? => { + Self::$variant_ident_fixed }, )* _ => return Err(crate::Error::UnexpectedString { length: from.len() }), diff --git a/wtx/src/misc.rs b/wtx/src/misc.rs index 9e6aa480..416c73c0 100644 --- a/wtx/src/misc.rs +++ b/wtx/src/misc.rs @@ -13,16 +13,16 @@ mod filled_buffer_writer; mod fn_mut_fut; mod generic_time; mod partitioned_filled_buffer; +mod poll_once; mod query_writer; mod stream; -#[cfg(feature = "_tokio-rustls-client")] +#[cfg(feature = "tokio-rustls")] mod tokio_rustls; mod traits; mod uri; -mod wrapper; -#[cfg(feature = "_tokio-rustls-client")] -pub use self::tokio_rustls::*; +#[cfg(feature = "tokio-rustls")] +pub use self::tokio_rustls::{TokioRustlsAcceptor, TokioRustlsConnector}; #[cfg(test)] use alloc::string::String; pub(crate) use array_chunks::ArrayChunksMut; @@ -34,11 +34,11 @@ pub use filled_buffer_writer::FilledBufferWriter; pub use fn_mut_fut::FnMutFut; pub use generic_time::GenericTime; pub(crate) use partitioned_filled_buffer::PartitionedFilledBuffer; +pub use poll_once::PollOnce; pub use query_writer::QueryWriter; pub use stream::{BytesStream, Stream, TlsStream}; pub use traits::SingleTypeStorage; pub use uri::{Uri, UriRef, UriString}; -pub use wrapper::Wrapper; /// Internally uses `simdutf8` if the feature was activated. #[inline] @@ -58,8 +58,6 @@ pub fn into_rslt(opt: Option) -> crate::Result { } /// Sleeps for the specified amount of time. -/// -/// Intended for asynchronous usage, i.e., won't block threads. #[allow( // Depends on the selected set of features. clippy::unused_async diff --git a/wtx/src/misc/partitioned_filled_buffer.rs b/wtx/src/misc/partitioned_filled_buffer.rs index 17e77974..5c0d678f 100644 --- a/wtx/src/misc/partitioned_filled_buffer.rs +++ b/wtx/src/misc/partitioned_filled_buffer.rs @@ -29,6 +29,10 @@ impl PartitionedFilledBuffer { } } + pub(crate) fn _empty() -> Self { + Self { _antecedent_end_idx: 0, _buffer: Vec::new(), _current_end_idx: 0, _following_end_idx: 0 } + } + pub(crate) fn _antecedent_end_idx(&self) -> usize { self._antecedent_end_idx } diff --git a/wtx/src/misc/poll_once.rs b/wtx/src/misc/poll_once.rs new file mode 100644 index 00000000..28ab3896 --- /dev/null +++ b/wtx/src/misc/poll_once.rs @@ -0,0 +1,34 @@ +use core::{ + fmt::{Debug, Formatter}, + future::Future, + pin::{pin, Pin}, + task::{Context, Poll}, +}; + +/// Pools a future in only one try. +pub struct PollOnce( + /// Future + pub F, +); + +impl Debug for PollOnce { + #[inline] + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> { + f.debug_tuple("PollOnce").finish() + } +} + +impl Future for PollOnce +where + F: Future + Unpin, +{ + type Output = Option; + + #[inline] + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match Pin::new(&mut self.0).poll(cx) { + Poll::Ready(t) => Poll::Ready(Some(t)), + Poll::Pending => Poll::Ready(None), + } + } +} diff --git a/wtx/src/misc/tokio_rustls.rs b/wtx/src/misc/tokio_rustls.rs index 578175df..53f282fa 100644 --- a/wtx/src/misc/tokio_rustls.rs +++ b/wtx/src/misc/tokio_rustls.rs @@ -1,47 +1,132 @@ -use rustls_pemfile::certs; use rustls_pki_types::ServerName; use std::sync::Arc; -use tokio::net::TcpStream; +use tokio::{ + io::{AsyncRead, AsyncWrite}, + net::TcpStream, +}; use tokio_rustls::{ client::TlsStream, - rustls::{ClientConfig, RootCertStore}, + rustls::{ + server::WantsServerCert, ClientConfig, ConfigBuilder, RootCertStore, ServerConfig, + WantsVerifier, + }, TlsConnector, }; -use webpki_roots::TLS_SERVER_ROOTS; - -/// Private method. -pub async fn tls_stream_from_host( - host: &str, - hostname: &str, - root_ca: Option<&[u8]>, -) -> crate::Result> { - let stream = TcpStream::connect(host).await?; - Ok(tls_connector(root_ca)?.connect(server_name(hostname)?, stream).await?) + +/// TLS client using `tokio-rustls` and associated crates. +#[derive(Debug)] +pub struct TokioRustlsConnector { + _alpn_protocols: Vec>, + _store: RootCertStore, } -/// Private method. -pub async fn tls_stream_from_stream( - hostname: &str, - root_ca: Option<&[u8]>, - stream: TcpStream, -) -> crate::Result> { - Ok(tls_connector(root_ca)?.connect(server_name(hostname)?, stream).await?) +impl TokioRustlsConnector { + /// From the certificates of the `webpkis-roots` project. + #[cfg(feature = "webpki-roots")] + pub fn from_webpki_roots() -> Self { + let mut this = Self::default(); + this._store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + this + } + + /// Erases the current set of ALPN protocols and then pushes the expected ALPN value for a HTTP2 + /// connection. + pub fn http2(mut self) -> Self { + self._alpn_protocols.clear(); + self._alpn_protocols.push("h2".into()); + self + } + + /// Avoids additional round trips by specifying in advance which protocols should be used. + pub fn push_alpn_protocol(mut self, protocol: &[u8]) -> Self { + self._alpn_protocols.push(protocol.into()); + self + } + + /// Pushes a sequence of certificates. + #[cfg(feature = "rustls-pemfile")] + pub fn push_certs(mut self, bytes: &[u8]) -> crate::Result { + for rslt in rustls_pemfile::certs(&mut &*bytes) { + let cert = rslt?; + self._store.add(cert)?; + } + Ok(self) + } + + /// Connects using a generic stream. + pub async fn with_generic_stream( + self, + hostname: &str, + stream: S, + ) -> crate::Result> + where + S: AsyncRead + AsyncWrite + Unpin, + { + Ok(self.tls_connector().connect(Self::server_name(hostname)?, stream).await?) + } + + /// Connects using a [TcpStream] stream. + pub async fn with_tcp_stream( + self, + host: &str, + hostname: &str, + ) -> crate::Result> { + let stream = TcpStream::connect(host).await?; + Ok(self.tls_connector().connect(Self::server_name(hostname)?, stream).await?) + } + + fn server_name(hostname: &str) -> crate::Result> { + Ok(ServerName::try_from(hostname.to_string()).map_err(invalid_input_err)?) + } + + fn tls_connector(self) -> TlsConnector { + let mut config = + ClientConfig::builder().with_root_certificates(self._store).with_no_client_auth(); + config.alpn_protocols = self._alpn_protocols; + TlsConnector::from(Arc::new(config)) + } +} + +impl Default for TokioRustlsConnector { + #[inline] + fn default() -> Self { + Self { _alpn_protocols: Vec::new(), _store: RootCertStore::empty() } + } +} + +/// TLS server using `tokio-rustls` and associated crates. +#[derive(Debug)] +pub struct TokioRustlsAcceptor { + _builder: ConfigBuilder, } -fn server_name(hostname: &str) -> crate::Result> { - Ok( - ServerName::try_from(hostname.to_string()) - .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))?, - ) +impl TokioRustlsAcceptor { + /// Sets a single certificate chain and matching private key. + #[cfg(feature = "rustls-pemfile")] + pub fn with_cert_chain_and_priv_key( + self, + cert_chain: &[u8], + priv_key: &[u8], + ) -> crate::Result { + let config = self._builder.with_single_cert( + rustls_pemfile::certs(&mut &*cert_chain).collect::>()?, + rustls_pemfile::private_key(&mut &*priv_key)? + .ok_or_else(|| invalid_input_err("No private key found"))?, + )?; + Ok(tokio_rustls::TlsAcceptor::from(Arc::new(config))) + } } -fn tls_connector(root_ca: Option<&[u8]>) -> crate::Result { - let mut root_store = RootCertStore::empty(); - root_store.extend(TLS_SERVER_ROOTS.iter().cloned()); - if let Some(elem) = root_ca { - let certs: Vec<_> = certs(&mut &*elem).collect::>()?; - let _ = root_store.add_parsable_certificates(certs); +impl Default for TokioRustlsAcceptor { + #[inline] + fn default() -> Self { + Self { _builder: ServerConfig::builder().with_no_client_auth() } } - let config = ClientConfig::builder().with_root_certificates(root_store).with_no_client_auth(); - Ok(TlsConnector::from(Arc::new(config))) +} + +fn invalid_input_err(err: E) -> std::io::Error +where + E: Into>, +{ + std::io::Error::new(std::io::ErrorKind::InvalidInput, err) } diff --git a/wtx/src/misc/traits.rs b/wtx/src/misc/traits.rs index 8216780e..c520e629 100644 --- a/wtx/src/misc/traits.rs +++ b/wtx/src/misc/traits.rs @@ -6,6 +6,13 @@ pub trait SingleTypeStorage { type Item; } +impl SingleTypeStorage for Option +where + T: SingleTypeStorage, +{ + type Item = T::Item; +} + impl SingleTypeStorage for &T where T: SingleTypeStorage, @@ -34,3 +41,8 @@ impl SingleTypeStorage for &'_ mut [T] { impl SingleTypeStorage for Vec { type Item = T; } + +#[cfg(feature = "arrayvec")] +impl SingleTypeStorage for arrayvec::ArrayVec { + type Item = T; +} diff --git a/wtx/src/misc/wrapper.rs b/wtx/src/misc/wrapper.rs deleted file mode 100644 index 6ebecc88..00000000 --- a/wtx/src/misc/wrapper.rs +++ /dev/null @@ -1,30 +0,0 @@ -/// This structure only exists because of the current coherence rules. -#[derive(Debug)] -pub struct Wrapper( - /// Element - pub T, -); - -#[cfg(all(feature = "deadpool", feature = "web-socket"))] -mod deadpool_web_socket { - use crate::{ - misc::{PartitionedFilledBuffer, Wrapper}, - web_socket::FrameBufferVec, - }; - use alloc::boxed::Box; - use deadpool::managed::{Manager, Metrics, RecycleResult}; - - #[async_trait::async_trait] - impl Manager for Wrapper<(FrameBufferVec, PartitionedFilledBuffer)> { - type Error = crate::Error; - type Type = (FrameBufferVec, PartitionedFilledBuffer); - - async fn create(&self) -> Result { - Ok(<_>::default()) - } - - async fn recycle(&self, _: &mut Self::Type, _: &Metrics) -> RecycleResult { - Ok(()) - } - } -} diff --git a/wtx/src/pool_manager.rs b/wtx/src/pool_manager.rs new file mode 100644 index 00000000..f1f9d25a --- /dev/null +++ b/wtx/src/pool_manager.rs @@ -0,0 +1,15 @@ +//! Pool Manager + +mod lock; +mod lock_guard; +mod resource_manager; +mod static_pool; + +pub use lock::Lock; +pub use lock_guard::LockGuard; +#[cfg(feature = "database")] +pub use resource_manager::database::PostgresRM; +#[cfg(feature = "web-socket")] +pub use resource_manager::websocket::WebSocketRM; +pub use resource_manager::ResourceManager; +pub use static_pool::StaticPool; diff --git a/wtx/src/pool_manager/lock.rs b/wtx/src/pool_manager/lock.rs new file mode 100644 index 00000000..9984a0e4 --- /dev/null +++ b/wtx/src/pool_manager/lock.rs @@ -0,0 +1,67 @@ +use crate::pool_manager::LockGuard; +use core::{ + cell::{RefCell, RefMut}, + future::{poll_fn, Future}, + task::Poll, +}; + +/// An asynchronous mutual exclusion primitive useful for protecting shared data. +pub trait Lock { + /// See [LockGuard]. + type Guard<'guard>: LockGuard<'guard, T> + where + Self: 'guard; + + /// Generic way to build a lock. + fn new(resource: T) -> Self; + + /// Locks this element, causing the current task to yield until the lock has been acquired. When + /// the lock has been acquired, returns a guard. + fn lock(&self) -> impl Future>; +} + +impl Lock for RefCell { + type Guard<'guard> = RefMut<'guard, T> + where + Self: 'guard; + + #[inline] + fn new(resource: T) -> Self { + RefCell::new(resource) + } + + #[inline] + fn lock(&self) -> impl Future> { + poll_fn( + |_| { + if let Ok(elem) = self.try_borrow_mut() { + Poll::Ready(elem) + } else { + Poll::Pending + } + }, + ) + } +} + +#[cfg(feature = "tokio")] +mod tokio { + use crate::pool_manager::Lock; + use tokio::sync::{Mutex, MutexGuard}; + + impl Lock for Mutex { + type Guard<'guard> = MutexGuard<'guard, T> + where + Self: 'guard; + + #[inline] + fn new(resource: T) -> Self { + Mutex::new(resource) + } + + #[inline] + async fn lock(&self) -> Self::Guard<'_> { + (*self).lock().await + } + } +} diff --git a/wtx/src/pool_manager/lock_guard.rs b/wtx/src/pool_manager/lock_guard.rs new file mode 100644 index 00000000..7162ccb3 --- /dev/null +++ b/wtx/src/pool_manager/lock_guard.rs @@ -0,0 +1,60 @@ +use core::{cell::RefMut, ops::DerefMut}; + +/// A handle to a held lock. +pub trait LockGuard<'guard, T>: DerefMut +where + T: ?Sized, +{ + /// Sometimes it is desirable to return a type that differs from the original lock. + type Mapped + where + U: ?Sized + 'guard; + + /// Makes a new mapped element for a component of the locked data. + fn map(this: Self, f: F) -> Self::Mapped + where + U: ?Sized, + F: FnOnce(&mut T) -> &mut U; +} + +impl<'guard, T> LockGuard<'guard, T> for RefMut<'guard, T> +where + T: ?Sized, +{ + type Mapped = RefMut<'guard, U> + where + U: ?Sized + 'guard; + + #[inline] + fn map(this: Self, f: F) -> Self::Mapped + where + U: ?Sized, + F: FnOnce(&mut T) -> &mut U, + { + RefMut::map(this, f) + } +} + +#[cfg(feature = "tokio")] +mod tokio { + use crate::pool_manager::LockGuard; + use tokio::sync::{MappedMutexGuard, MutexGuard}; + + impl<'guard, T> LockGuard<'guard, T> for MutexGuard<'guard, T> + where + T: ?Sized, + { + type Mapped = MappedMutexGuard<'guard, U> + where + U: ?Sized + 'guard; + + #[inline] + fn map(this: Self, f: F) -> Self::Mapped + where + U: ?Sized, + F: FnOnce(&mut T) -> &mut U, + { + MutexGuard::map(this, f) + } + } +} diff --git a/wtx/src/pool_manager/resource_manager.rs b/wtx/src/pool_manager/resource_manager.rs new file mode 100644 index 00000000..48b18052 --- /dev/null +++ b/wtx/src/pool_manager/resource_manager.rs @@ -0,0 +1,120 @@ +use core::future::Future; + +/// Manager of a specific pool resource. +pub trait ResourceManager { + /// Any custom error. + type Error; + /// Subset of a resource's data used across different instances. + type Persistent; + /// Any pool resource. + type Resource; + + /// If a resource is in an invalid state, then returns its persistent content to attempt to create + /// a new valid instance. + fn check_integrity(&self, resource: &mut Self::Resource) -> Option; + + /// Creates a new resource instance based on the contents of this manager. + fn create(&self) -> impl Future>; + + /// Re-creates a new valid instance using the persistent data of an invalid instance. + fn recycle( + &self, + persistent: Self::Persistent, + resource: &mut Self::Resource, + ) -> impl Future>; +} + +#[cfg(feature = "postgres")] +pub(crate) mod database { + use crate::{ + database::client::postgres::{Executor, ExecutorBuffer}, + pool_manager::ResourceManager, + }; + use core::{future::Future, mem}; + + /// Manages generic database executors. + #[derive(Debug)] + pub struct PostgresRM( + /// Create callback + pub fn(&I) -> CF, + /// Input data + pub I, + /// Recycle callback + pub fn(&I, ExecutorBuffer) -> RF, + ); + + impl ResourceManager for PostgresRM + where + ERR: From, + C: Future), ERR>>, + RF: Future, ERR>>, + { + type Error = ERR; + type Persistent = ExecutorBuffer; + type Resource = (O, Executor); + + #[inline] + async fn create(&self) -> Result { + (self.0)(&self.1).await + } + + #[inline] + fn check_integrity(&self, resource: &mut Self::Resource) -> Option { + if resource.1.is_closed { + let mut rslt = ExecutorBuffer::_empty(); + mem::swap(&mut rslt, &mut resource.1.eb); + Some(rslt) + } else { + None + } + } + + #[inline] + async fn recycle( + &self, + persistent: Self::Persistent, + resource: &mut Self::Resource, + ) -> Result<(), Self::Error> { + let executor = (self.2)(&self.1, persistent).await?; + resource.1 = executor; + Ok(()) + } + } +} + +#[cfg(feature = "web-socket")] +pub(crate) mod websocket { + use crate::{ + pool_manager::resource_manager::ResourceManager, + web_socket::{FrameBufferVec, WebSocketBuffer}, + }; + + /// Manages WebSocket resources. + #[derive(Debug)] + pub struct WebSocketRM; + + impl ResourceManager for WebSocketRM { + type Error = crate::Error; + type Persistent = (); + type Resource = (FrameBufferVec, WebSocketBuffer); + + #[inline] + async fn create(&self) -> Result { + Ok(<_>::default()) + } + + #[inline] + fn check_integrity(&self, _: &mut Self::Resource) -> Option { + None + } + + #[inline] + async fn recycle( + &self, + _: Self::Persistent, + _: &mut Self::Resource, + ) -> Result<(), Self::Error> { + Ok(()) + } + } +} diff --git a/wtx/src/pool_manager/static_pool.rs b/wtx/src/pool_manager/static_pool.rs new file mode 100644 index 00000000..fa41c224 --- /dev/null +++ b/wtx/src/pool_manager/static_pool.rs @@ -0,0 +1,139 @@ +use crate::{ + misc::PollOnce, + pool_manager::{Lock, LockGuard, ResourceManager}, +}; +use core::{ + array, + pin::pin, + sync::atomic::{AtomicUsize, Ordering}, +}; + +/// Fixed-size pool that does not require the use of reference-counters. On the other side, all +/// elements of the pool **must** be of type `Option` to lazily evaluate resources. +/// +/// If stack memory becomes a problem, try heap allocation. +#[derive(Debug)] +pub struct StaticPool { + idx: AtomicUsize, + locks: [L; N], + rm: M, +} + +impl StaticPool +where + L: Lock>, + M: ResourceManager, +{ + /// Initializes inner elements. + #[inline] + pub fn new(rm: M) -> crate::Result { + if N == 0 { + return Err(crate::Error::StaticPoolMustHaveCapacityForAtLeastOneElement); + } + Ok(Self { idx: AtomicUsize::new(0), locks: array::from_fn(|_| L::new(None)), rm }) + } + + /// Tries to retrieve a free resource. + /// + /// If the resource does not exist, a new one is created and if the pool is full, this method will + /// await until a free resource is available. + #[allow( + // Inner code does not trigger `panic!` + clippy::missing_panics_doc + )] + #[inline] + pub async fn get( + &self, + ) -> Result< as LockGuard<'_, Option>>::Mapped, M::Error> { + loop { + #[allow( + // `N` will never be zero + clippy::arithmetic_side_effects, + )] + let local_idx = self.idx.fetch_add(1, Ordering::Release) % N; + #[allow( + // `locks` is an array that will always have a valid `self.idx % N` element + clippy::unwrap_used + )] + let lock = self.locks.get(local_idx).unwrap(); + if let Some(mut guard) = PollOnce(pin!(lock.lock())).await { + match &mut *guard { + None => { + *guard = Some(self.rm.create().await?); + } + Some(resource) => { + if let Some(persistent) = self.rm.check_integrity(resource) { + self.rm.recycle(persistent, resource).await?; + } + } + } + #[allow( + // The above match took care of nullable guards + clippy::unwrap_used + )] + return Ok(LockGuard::map(guard, |el| el.as_mut().unwrap())); + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::pool_manager::{ResourceManager, StaticPool}; + use core::cell::RefCell; + + struct TestManager; + + impl ResourceManager for TestManager { + type Persistent = (); + type Error = crate::Error; + type Resource = i32; + + #[inline] + async fn create(&self) -> Result { + Ok(0) + } + + #[inline] + fn check_integrity(&self, _: &mut Self::Resource) -> Option { + None + } + + #[inline] + async fn recycle( + &self, + _: Self::Persistent, + _: &mut Self::Resource, + ) -> Result<(), Self::Error> { + Ok(()) + } + } + + #[tokio::test] + async fn modifies_elements() { + let pool = pool(); + assert_eq!([*pool.get().await.unwrap(), *pool.get().await.unwrap()], [0, 0]); + *pool.get().await.unwrap() = 1; + *pool.get().await.unwrap() = 2; + assert_eq!([*pool.get().await.unwrap(), *pool.get().await.unwrap()], [1, 2]); + *pool.get().await.unwrap() = 3; + assert_eq!([*pool.get().await.unwrap(), *pool.get().await.unwrap()], [2, 3]); + } + + #[tokio::test] + async fn held_lock_is_not_modified() { + let pool = pool(); + let lock = pool.get().await.unwrap(); + *pool.get().await.unwrap() = 1; + assert_eq!([*lock, *pool.get().await.unwrap()], [0, 1]); + *pool.get().await.unwrap() = 2; + assert_eq!([*lock, *pool.get().await.unwrap()], [0, 2]); + drop(lock); + *pool.get().await.unwrap() = 1; + assert_eq!([*pool.get().await.unwrap(), *pool.get().await.unwrap()], [2, 1]); + } + + fn pool() -> StaticPool>, TestManager, 2> { + StaticPool::new(TestManager).unwrap() + } +} diff --git a/wtx/tests/embed-migrations.rs b/wtx/tests/embed-migrations.rs index 5a5e3798..4822a6df 100644 --- a/wtx/tests/embed-migrations.rs +++ b/wtx/tests/embed-migrations.rs @@ -1,9 +1,9 @@ -#![cfg(feature = "sm")] +#![cfg(feature = "schema-manager")] #[allow(dead_code)] mod embedded_migrations; -use wtx::database::sm::Commands; +use wtx::database::schema_manager::Commands; async fn _compiles() { let mut commands = Commands::with_executor(()); diff --git a/wtx/tests/embedded_migrations/mod.rs b/wtx/tests/embedded_migrations/mod.rs index 0c128eb4..70477308 100644 --- a/wtx/tests/embedded_migrations/mod.rs +++ b/wtx/tests/embedded_migrations/mod.rs @@ -1,15 +1,15 @@ -#[rustfmt::skip]pub(crate) const GROUPS: wtx::database::sm::EmbeddedMigrationsTy = &[{const INITIAL: &wtx::database::sm::MigrationGroup<&'static str> = &wtx::database::sm::MigrationGroup::new("INITIAL",1);const INITIAL_MIGRATIONS: &[wtx::database::sm::UserMigrationRef<'static, 'static>] = &[wtx::database::sm::UserMigrationRef::from_all_parts(7573493478190316387,&[],"create_author",None,"DROP TABLE author;","CREATE TABLE author ( +#[rustfmt::skip]pub(crate) const GROUPS: wtx::database::schema_manager::EmbeddedMigrationsTy = &[{const INITIAL: &wtx::database::schema_manager::MigrationGroup<&'static str> = &wtx::database::schema_manager::MigrationGroup::new("INITIAL",1);const INITIAL_MIGRATIONS: &[wtx::database::schema_manager::UserMigrationRef<'static, 'static>] = &[wtx::database::schema_manager::UserMigrationRef::from_all_parts(7573493478190316387,&[],"create_author",None,"DROP TABLE author;","CREATE TABLE author ( id INT NOT NULL PRIMARY KEY, first_name VARCHAR(50) NOT NULL, last_name VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL -);",1),wtx::database::sm::UserMigrationRef::from_all_parts(14432364634995446648,&[],"create_post",None,"DROP TABLE post;","CREATE TABLE post ( +);",1),wtx::database::schema_manager::UserMigrationRef::from_all_parts(14432364634995446648,&[],"create_post",None,"DROP TABLE post;","CREATE TABLE post ( id INT NOT NULL PRIMARY KEY, author_id INT NOT NULL, title VARCHAR(255) NOT NULL, description VARCHAR(500) NOT NULL, content TEXT NOT NULL -);",2),wtx::database::sm::UserMigrationRef::from_all_parts(17129955996605416467,&[],"insert_author",None,"DELETE FROM author;","INSERT INTO author(id, first_name, last_name, email) VALUES +);",2),wtx::database::schema_manager::UserMigrationRef::from_all_parts(17129955996605416467,&[],"insert_author",None,"DELETE FROM author;","INSERT INTO author(id, first_name, last_name, email) VALUES ('1','Werner','Turcotte','bryce.wiza@example.com'), ('2','Issac','Schroeder','luisa01@example.com'), ('3','Lorenzo','Grant','granville.crist@example.com'), @@ -29,10 +29,10 @@ ('17','Grover','Cartwright','rcronin@example.net'), ('18','Sarah','Stokes','gavin.reinger@example.org'), ('19','Norwood','Hessel','torp.serena@example.org'), -('20','Carol','Fay','pacocha.ora@example.org');",3),wtx::database::sm::UserMigrationRef::from_all_parts(13816380377167203395,&[],"insert_post",None,"DELETE FROM post;","INSERT INTO post(id, author_id, title, description, content) VALUES +('20','Carol','Fay','pacocha.ora@example.org');",3),wtx::database::schema_manager::UserMigrationRef::from_all_parts(13816380377167203395,&[],"insert_post",None,"DELETE FROM post;","INSERT INTO post(id, author_id, title, description, content) VALUES ('1','1','Laborum voluptatum est et.','Optio fugiat eveniet nihil voluptatem ea. Ut enim qui in ratione. Veritatis non in nisi est quis accusamus animi. Velit quasi ducimus nostrum deleniti consequatur sapiente nulla. Placeat dolores tempore totam amet occaecati minus error.','Aliquid ea quis reiciendis debitis omnis iste at. Voluptas quasi perspiciatis id officiis iste eligendi. Et quia et voluptatem ea. Aut incidunt alias quibusdam quas temporibus molestias facere porro.'), ('2','2','Ea aliquid suscipit mollitia ex adipisci.','Dolores explicabo sed illum nihil magnam animi quae temporibus. Omnis a nesciunt optio consequatur non. Veritatis omnis reprehenderit dolorem reprehenderit aspernatur. Natus id est nihil est voluptas aut omnis. Velit labore architecto explicabo labore.','Enim sit non eum veritatis. Aspernatur ipsa dolores quae perferendis. Nobis nemo nobis ab esse quia soluta. Magnam aperiam harum veritatis quos et laborum repudiandae.'), -('3','3','Dicta vero fugiat suscipit ut.','Quis quia qui modi voluptatem. Quo omnis consequatur et blanditiis quia consequuntur rerum. Ea dolorum magnam ab quibusdam. Velit fugit ratione ex qui quisquam aspernatur repellat.','Recusandae officia placeat enim ut aut animi. Atque minima hic quia repudiandae nobis et sed. Incidunt explicabo debitis eligendi sed. Reiciendis maiores velit atque ea ut.');",4),];(INITIAL,INITIAL_MIGRATIONS)},{const MORE_STUFF: &wtx::database::sm::MigrationGroup<&'static str> = &wtx::database::sm::MigrationGroup::new("MORE_STUFF",2);const MORE_STUFF_MIGRATIONS: &[wtx::database::sm::UserMigrationRef<'static, 'static>] = &[wtx::database::sm::UserMigrationRef::from_all_parts(8208328219135761847,&[],"create_stuff",None,"DROP TABLE coffee; +('3','3','Dicta vero fugiat suscipit ut.','Quis quia qui modi voluptatem. Quo omnis consequatur et blanditiis quia consequuntur rerum. Ea dolorum magnam ab quibusdam. Velit fugit ratione ex qui quisquam aspernatur repellat.','Recusandae officia placeat enim ut aut animi. Atque minima hic quia repudiandae nobis et sed. Incidunt explicabo debitis eligendi sed. Reiciendis maiores velit atque ea ut.');",4),];(INITIAL,INITIAL_MIGRATIONS)},{const MORE_STUFF: &wtx::database::schema_manager::MigrationGroup<&'static str> = &wtx::database::schema_manager::MigrationGroup::new("MORE_STUFF",2);const MORE_STUFF_MIGRATIONS: &[wtx::database::schema_manager::UserMigrationRef<'static, 'static>] = &[wtx::database::schema_manager::UserMigrationRef::from_all_parts(8208328219135761847,&[],"create_stuff",None,"DROP TABLE coffee; DROP TABLE apple;","CREATE TABLE apple ( id INT NOT NULL PRIMARY KEY, weight INT NOT NULL @@ -40,4 +40,4 @@ DROP TABLE apple;","CREATE TABLE apple ( CREATE TABLE coffee ( id INT NOT NULL PRIMARY KEY -);",1),wtx::database::sm::UserMigrationRef::from_all_parts(7192396384181136034,&[],"insert_stuff",None,"","",2),wtx::database::sm::UserMigrationRef::from_all_parts(16226037772308796192,&[wtx::database::DatabaseTy::Postgres,],"ultra_fancy_stuff",Some(wtx::database::sm::Repeatability::Always),"","",3),];(MORE_STUFF,MORE_STUFF_MIGRATIONS)},]; +);",1),wtx::database::schema_manager::UserMigrationRef::from_all_parts(7192396384181136034,&[],"insert_stuff",None,"","",2),wtx::database::schema_manager::UserMigrationRef::from_all_parts(16226037772308796192,&[wtx::database::DatabaseTy::Postgres,],"ultra_fancy_stuff",Some(wtx::database::schema_manager::Repeatability::Always),"","",3),];(MORE_STUFF,MORE_STUFF_MIGRATIONS)},];