diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d123063de725..7ead1e69a92b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: matrix: rust: ["stable", "beta", "nightly"] backend: ["postgres", "sqlite", "mysql"] - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-latest, windows-2019] runs-on: ${{ matrix.os }} steps: - name: Checkout sources @@ -153,13 +153,7 @@ jobs: run: | choco install sqlite cd /D C:\ProgramData\chocolatey\lib\SQLite\tools - dir "C:\Program Files\Microsoft Visual Studio" - dir "C:\Program Files\Microsoft Visual Studio\2022" - dir "C:\Program Files\Microsoft Visual Studio\2022\Enterprise" - dir "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC" - dir "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary" - dir "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build" - call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" lib /machine:x64 /def:sqlite3.def /out:sqlite3.lib - name: Set variables for sqlite (Windows) @@ -324,7 +318,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.54.0 + toolchain: 1.56.0 profile: minimal override: true - name: Cache cargo registry @@ -353,7 +347,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.54.0 + toolchain: 1.57.0 profile: minimal components: clippy, rustfmt override: true @@ -369,6 +363,11 @@ jobs: run: | sudo apt-get update sudo apt-get -y install libsqlite3-dev libpq-dev libmysqlclient-dev + - name: Set environment variables + shell: bash + run: | + echo "RUSTFLAGS=-D warnings" >> $GITHUB_ENV + echo "RUSTDOCFLAGS=-D warnings" >> $GITHUB_ENV - name: Remove potential newer clippy.toml from dependencies run: | @@ -377,10 +376,46 @@ jobs: find ~/.cargo/registry -iname "*clippy.toml" -delete - name: Run clippy - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --all + run: | + cargo clippy --tests --manifest-path diesel_derives/Cargo.toml --features "postgres diesel/postgres" + cargo clippy --tests --manifest-path diesel/Cargo.toml --features "postgres" + cargo clippy --tests --manifest-path diesel_dynamic_schema/Cargo.toml --features "postgres diesel/postgres" + cargo clippy --tests --manifest-path diesel_migrations/migrations_internals/Cargo.toml + cargo clippy --tests --manifest-path diesel_migrations/migrations_macros/Cargo.toml --features "postgres diesel/postgres" + cargo clippy --tests --manifest-path diesel_migrations/Cargo.toml --features "postgres diesel/postgres" + cargo clippy --tests --manifest-path diesel_cli/Cargo.toml --features "postgres" --no-default-features + cargo clippy --tests --manifest-path diesel_tests/Cargo.toml --features "postgres" + cargo clippy --tests --manifest-path examples/postgres/getting_started_step_1/Cargo.toml + cargo clippy --tests --manifest-path examples/postgres/getting_started_step_2/Cargo.toml + cargo clippy --tests --manifest-path examples/postgres/getting_started_step_3/Cargo.toml + cargo clippy --tests --manifest-path examples/postgres/advanced-blog-cli/Cargo.toml + cargo clippy --tests --manifest-path examples/postgres/all_about_inserts/Cargo.toml + cargo clippy --tests --manifest-path examples/postgres/all_about_updates/Cargo.toml + cargo clippy --tests --manifest-path examples/postgres/custom_types/Cargo.toml + + cargo clippy --tests --manifest-path diesel_derives/Cargo.toml --features "sqlite diesel/sqlite" + cargo clippy --tests --manifest-path diesel/Cargo.toml --features "sqlite" + cargo clippy --tests --manifest-path diesel_dynamic_schema/Cargo.toml --features "sqlite diesel/sqlite" + cargo clippy --tests --manifest-path diesel_migrations/migrations_macros/Cargo.toml --features "sqlite diesel/sqlite" + cargo clippy --tests --manifest-path diesel_migrations/Cargo.toml --features "sqlite diesel/sqlite" + cargo clippy --tests --manifest-path diesel_cli/Cargo.toml --features "sqlite" --no-default-features + cargo clippy --tests --manifest-path diesel_tests/Cargo.toml --features "sqlite" + cargo clippy --tests --manifest-path examples/sqlite/getting_started_step_1/Cargo.toml + cargo clippy --tests --manifest-path examples/sqlite/getting_started_step_2/Cargo.toml + cargo clippy --tests --manifest-path examples/sqlite/getting_started_step_3/Cargo.toml + cargo clippy --tests --manifest-path examples/sqlite/all_about_inserts/Cargo.toml + + cargo clippy --tests --manifest-path diesel_derives/Cargo.toml --features "mysql diesel/mysql" + cargo clippy --tests --manifest-path diesel/Cargo.toml --features "mysql" + cargo clippy --tests --manifest-path diesel_dynamic_schema/Cargo.toml --features "mysql diesel/mysql" + cargo clippy --tests --manifest-path diesel_migrations/migrations_macros/Cargo.toml --features "mysql diesel/mysql" + cargo clippy --tests --manifest-path diesel_migrations/Cargo.toml --features "mysql diesel/mysql" + cargo clippy --tests --manifest-path diesel_cli/Cargo.toml --features "mysql" --no-default-features + cargo clippy --tests --manifest-path diesel_tests/Cargo.toml --features "mysql" + cargo clippy --tests --manifest-path examples/mysql/getting_started_step_1/Cargo.toml + cargo clippy --tests --manifest-path examples/mysql/getting_started_step_2/Cargo.toml + cargo clippy --tests --manifest-path examples/mysql/getting_started_step_3/Cargo.toml + cargo clippy --tests --manifest-path examples/mysql/all_about_inserts/Cargo.toml - name: Check formating uses: actions-rs/cargo@v1 @@ -427,13 +462,13 @@ jobs: run: cargo -Z build-std test --manifest-path diesel/Cargo.toml --no-default-features --features "sqlite extras libsqlite3-sys libsqlite3-sys/bundled libsqlite3-sys/with-asan" --target x86_64-unknown-linux-gnu minimal_rust_version: - name: Check Minimal supported rust version (1.54.0) + name: Check Minimal supported rust version (1.56.0) runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: - toolchain: 1.54.0 + toolchain: 1.56.0 profile: minimal override: true - name: Cache cargo registry diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b31193b4632..78a5d08f2653 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,28 @@ default features enabled using some set of dependencies. Those set of dependenci an up to date version of the specific dependency. We check this by using the unstable `-Z minimal-version` cargo flag. Increasing the minimal supported Rust version will always be coupled at least with a minor release. -## Unreleased +## [2.0.0 Rc1] 2022-07-19 + +### Changed + +* We've changed the `RunQueryDsl::load_iter` interface to support different +loading modes. This enables us to support more than one strategy for loading +values by iterator from the database. + +### Added + +* Adds an `ipnet-address` feature flag, allowing support (de)serializing IP + values from the database using types provided by `ipnet`. This feature + may be enabled concurrently with the previously existing `network-address` + feature. +* We've added support for loading values using libpq's row-by-row mode via +the new iterator interface + +### Fixed + +* Updated `ipnetwork` to allow version 0.20. +* Updated `libsqlite3-sys` to allow version 0.25 +* Fix a bug that prevents connection reusing with r2d2 ## [2.0.0 Rc0] 2022-04-22 @@ -109,7 +130,7 @@ Increasing the minimal supported Rust version will always be coupled at least wi ### Changed -* The minimal officially supported rustc version is now 1.54.0 +* The minimal officially supported rustc version is now 1.56.0 * Interacting with a database requires a mutable connection. diff --git a/diesel/Cargo.toml b/diesel/Cargo.toml index 78341f1eba8e..6e0603e42daa 100644 --- a/diesel/Cargo.toml +++ b/diesel/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "diesel" -version = "2.0.0-rc.0" +version = "2.0.0-rc.1" license = "MIT OR Apache-2.0" description = "A safe, extensible ORM and Query Builder for PostgreSQL, SQLite, and MySQL" readme = "README.md" @@ -10,13 +10,13 @@ repository = "https://github.com/diesel-rs/diesel" keywords = ["orm", "database", "sql"] categories = ["database"] edition = "2018" -rust-version = "1.54.0" +rust-version = "1.56.0" [dependencies] byteorder = { version = "1.0", optional = true } chrono = { version = "0.4.19", optional = true, default-features = false, features = ["clock", "std"] } libc = { version = "0.2.0", optional = true } -libsqlite3-sys = { version = ">=0.17.2, <0.25.0", optional = true, features = ["bundled_bindings"] } +libsqlite3-sys = { version = ">=0.17.2, <0.26.0", optional = true, features = ["bundled_bindings"] } mysqlclient-sys = { version = "0.2.5", optional = true } pq-sys = { version = "0.4.0", optional = true } quickcheck = { version = "1.0.3", optional = true } @@ -24,7 +24,8 @@ serde_json = { version = ">=0.8.0, <2.0", optional = true } url = { version = "2.1.0", optional = true } percent-encoding = { version = "2.1.0", optional = true } uuid = { version = ">=0.7.0, <2.0.0", optional = true } -ipnetwork = { version = ">=0.12.2, <0.19.0", optional = true } +ipnetwork = { version = ">=0.12.2, <0.21.0", optional = true } +ipnet = { version = "2.5.0", optional = true } num-bigint = { version = ">=0.2.0, <0.5.0", optional = true } num-traits = { version = "0.2.0", optional = true } num-integer = { version = "0.1.39", optional = true } @@ -34,13 +35,13 @@ r2d2 = { version = ">= 0.8.2, < 0.9.0", optional = true } itoa = { version = "1.0.0", optional = true } [dependencies.diesel_derives] -version = "~2.0.0-rc.0" +version = "~2.0.0-rc.1" path = "../diesel_derives" [dev-dependencies] cfg-if = "1" dotenvy = "0.15" -ipnetwork = ">=0.12.2, <0.19.0" +ipnetwork = ">=0.12.2, <0.21.0" quickcheck = "1.0.3" [features] @@ -58,6 +59,7 @@ mysql = ["mysqlclient-sys", "url", "percent-encoding", "bitflags", "mysql_backen without-deprecated = ["diesel_derives/without-deprecated"] with-deprecated = ["diesel_derives/with-deprecated"] network-address = ["ipnetwork", "libc"] +ipnet-address = ["ipnet", "libc"] numeric = ["num-bigint", "bigdecimal", "num-traits", "num-integer"] postgres_backend = ["diesel_derives/postgres", "bitflags", "byteorder", "itoa"] mysql_backend = ["diesel_derives/mysql", "byteorder"] diff --git a/diesel/src/backend.rs b/diesel/src/backend.rs index 07efb81f700e..93e4bdc2e90d 100644 --- a/diesel/src/backend.rs +++ b/diesel/src/backend.rs @@ -142,7 +142,10 @@ pub type BindCollector<'a, DB> = >::BindCollector; /// a custom `QueryFragment` implementation /// to specialize on generic `QueryFragment` implementations. /// -/// See the [`sql_dialect`] module for options provided by diesel out of the box. +#[cfg_attr( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes", + doc = "See the [`sql_dialect`] module for options provided by diesel out of the box." +)] pub trait SqlDialect: self::private::TrustedBackend { /// Configures how this backends supports `RETURNING` clauses /// @@ -157,13 +160,19 @@ pub trait SqlDialect: self::private::TrustedBackend { doc = "implementation for `ReturningClause`" )] /// - /// See [`sql_dialect::returning_clause`] for provided default implementations + #[cfg_attr( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes", + doc = "See [`sql_dialect::returning_clause`] for provided default implementations" + )] type ReturningClause; /// Configures how this backend supports `ON CONFLICT` clauses /// /// This allows backends to opt in `ON CONFLICT` clause support /// - /// See [`sql_dialect::on_conflict_clause`] for provided default implementations + #[cfg_attr( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes", + doc = "See [`sql_dialect::on_conflict_clause`] for provided default implementations" + )] type OnConflictClause; /// Configures how this backend handles the bare `DEFAULT` keyword for /// inserting the default value in a `INSERT INTO` `VALUES` clause @@ -171,7 +180,10 @@ pub trait SqlDialect: self::private::TrustedBackend { /// This allows backends to opt in support for `DEFAULT` value expressions /// for insert statements /// - /// See [`sql_dialect::default_keyword_for_insert`] for provided default implementations + #[cfg_attr( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes", + doc = "See [`sql_dialect::default_keyword_for_insert`] for provided default implementations" + )] type InsertWithDefaultKeyword; /// Configures how this backend handles Batch insert statements /// @@ -185,7 +197,10 @@ pub trait SqlDialect: self::private::TrustedBackend { doc = "implementation for `BatchInsert`" )] /// - /// See [`sql_dialect::batch_insert_support`] for provided default implementations + #[cfg_attr( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes", + doc = "See [`sql_dialect::batch_insert_support`] for provided default implementations" + )] type BatchInsertSupport; /// Configures how this backend handles the `DEFAULT VALUES` clause for /// insert statements. @@ -200,7 +215,10 @@ pub trait SqlDialect: self::private::TrustedBackend { doc = "implementation for `DefaultValues`" )] /// - /// See [`sql_dialect::default_value_clause`] for provided default implementations + #[cfg_attr( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes", + doc = "See [`sql_dialect::default_value_clause`] for provided default implementations" + )] type DefaultValueClauseForInsert; /// Configures how this backend handles empty `FROM` clauses for select statements. /// @@ -214,7 +232,10 @@ pub trait SqlDialect: self::private::TrustedBackend { doc = "implementation for `NoFromClause`" )] /// - /// See [`sql_dialect::from_clause_syntax`] for provided default implementations + #[cfg_attr( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes", + doc = "See [`sql_dialect::from_clause_syntax`] for provided default implementations" + )] type EmptyFromClauseSyntax; /// Configures how this backend handles `EXISTS()` expressions. /// @@ -228,7 +249,10 @@ pub trait SqlDialect: self::private::TrustedBackend { doc = "implementation for `Exists`" )] /// - /// See [`sql_dialect::exists_syntax`] for provided default implementations + #[cfg_attr( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes", + doc = "See [`sql_dialect::exists_syntax`] for provided default implementations" + )] type ExistsSyntax; /// Configures how this backend handles `IN()` and `NOT IN()` expressions. @@ -245,18 +269,43 @@ pub trait SqlDialect: self::private::TrustedBackend { doc = "implementations for `In`, `NotIn` and `Many`" )] /// - /// See `[sql_dialect::array_comparison`] for provided default implementations - type ArrayComparision; + #[cfg_attr( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes", + doc = "See [`sql_dialect::array_comparison`] for provided default implementations" + )] + type ArrayComparison; } /// This module contains all options provided by diesel to configure the [`SqlDialect`] trait. -pub mod sql_dialect { +// This module is only public behind the unstable feature flag, as we may want to change SqlDialect +// implementations of existing backends because of: +// * The backend gained support for previously unsupported SQL operations +// * The backend fixed/introduced a bug that requires special handling +// * We got some edge case wrong with sharing the implementation between backends +// +// By not exposing these types publically we are able to change the exact definitions later on +// as users cannot write trait bounds that ensure that a specific type is used in place of +// an existing associated type. +#[diesel_derives::__diesel_public_if( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" +)] +pub(crate) mod sql_dialect { + #![cfg_attr( + not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"), + // Otherwise there are false positives + // because the lint seems to believe that these pub statements + // are not required, but they are required through the various backend impls + allow(unreachable_pub) + )] #[cfg(doc)] use super::SqlDialect; /// This module contains all diesel provided reusable options to /// configure [`SqlDialect::OnConflictClause`] - pub mod on_conflict_clause { + #[diesel_derives::__diesel_public_if( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" + )] + pub(crate) mod on_conflict_clause { /// A marker trait indicating if a `ON CONFLICT` clause is supported or not /// /// If you use a custom type to specify specialized support for `ON CONFLICT` clauses @@ -271,7 +320,10 @@ pub mod sql_dialect { /// This module contains all reusable options to configure /// [`SqlDialect::ReturningClause`] - pub mod returning_clause { + #[diesel_derives::__diesel_public_if( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" + )] + pub(crate) mod returning_clause { /// A marker trait indicating if a `RETURNING` clause is supported or not /// /// If you use custom type to specify specialized support for `RETURNING` clauses @@ -292,7 +344,10 @@ pub mod sql_dialect { /// This module contains all reusable options to configure /// [`SqlDialect::InsertWithDefaultKeyword`] - pub mod default_keyword_for_insert { + #[diesel_derives::__diesel_public_if( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" + )] + pub(crate) mod default_keyword_for_insert { /// A marker trait indicating if a `DEFAULT` like expression /// is supported as part of `INSERT INTO` clauses to indicate /// that a default value should be inserted at a specific position @@ -318,7 +373,10 @@ pub mod sql_dialect { /// This module contains all reusable options to configure /// [`SqlDialect::BatchInsertSupport`] - pub mod batch_insert_support { + #[diesel_derives::__diesel_public_if( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" + )] + pub(crate) mod batch_insert_support { /// A marker trait indicating if batch insert statements /// are supported for this backend or not pub trait SupportsBatchInsert {} @@ -341,7 +399,10 @@ pub mod sql_dialect { /// This module contains all reusable options to configure /// [`SqlDialect::DefaultValueClauseForInsert`] - pub mod default_value_clause { + #[diesel_derives::__diesel_public_if( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" + )] + pub(crate) mod default_value_clause { /// Indicates that this backend uses the /// `DEFAULT VALUES` syntax to specify @@ -353,7 +414,10 @@ pub mod sql_dialect { /// This module contains all reusable options to configure /// [`SqlDialect::EmptyFromClauseSyntax`] - pub mod from_clause_syntax { + #[diesel_derives::__diesel_public_if( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" + )] + pub(crate) mod from_clause_syntax { /// Indicates that this backend skips /// the `FROM` clause in `SELECT` statements @@ -364,7 +428,10 @@ pub mod sql_dialect { /// This module contains all reusable options to configure /// [`SqlDialect::ExistsSyntax`] - pub mod exists_syntax { + #[diesel_derives::__diesel_public_if( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" + )] + pub(crate) mod exists_syntax { /// Indicates that this backend /// treats `EXIST()` as function @@ -374,8 +441,11 @@ pub mod sql_dialect { } /// This module contains all reusable options to configure - /// [`SqlDialect::ArrayComparision`] - pub mod array_comparision { + /// [`SqlDialect::ArrayComparison`] + #[diesel_derives::__diesel_public_if( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" + )] + pub(crate) mod array_comparison { /// Indicates that this backend requires a single bind /// per array element in `IN()` and `NOT IN()` expression diff --git a/diesel/src/connection/commit_error_processor.rs b/diesel/src/connection/commit_error_processor.rs deleted file mode 100644 index 25845ca8f555..000000000000 --- a/diesel/src/connection/commit_error_processor.rs +++ /dev/null @@ -1,162 +0,0 @@ -//! A module to evaluate what to do when a commit triggers an error. -use crate::result::Error; - -/// Transaction status returned upon error on commit. -#[derive(Debug)] -#[non_exhaustive] -#[cfg_attr( - doc_cfg, - doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")) -)] -pub enum CommitErrorOutcome { - /// Necessitates a rollback to return to a valid transaction - RollbackAndThrow(Error), - /// Broken transaction. Returned if an error has occurred earlier in a Postgres transaction. - Throw(Error), - /// Broken transaction. Similar to `Throw`, but marks the manager as broken. It should switch - /// to `TransactionManagerStatus::InError` and refuse to run additional operations. - ThrowAndMarkManagerAsBroken(Error), -} - -/// Trait needed for the transaction manager. -#[cfg_attr( - doc_cfg, - doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")) -)] -pub trait CommitErrorProcessor { - /// Returns the status of the transaction following an error upon commit. - /// When any of these kinds of error happen on `COMMIT`, it is expected - /// that a `ROLLBACK` would succeed, leaving the transaction in a non-broken state. - /// If there are other such errors, it is fine to add them here. - fn process_commit_error(&self, error: Error) -> CommitErrorOutcome; -} - -/// Default implementation of CommitErrorProcessor::process_commit_error(), used for MySql and -/// Sqlite connections. Returns `CommitErrorOutcome::RollbackAndThrow` if the transaction depth is -/// greater than 1, the error is a `DatabaseError` and the error kind is either -/// `DatabaseErrorKind::SerializationFailure` or `DatabaseErrorKind::ReadOnlyTransaction` -#[cfg_attr( - doc_cfg, - doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")) -)] -#[cfg(any( - feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes", - feature = "mysql", - feature = "sqlite" -))] -#[diesel_derives::__diesel_public_if( - feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" -)] -pub(crate) fn default_process_commit_error( - transaction_state: &super::ValidTransactionManagerStatus, - error: Error, -) -> CommitErrorOutcome { - use crate::result::DatabaseErrorKind; - - if let Some(transaction_depth) = transaction_state.transaction_depth() { - match error { - // Neither mysql nor sqlite do currently produce these errors - // we keep this match arm here for the case we may generate - // such errors in future versions of diesel - Error::DatabaseError(DatabaseErrorKind::ReadOnlyTransaction, _) - | Error::DatabaseError(DatabaseErrorKind::SerializationFailure, _) - if transaction_depth.get() == 1 => - { - CommitErrorOutcome::RollbackAndThrow(error) - } - Error::AlreadyInTransaction - | Error::DatabaseError(DatabaseErrorKind::CheckViolation, _) - | Error::DatabaseError(DatabaseErrorKind::ClosedConnection, _) - | Error::DatabaseError(DatabaseErrorKind::ForeignKeyViolation, _) - | Error::DatabaseError(DatabaseErrorKind::NotNullViolation, _) - | Error::DatabaseError(DatabaseErrorKind::ReadOnlyTransaction, _) - | Error::DatabaseError(DatabaseErrorKind::SerializationFailure, _) - | Error::DatabaseError(DatabaseErrorKind::UnableToSendCommand, _) - | Error::DatabaseError(DatabaseErrorKind::UniqueViolation, _) - | Error::DatabaseError(DatabaseErrorKind::Unknown, _) - | Error::DeserializationError(_) - | Error::InvalidCString(_) - | Error::NotFound - | Error::QueryBuilderError(_) - | Error::RollbackError(_) - | Error::RollbackTransaction - | Error::SerializationError(_) - | Error::NotInTransaction - | Error::BrokenTransaction - | Error::CommitTransactionFailed { .. } => CommitErrorOutcome::Throw(error), - } - } else { - unreachable!( - "Calling commit_error_processor outside of a transaction is implementation error.\ - If you ever see this error message outside implementing a custom transaction manager\ - please open a new issue at diesels issue tracker." - ) - } -} - -#[cfg(test)] -#[cfg(any( - feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes", - feature = "mysql", - feature = "sqlite" -))] -mod tests { - use super::CommitErrorOutcome; - use crate::connection::ValidTransactionManagerStatus; - use crate::result::{DatabaseErrorKind, Error}; - use std::num::NonZeroU32; - - #[test] - fn check_default_process_commit_error_implementation() { - let state = ValidTransactionManagerStatus { - // Transaction depth == 1, so one unnested transaction - transaction_depth: NonZeroU32::new(1), - previous_error_relevant_for_rollback: None, - }; - let error = Error::DatabaseError( - DatabaseErrorKind::ReadOnlyTransaction, - Box::new(String::from("whatever")), - ); - let action = super::default_process_commit_error(&state, error); - assert!(matches!(action, CommitErrorOutcome::RollbackAndThrow(_))); - - let error = Error::DatabaseError( - DatabaseErrorKind::UnableToSendCommand, - Box::new(String::from("whatever")), - ); - let action = super::default_process_commit_error(&state, error); - assert!(matches!(action, CommitErrorOutcome::Throw(_))); - - let state = ValidTransactionManagerStatus { - // Transaction depth == 2, so two nested transactions - transaction_depth: NonZeroU32::new(2), - previous_error_relevant_for_rollback: None, - }; - let error = Error::DatabaseError( - DatabaseErrorKind::ReadOnlyTransaction, - Box::new(String::from("whatever")), - ); - let action = super::default_process_commit_error(&state, error); - assert!(matches!(action, CommitErrorOutcome::Throw(_))); - } - - #[test] - #[should_panic] - fn check_invalid_transaction_state_rejected() { - let state = ValidTransactionManagerStatus { - // Transaction depth == None, so no transaction running, so nothing - // to rollback. Something went wrong so mark everything as broken. - transaction_depth: None, - previous_error_relevant_for_rollback: None, - }; - let error = Error::DatabaseError( - DatabaseErrorKind::ReadOnlyTransaction, - Box::new(String::from("whatever")), - ); - let action = super::default_process_commit_error(&state, error); - assert!(matches!( - action, - CommitErrorOutcome::ThrowAndMarkManagerAsBroken(_) - )); - } -} diff --git a/diesel/src/connection/mod.rs b/diesel/src/connection/mod.rs index b1d82951f30d..defeb5c2ac2d 100644 --- a/diesel/src/connection/mod.rs +++ b/diesel/src/connection/mod.rs @@ -1,11 +1,5 @@ //! Types related to database connections -#[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")] -pub mod commit_error_processor; - -#[cfg(not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))] -pub(crate) mod commit_error_processor; - #[cfg(all( not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"), any(feature = "sqlite", feature = "postgres", feature = "mysql") @@ -22,7 +16,7 @@ use crate::result::*; use std::fmt::Debug; pub use self::transaction_manager::{ - AnsiTransactionManager, TransactionManager, TransactionManagerStatus, + AnsiTransactionManager, TransactionDepthChange, TransactionManager, TransactionManagerStatus, ValidTransactionManagerStatus, }; @@ -42,11 +36,11 @@ pub trait SimpleConnection { fn batch_execute(&mut self, query: &str) -> QueryResult<()>; } -/// Return type of [`Connection::load`]. +/// Return type of [`LoadConnection::load`]. /// /// Users should threat this type as `impl Iterator>>` -pub type LoadRowIter<'conn, 'query, C, DB> = - >::Cursor; +pub type LoadRowIter<'conn, 'query, C, DB, B = DefaultLoadingMode> = + >::Cursor; /// A connection to a database /// @@ -72,10 +66,11 @@ pub type LoadRowIter<'conn, 'query, C, DB> = /// /// Wrapping an existing connection allows you to customize the implementation to /// add additional functionality, like for example instrumentation. For this use case -/// you only need to implement `Connection` and all super traits. You should forward -/// any method call to the wrapped connection type. It is **important** to also -/// forward any method where diesel provides a default implementation, as the -/// wrapped connection implementation may contain a customized implementation. +/// you only need to implement `Connection`, [`LoadConnection`] and all super traits. +/// You should forward any method call to the wrapped connection type. +/// It is **important** to also forward any method where diesel provides a +/// default implementation, as the wrapped connection implementation may +/// contain a customized implementation. /// /// To allow the integration of your new connection type with other diesel features #[cfg_attr( @@ -96,7 +91,7 @@ pub type LoadRowIter<'conn, 'query, C, DB> = /// based on a pure rust connection crate. /// /// **It's important to use prepared statements to implement the following methods:** -/// * [`Connection::load`] +/// * [`LoadConnection::load`] /// * [`Connection::execute_returning_count`] /// /// For performance reasons it may also be meaningful to cache already prepared statements. @@ -196,7 +191,11 @@ pub type LoadRowIter<'conn, 'query, C, DB> = /// implementations to see how you can implement your own connection. pub trait Connection: SimpleConnection + Sized + Send where - Self: for<'a, 'b> ConnectionGatWorkaround<'a, 'b, ::Backend>, + // This trait bound is there so that implementing a new connection is + // gated behind the `i-implement-a-third-party-backend-and-opt-into-breaking-changes` + // feature flag + for<'conn, 'query> Self: + ConnectionGatWorkaround<'conn, 'query, Self::Backend, DefaultLoadingMode>, { /// The backend this type connects to type Backend: Backend; @@ -223,26 +222,22 @@ where /// For both cases the original result value will be returned from this function. /// /// If the transaction fails to commit due to a `SerializationFailure` or a - /// `ReadOnlyTransaction` a rollback will be attempted. In this case a - /// [`Error::CommitTransactionFailed`](crate::result::Error::CommitTransactionFailed) - /// error is returned, which contains details about the original error and - /// the success of the rollback attempt. - /// If the rollback failed the connection should be considered broken + /// `ReadOnlyTransaction` a rollback will be attempted. + /// If the rollback fails, the error will be returned in a + /// [`Error::RollbackErrorOnCommit`](crate::result::Error::RollbackErrorOnCommit), + /// from which you will be able to extract both the original commit error and + /// the rollback error. + /// In addition, the connection will be considered broken /// as it contains a uncommitted unabortable open transaction. Any further /// interaction with the transaction system will result in an returned error - /// in this cases. + /// in this case. /// /// If the closure returns an `Err(_)` and the rollback fails the function - /// will return a [`Error::RollbackError`](crate::result::Error::RollbackError) - /// wrapping the error generated by the rollback operation instead. - /// In this case the connection should be considered broken as it contains - /// an unabortable open transaction. + /// will return that rollback error directly, and the transaction manager will + /// be marked as broken as it contains a uncommitted unabortable open transaction. /// /// If a nested transaction fails to release the corresponding savepoint - /// a rollback will be attempted. In this case a - /// [`Error::CommitTransactionFailed`](crate::result::Error::CommitTransactionFailed) - /// error is returned, which contains the original error and - /// details about the success of the rollback attempt. + /// the error will be returned directly. /// /// # Example /// @@ -353,6 +348,35 @@ where user_result.expect("Transaction did not succeed") } + /// Execute a single SQL statements given by a query and return + /// number of affected rows + #[diesel_derives::__diesel_public_if( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" + )] + fn execute_returning_count(&mut self, source: &T) -> QueryResult + where + T: QueryFragment + QueryId; + + /// Get access to the current transaction state of this connection + /// + /// This function should be used from [`TransactionManager`] to access + /// internally required state. + #[diesel_derives::__diesel_public_if( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" + )] + fn transaction_state( + &mut self, + ) -> &mut >::TransactionStateData; +} + +/// The specific part of a [`Connection`] which actually loads data from the database +/// +/// This is a seperate trait to allow connection implementations to specify +/// different loading modes via the generic paramater. +pub trait LoadConnection: Connection +where + for<'conn, 'query> Self: ConnectionGatWorkaround<'conn, 'query, Self::Backend, B>, +{ /// Executes a given query and returns any requested values /// /// This function executes a given query and returns the @@ -370,30 +394,10 @@ where fn load<'conn, 'query, T>( &'conn mut self, source: T, - ) -> QueryResult> + ) -> QueryResult> where T: Query + QueryFragment + QueryId + 'query, Self::Backend: QueryMetadata; - - /// Execute a single SQL statements given by a query and return - /// number of affected rows - #[diesel_derives::__diesel_public_if( - feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" - )] - fn execute_returning_count(&mut self, source: &T) -> QueryResult - where - T: QueryFragment + QueryId; - - /// Get access to the current transaction state of this connection - /// - /// This function should be used from [`TransactionManager`] to access - /// internally required state. - #[diesel_derives::__diesel_public_if( - feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" - )] - fn transaction_state( - &mut self, - ) -> &mut >::TransactionStateData; } /// A variant of the [`Connection`](trait.Connection.html) trait that is @@ -428,6 +432,17 @@ where } } +/// The default loading mode provided by a [`Connection`]. +/// +/// Checkout the documentation of concrete connection types for details about +/// supported loading modes. +/// +/// All types implementing [`Connection`] should provide at least +/// a single [`LoadConnection`](self::LoadConnection) +/// implementation. +#[derive(Debug, Copy, Clone)] +pub struct DefaultLoadingMode; + impl dyn BoxableConnection { /// Downcast the current connection to a specific connection /// type. @@ -472,6 +487,8 @@ mod private { use crate::backend::Backend; use crate::QueryResult; + use super::DefaultLoadingMode; + /// This trait describes which cursor type is used by a given connection /// implementation. This trait is only useful in combination with [`Connection`]. /// @@ -482,12 +499,12 @@ mod private { doc_cfg, doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")) )] - pub trait ConnectionGatWorkaround<'conn, 'query, DB: Backend> { - /// The cursor type returned by [`Connection::load`] + pub trait ConnectionGatWorkaround<'conn, 'query, DB: Backend, B = DefaultLoadingMode> { + /// The cursor type returned by [`LoadConnection::load`] /// /// Users should handle this as opaque type that implements [`Iterator`] /// - /// [`Connection::load`]: super::Connection::load + /// [`LoadConnection::load`]: super::LoadConnection::load type Cursor: Iterator>; /// The row type used as [`Iterator::Item`] for the iterator implementation /// of [`ConnectionGatWorkaround::Cursor`] diff --git a/diesel/src/connection/transaction_manager.rs b/diesel/src/connection/transaction_manager.rs index e63f7db43374..1e5eb773fe89 100644 --- a/diesel/src/connection/transaction_manager.rs +++ b/diesel/src/connection/transaction_manager.rs @@ -1,6 +1,5 @@ -use crate::connection::commit_error_processor::{CommitErrorOutcome, CommitErrorProcessor}; use crate::connection::Connection; -use crate::result::{DatabaseErrorKind, Error, QueryResult}; +use crate::result::{Error, QueryResult}; use std::borrow::Cow; use std::num::NonZeroU32; @@ -56,10 +55,15 @@ pub trait TransactionManager { Self::commit_transaction(conn)?; Ok(value) } - Err(e) => { - Self::rollback_transaction(conn).map_err(|e| Error::RollbackError(Box::new(e)))?; - Err(e) - } + Err(user_error) => match Self::rollback_transaction(conn) { + Ok(()) => Err(user_error), + Err(Error::BrokenTransactionManager) => { + // In this case we are probably more interested by the + // original error, which likely caused this + Err(user_error) + } + Err(rollback_error) => Err(rollback_error.into()), + }, } } } @@ -89,18 +93,68 @@ impl Default for TransactionManagerStatus { impl TransactionManagerStatus { /// Returns the transaction depth if the transaction manager's status is valid, or returns - /// [`Error::BrokenTransaction`] if the transaction manager is in error. + /// [`Error::BrokenTransactionManager`] if the transaction manager is in error. pub fn transaction_depth(&self) -> QueryResult> { match self { TransactionManagerStatus::Valid(valid_status) => Ok(valid_status.transaction_depth()), - TransactionManagerStatus::InError => Err(Error::BrokenTransaction), + TransactionManagerStatus::InError => Err(Error::BrokenTransactionManager), + } + } + + #[cfg(any( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes", + feature = "postgres", + ))] + #[diesel_derives::__diesel_public_if( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" + )] + /// Whether we may be interested in calling + /// `set_top_level_transaction_requires_rollback_if_not_broken` + /// + /// You should typically not need this outside of a custom backend implementation + pub(crate) fn is_not_broken_and_in_transaction(&self) -> bool { + match self { + TransactionManagerStatus::Valid(valid_status) => valid_status.in_transaction.is_some(), + TransactionManagerStatus::InError => false, + } + } + + #[cfg(any( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes", + feature = "postgres", + feature = "mysql", + test + ))] + #[diesel_derives::__diesel_public_if( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" + )] + /// If in transaction and transaction manager is not broken, registers that the + /// connection can not be used anymore until top-level transaction is rolled back + pub(crate) fn set_top_level_transaction_requires_rollback(&mut self) { + if let TransactionManagerStatus::Valid(ValidTransactionManagerStatus { + in_transaction: + Some(InTransactionStatus { + top_level_transaction_requires_rollback, + .. + }), + }) = self + { + *top_level_transaction_requires_rollback = true; } } + /// Sets the transaction manager status to InError + /// + /// Subsequent attempts to use transaction-related features will result in a + /// [`Error::BrokenTransactionManager`] error + pub fn set_in_error(&mut self) { + *self = TransactionManagerStatus::InError + } + fn transaction_state(&mut self) -> QueryResult<&mut ValidTransactionManagerStatus> { match self { TransactionManagerStatus::Valid(valid_status) => Ok(valid_status), - TransactionManagerStatus::InError => Err(Error::BrokenTransaction), + TransactionManagerStatus::InError => Err(Error::BrokenTransactionManager), } } } @@ -109,8 +163,14 @@ impl TransactionManagerStatus { #[allow(missing_copy_implementations)] #[derive(Debug, Default)] pub struct ValidTransactionManagerStatus { - pub(super) transaction_depth: Option, - pub(crate) previous_error_relevant_for_rollback: Option<(DatabaseErrorKind, String)>, + in_transaction: Option, +} + +#[allow(missing_copy_implementations)] +#[derive(Debug)] +struct InTransactionStatus { + transaction_depth: NonZeroU32, + top_level_transaction_requires_rollback: bool, } impl ValidTransactionManagerStatus { @@ -119,7 +179,7 @@ impl ValidTransactionManagerStatus { /// This value is `None` if no current transaction is running /// otherwise the number of nested transactions is returned. pub fn transaction_depth(&self) -> Option { - self.transaction_depth + self.in_transaction.as_ref().map(|it| it.transaction_depth) } /// Update the transaction depth by adding the value of the `transaction_depth_change` parameter if the `query` is @@ -127,30 +187,38 @@ impl ValidTransactionManagerStatus { pub fn change_transaction_depth( &mut self, transaction_depth_change: TransactionDepthChange, - query: QueryResult<()>, ) -> QueryResult<()> { - if query.is_ok() { - match (&mut self.transaction_depth, transaction_depth_change) { - (Some(depth), TransactionDepthChange::IncreaseDepth) => { - // This is always `Some(_)` - self.transaction_depth = NonZeroU32::new(depth.get().saturating_add(1)); - } - (Some(depth), TransactionDepthChange::DecreaseDepth) => { - // This sets `transaction_depth` to `None` as soon as we reach zero - self.transaction_depth = NonZeroU32::new(depth.get().saturating_sub(1)); - } - (None, TransactionDepthChange::IncreaseDepth) => { - self.transaction_depth = NonZeroU32::new(1); - } - (None, TransactionDepthChange::DecreaseDepth) => { - // We screwed up something somewhere - // we cannot decrease the transaction count if - // we are not inside a transaction - return Err(Error::NotInTransaction); + match (&mut self.in_transaction, transaction_depth_change) { + (Some(in_transaction), TransactionDepthChange::IncreaseDepth) => { + // Can be replaced with saturating_add directly on NonZeroU32 once + // is stable + in_transaction.transaction_depth = + NonZeroU32::new(in_transaction.transaction_depth.get().saturating_add(1)) + .expect("nz + nz is always non-zero"); + Ok(()) + } + (Some(in_transaction), TransactionDepthChange::DecreaseDepth) => { + // This sets `transaction_depth` to `None` as soon as we reach zero + match NonZeroU32::new(in_transaction.transaction_depth.get() - 1) { + Some(depth) => in_transaction.transaction_depth = depth, + None => self.in_transaction = None, } + Ok(()) + } + (None, TransactionDepthChange::IncreaseDepth) => { + self.in_transaction = Some(InTransactionStatus { + transaction_depth: NonZeroU32::new(1).expect("1 is non-zero"), + top_level_transaction_requires_rollback: false, + }); + Ok(()) + } + (None, TransactionDepthChange::DecreaseDepth) => { + // We screwed up something somewhere + // we cannot decrease the transaction count if + // we are not inside a transaction + Err(Error::NotInTransaction) } } - query } } @@ -168,7 +236,7 @@ impl AnsiTransactionManager { conn: &mut Conn, ) -> QueryResult<&mut ValidTransactionManagerStatus> where - Conn: Connection + CommitErrorProcessor, + Conn: Connection, { conn.transaction_state().status.transaction_state() } @@ -180,14 +248,15 @@ impl AnsiTransactionManager { /// Returns an error if already inside of a transaction. pub fn begin_transaction_sql(conn: &mut Conn, sql: &str) -> QueryResult<()> where - Conn: Connection + CommitErrorProcessor, + Conn: Connection, { let state = Self::get_transaction_state(conn)?; match state.transaction_depth() { None => { - let res = conn.batch_execute(sql); - let state = Self::get_transaction_state(conn)?; - state.change_transaction_depth(TransactionDepthChange::IncreaseDepth, res) + conn.batch_execute(sql)?; + Self::get_transaction_state(conn)? + .change_transaction_depth(TransactionDepthChange::IncreaseDepth)?; + Ok(()) } Some(_depth) => Err(Error::AlreadyInTransaction), } @@ -196,7 +265,7 @@ impl AnsiTransactionManager { impl TransactionManager for AnsiTransactionManager where - Conn: Connection + CommitErrorProcessor, + Conn: Connection, { type TransactionStateData = Self; @@ -208,168 +277,141 @@ where Cow::from(format!("SAVEPOINT diesel_savepoint_{}", transaction_depth)) } }; - let result = conn.batch_execute(&*start_transaction_sql); - let state = Self::get_transaction_state(conn)?; - state.change_transaction_depth(TransactionDepthChange::IncreaseDepth, result) + conn.batch_execute(&*start_transaction_sql)?; + Self::get_transaction_state(conn)? + .change_transaction_depth(TransactionDepthChange::IncreaseDepth)?; + + Ok(()) } fn rollback_transaction(conn: &mut Conn) -> QueryResult<()> { let transaction_state = Self::get_transaction_state(conn)?; - let rollback_sql = match transaction_state.transaction_depth() { + + let rollback_sql = match transaction_state.in_transaction { + Some(ref mut in_transaction) => { + match in_transaction.transaction_depth.get() { + 1 => Cow::Borrowed("ROLLBACK"), + depth_gt1 => { + if in_transaction.top_level_transaction_requires_rollback { + // There's no point in *actually* rolling back this one + // because we won't be able to do anything until top-level + // is rolled back. + + // To make it easier on the user (that they don't have to really look + // at actual transaction depth and can just rely on the number of + // times they have called begin/commit/rollback) we don't mark the + // transaction manager as out of the savepoints as soon as we + // realize there is that issue, but instead we still decrement here: + in_transaction.transaction_depth = NonZeroU32::new(depth_gt1 - 1) + .expect("Depth was checked to be > 1"); + return Ok(()); + } else { + Cow::Owned(format!( + "ROLLBACK TO SAVEPOINT diesel_savepoint_{}", + depth_gt1 - 1 + )) + } + } + } + } None => return Err(Error::NotInTransaction), - Some(transaction_depth) if transaction_depth.get() == 1 => Cow::from("ROLLBACK"), - Some(transaction_depth) => Cow::from(format!( - "ROLLBACK TO SAVEPOINT diesel_savepoint_{}", - transaction_depth.get() - 1 - )), }; - if let Some((kind, msg)) = transaction_state - .previous_error_relevant_for_rollback - .take() - { - // we can safely ignore the result of process_commit_error here - // as the only other error than the "rollback error" variants - // is failing to load the transaction state, but that's something - // we have already done above - let _ = process_commit_error( - conn, - crate::result::Error::DatabaseError(kind, Box::new(String::new())), - rollback_sql, - ); - let transaction_state = Self::get_transaction_state(conn)?; - if transaction_state - .transaction_depth - .map(|t| t.get()) - .unwrap_or_default() - > 0 - { - transaction_state.previous_error_relevant_for_rollback = Some((kind, msg.clone())); + match conn.batch_execute(&*rollback_sql) { + Ok(()) => { + Self::get_transaction_state(conn)? + .change_transaction_depth(TransactionDepthChange::DecreaseDepth)?; + Ok(()) + } + Err(rollback_error) => { + let tm_status = Self::transaction_manager_status_mut(conn); + match tm_status { + TransactionManagerStatus::Valid(ValidTransactionManagerStatus { + in_transaction: + Some(InTransactionStatus { + transaction_depth, + top_level_transaction_requires_rollback, + }), + }) if transaction_depth.get() > 1 + && !*top_level_transaction_requires_rollback => + { + // A savepoint failed to rollback - we may still attempt to repair + // the connection by rolling back top-level transaction. + *transaction_depth = NonZeroU32::new(transaction_depth.get() - 1) + .expect("Depth was checked to be > 1"); + *top_level_transaction_requires_rollback = true; + } + _ => tm_status.set_in_error(), + } + Err(rollback_error) } - return Err(crate::result::Error::DatabaseError(kind, Box::new(msg))); } - let result = conn.batch_execute(&*rollback_sql); - let state = Self::get_transaction_state(conn)?; - state.change_transaction_depth(TransactionDepthChange::DecreaseDepth, result) } /// If the transaction fails to commit due to a `SerializationFailure` or a /// `ReadOnlyTransaction` a rollback will be attempted. If the rollback succeeds, /// the original error will be returned, otherwise the error generated by the rollback - /// will be returned. In the second case the connection should be considered broken + /// will be returned. In the second case the connection will be considered broken /// as it contains a uncommitted unabortable open transaction. fn commit_transaction(conn: &mut Conn) -> QueryResult<()> { let transaction_state = Self::get_transaction_state(conn)?; let transaction_depth = transaction_state.transaction_depth(); - let (commit_sql, rollback_sql) = match transaction_depth { + let commit_sql = match transaction_depth { None => return Err(Error::NotInTransaction), - Some(transaction_depth) if transaction_depth.get() == 1 => { - (Cow::from("COMMIT"), Cow::from("ROLLBACK")) - } - Some(transaction_depth) => ( - Cow::from(format!( - "RELEASE SAVEPOINT diesel_savepoint_{}", - transaction_depth.get() - 1 - )), - Cow::from(format!( - "ROLLBACK TO SAVEPOINT diesel_savepoint_{}", - transaction_depth.get() - 1 - )), - ), + Some(transaction_depth) if transaction_depth.get() == 1 => Cow::Borrowed("COMMIT"), + Some(transaction_depth) => Cow::Owned(format!( + "RELEASE SAVEPOINT diesel_savepoint_{}", + transaction_depth.get() - 1 + )), }; - let res = conn.batch_execute(&*commit_sql); - let state = Self::get_transaction_state(conn)?; - match res { + match conn.batch_execute(&*commit_sql) { Ok(()) => { - // commit succeeded, so we just decrease the transaction depth - // and we are done - state.change_transaction_depth(TransactionDepthChange::DecreaseDepth, res) - } - Err(error) => process_commit_error(conn, error, rollback_sql), - } - } - - fn transaction_manager_status_mut(conn: &mut Conn) -> &mut TransactionManagerStatus { - &mut conn.transaction_state().status - } - - fn transaction(conn: &mut Conn, f: F) -> Result - where - F: FnOnce(&mut Conn) -> Result, - E: From, - { - Self::begin_transaction(conn)?; - match f(&mut *conn) { - Ok(value) => { - Self::commit_transaction(conn)?; - Ok(value) + Self::get_transaction_state(conn)? + .change_transaction_depth(TransactionDepthChange::DecreaseDepth)?; + Ok(()) } - Err(e) => { - let transaction_state = Self::get_transaction_state(conn)?; - let is_error_relevant_for_rollback = transaction_state - .previous_error_relevant_for_rollback - .is_some(); - - Self::rollback_transaction(conn).map_err(|e| { - if is_error_relevant_for_rollback { - if let Error::DatabaseError( - crate::result::DatabaseErrorKind::SerializationFailure, - _, - ) = e - { - return e; + Err(commit_error) => { + if let TransactionManagerStatus::Valid(ValidTransactionManagerStatus { + in_transaction: + Some(InTransactionStatus { + ref mut transaction_depth, + top_level_transaction_requires_rollback: true, + }), + }) = conn.transaction_state().status + { + match transaction_depth.get() { + 1 => match Self::rollback_transaction(conn) { + Ok(()) => {} + Err(rollback_error) => { + conn.transaction_state().status.set_in_error(); + return Err(Error::RollbackErrorOnCommit { + rollback_error: Box::new(rollback_error), + commit_error: Box::new(commit_error), + }); + } + }, + depth_gt1 => { + // There's no point in *actually* rolling back this one + // because we won't be able to do anything until top-level + // is rolled back. + + // To make it easier on the user (that they don't have to really look + // at actual transaction depth and can just rely on the number of + // times they have called begin/commit/rollback) we don't mark the + // transaction manager as out of the savepoints as soon as we + // realize there is that issue, but instead we still decrement here: + *transaction_depth = NonZeroU32::new(depth_gt1 - 1) + .expect("Depth was checked to be > 1"); } } - - Error::RollbackError(Box::new(e)) - })?; - Err(e) + } + Err(commit_error) } } } -} -fn process_commit_error( - conn: &mut Conn, - error: Error, - rollback_sql: Cow<'_, str>, -) -> QueryResult<()> -where - Conn: Connection + CommitErrorProcessor, -{ - let commit_error_outcome = conn.process_commit_error(error); - let state = AnsiTransactionManager::get_transaction_state(conn)?; - match commit_error_outcome { - CommitErrorOutcome::RollbackAndThrow(error) => { - // We should try to rollback the transaction here - let rollback_result = conn.batch_execute(&*rollback_sql); - let state = AnsiTransactionManager::get_transaction_state(conn)?; - let rollback_result = state - .change_transaction_depth(TransactionDepthChange::DecreaseDepth, rollback_result); - Err(Error::CommitTransactionFailed { - commit_error: Box::new(error), - rollback_result: Box::new(rollback_result), - }) - } - CommitErrorOutcome::Throw(error) => { - // The error processor indicated that we just - // need to decrease the transaction depth and return the original error - let _ = state.change_transaction_depth(TransactionDepthChange::DecreaseDepth, Ok(())); - Err(Error::CommitTransactionFailed { - commit_error: Box::new(error), - rollback_result: Box::new(Ok(())), - }) - } - CommitErrorOutcome::ThrowAndMarkManagerAsBroken(error) => { - // The connection contains an unrecoverable broken transaction - // so mark the transaction state as broken and return the error - *AnsiTransactionManager::transaction_manager_status_mut(conn) = - TransactionManagerStatus::InError; - Err(Error::CommitTransactionFailed { - commit_error: Box::new(error), - rollback_result: Box::new(Err(Error::BrokenTransaction)), - }) - } + fn transaction_manager_status_mut(conn: &mut Conn) -> &mut TransactionManagerStatus { + &mut conn.transaction_state().status } } @@ -377,28 +419,33 @@ where mod test { // Mock connection. mod mock { - use crate::connection::commit_error_processor::{CommitErrorOutcome, CommitErrorProcessor}; use crate::connection::transaction_manager::AnsiTransactionManager; use crate::connection::{ Connection, ConnectionGatWorkaround, SimpleConnection, TransactionManager, }; - use crate::expression::QueryMetadata; - use crate::query_builder::{AsQuery, QueryFragment, QueryId}; - use crate::result::{Error, QueryResult}; + use crate::result::QueryResult; use crate::test_helpers::TestConnection; + use std::collections::VecDeque; pub(crate) struct MockConnection { - pub(crate) next_result: Option>, - pub(crate) next_batch_execute_result: Option>, - pub(crate) broken: bool, + pub(crate) next_results: VecDeque>, + pub(crate) next_batch_execute_results: VecDeque>, + pub(crate) top_level_requires_rollback_after_next_batch_execute: bool, transaction_state: AnsiTransactionManager, } impl SimpleConnection for MockConnection { fn batch_execute(&mut self, _query: &str) -> QueryResult<()> { - self.next_batch_execute_result - .take() - .expect("No next result") + let res = self + .next_batch_execute_results + .pop_front() + .expect("No next result"); + if self.top_level_requires_rollback_after_next_batch_execute { + self.transaction_state + .status + .set_top_level_transaction_requires_rollback(); + } + res } } @@ -419,16 +466,6 @@ mod test { >>::Row; } - impl CommitErrorProcessor for MockConnection { - fn process_commit_error(&self, error: Error) -> CommitErrorOutcome { - if self.broken { - CommitErrorOutcome::ThrowAndMarkManagerAsBroken(error) - } else { - CommitErrorOutcome::Throw(error) - } - } - } - impl Connection for MockConnection { type Backend = ::Backend; @@ -436,31 +473,19 @@ mod test { fn establish(_database_url: &str) -> crate::ConnectionResult { Ok(Self { - next_result: None, - next_batch_execute_result: None, - broken: false, + next_results: VecDeque::new(), + next_batch_execute_results: VecDeque::new(), + top_level_requires_rollback_after_next_batch_execute: false, transaction_state: AnsiTransactionManager::default(), }) } - fn load<'conn, 'query, T>( - &'conn mut self, - _source: T, - ) -> QueryResult<>::Cursor> - where - T: AsQuery, - T::Query: QueryFragment + QueryId + 'query, - Self::Backend: QueryMetadata, - { - unimplemented!() - } - fn execute_returning_count(&mut self, _source: &T) -> QueryResult where T: crate::query_builder::QueryFragment + crate::query_builder::QueryId, { - self.next_result.take().expect("No next result") + self.next_results.pop_front().expect("No next result") } fn transaction_state( @@ -521,31 +546,47 @@ mod test { let mut conn = mock::MockConnection::establish("mock").expect("Mock connection"); // Set result for BEGIN - conn.next_batch_execute_result = Some(Ok(())); + conn.next_batch_execute_results.push_back(Ok(())); let result = conn.transaction(|conn| { - conn.next_result = Some(Ok(1)); + conn.next_results.push_back(Ok(1)); let query_result = sql_query("SELECT 1").execute(conn); assert!(query_result.is_ok()); - conn.broken = true; // Set result for COMMIT attempt - conn.next_batch_execute_result = Some(Err(Error::DatabaseError( - DatabaseErrorKind::Unknown, - Box::new("whatever".to_string()), - ))); + conn.next_batch_execute_results + .push_back(Err(Error::DatabaseError( + DatabaseErrorKind::Unknown, + Box::new("commit fails".to_string()), + ))); + conn.top_level_requires_rollback_after_next_batch_execute = true; + conn.next_batch_execute_results + .push_back(Err(Error::DatabaseError( + DatabaseErrorKind::Unknown, + Box::new("rollback also fails".to_string()), + ))); Ok(()) }); + assert!( + matches!( + &result, + Err(Error::RollbackErrorOnCommit { + rollback_error, + commit_error + }) if matches!(**commit_error, Error::DatabaseError(DatabaseErrorKind::Unknown, _)) + && matches!(&**rollback_error, + Error::DatabaseError(DatabaseErrorKind::Unknown, msg) + if msg.message() == "rollback also fails" + ) + ), + "Got {:?}", + result + ); assert!(matches!( - result, - Err(Error::CommitTransactionFailed{commit_error, ..}) if matches!(&*commit_error, Error::DatabaseError(DatabaseErrorKind::Unknown, _)) + *AnsiTransactionManager::transaction_manager_status_mut(&mut conn), + TransactionManagerStatus::InError )); - assert!(matches!( - *>::transaction_manager_status_mut( - &mut conn), - TransactionManagerStatus::InError) - ); // Ensure the transaction manager is unusable let result = conn.transaction(|_conn| Ok(())); - assert!(matches!(result, Err(Error::BrokenTransaction))) + assert!(matches!(result, Err(Error::BrokenTransactionManager))) } #[test] @@ -672,6 +713,9 @@ mod test { #[test] #[cfg(feature = "mysql")] + // This function uses a collect with side effects (spawning threads) + // so clippy is wrong here + #[allow(clippy::needless_collect)] fn mysql_transaction_depth_commits_tracked_properly_on_serialization_failure() { use crate::result::DatabaseErrorKind::SerializationFailure; use crate::result::Error::DatabaseError; @@ -775,6 +819,9 @@ mod test { #[test] #[cfg(feature = "mysql")] + // This function uses a collect with side effects (spawning threads) + // so clippy is wrong here + #[allow(clippy::needless_collect)] fn mysql_nested_transaction_depth_commits_tracked_properly_on_serialization_failure() { use crate::result::DatabaseErrorKind::SerializationFailure; use crate::result::Error::DatabaseError; @@ -834,7 +881,7 @@ mod test { let result = conn.transaction(|conn| { assert_eq!(NonZeroU32::new(1), >::transaction_manager_status_mut(conn).transaction_depth().expect("Transaction depth")); - conn.transaction(|conn| { + conn.transaction(|conn| { assert_eq!(NonZeroU32::new(2), >::transaction_manager_status_mut(conn).transaction_depth().expect("Transaction depth")); let _ = serialization_example::table .filter(serialization_example::class.eq(i)) diff --git a/diesel/src/expression/array_comparison.rs b/diesel/src/expression/array_comparison.rs index 9b10a48994e1..9b914d96c9cd 100644 --- a/diesel/src/expression/array_comparison.rs +++ b/diesel/src/expression/array_comparison.rs @@ -23,7 +23,7 @@ use std::marker::PhantomData; /// /// Third party backend can customize the [`QueryFragment`] /// implementation of this query dsl node via -/// [`SqlDialect::ArrayComparision`]. A customized implementation +/// [`SqlDialect::ArrayComparison`]. A customized implementation /// is expected to provide the same sematics as a ANSI SQL /// `IN` expression. /// @@ -43,7 +43,7 @@ pub struct In { /// /// Third party backend can customize the [`QueryFragment`] /// implementation of this query dsl node via -/// [`SqlDialect::ArrayComparision`]. A customized implementation +/// [`SqlDialect::ArrayComparison`]. A customized implementation /// is expected to provide the same sematics as a ANSI SQL /// `NOT IN` expression.0 /// @@ -89,18 +89,17 @@ where impl QueryFragment for In where DB: Backend, - Self: QueryFragment, + Self: QueryFragment, { fn walk_ast<'b>(&'b self, pass: AstPass<'_, 'b, DB>) -> QueryResult<()> { - >::walk_ast(self, pass) + >::walk_ast(self, pass) } } -impl QueryFragment - for In +impl QueryFragment for In where DB: Backend - + SqlDialect, + + SqlDialect, T: QueryFragment, U: QueryFragment + MaybeEmpty, { @@ -120,18 +119,18 @@ where impl QueryFragment for NotIn where DB: Backend, - Self: QueryFragment, + Self: QueryFragment, { fn walk_ast<'b>(&'b self, pass: AstPass<'_, 'b, DB>) -> QueryResult<()> { - >::walk_ast(self, pass) + >::walk_ast(self, pass) } } -impl QueryFragment +impl QueryFragment for NotIn where DB: Backend - + SqlDialect, + + SqlDialect, T: QueryFragment, U: QueryFragment + MaybeEmpty, { @@ -234,7 +233,7 @@ where /// /// Third party backend can customize the [`QueryFragment`] /// implementation of this query dsl node via -/// [`SqlDialect::ArrayComparision`]. The default +/// [`SqlDialect::ArrayComparison`]. The default /// implementation does generate one bind per value /// in the `values` field. /// @@ -290,20 +289,20 @@ where impl QueryFragment for Many where - Self: QueryFragment, + Self: QueryFragment, DB: Backend, { fn walk_ast<'b>(&'b self, pass: AstPass<'_, 'b, DB>) -> QueryResult<()> { - >::walk_ast(self, pass) + >::walk_ast(self, pass) } } -impl QueryFragment +impl QueryFragment for Many where DB: Backend + HasSqlType - + SqlDialect, + + SqlDialect, ST: SingleValue, I: ToSql, { diff --git a/diesel/src/expression/mod.rs b/diesel/src/expression/mod.rs index b2c026631be6..0f6fefc6f953 100644 --- a/diesel/src/expression/mod.rs +++ b/diesel/src/expression/mod.rs @@ -212,9 +212,9 @@ where T: Expression, ST: SqlType + TypedExpressionType, { - type Expression = Self; + type Expression = T; - fn as_expression(self) -> Self { + fn as_expression(self) -> T { self } } diff --git a/diesel/src/expression/nullable.rs b/diesel/src/expression/nullable.rs index 223c636e5538..8912b002a6e9 100644 --- a/diesel/src/expression/nullable.rs +++ b/diesel/src/expression/nullable.rs @@ -48,19 +48,6 @@ impl QueryId for Nullable { const HAS_STATIC_QUERY_ID: bool = T::HAS_STATIC_QUERY_ID; } -impl Selectable for Option -where - DB: Backend, - T: Selectable, - Nullable: Expression, -{ - type SelectExpression = Nullable; - - fn construct_selection() -> Self::SelectExpression { - Nullable::new(T::construct_selection()) - } -} - impl SelectableExpression for Nullable where Self: AppearsOnTable, diff --git a/diesel/src/lib.rs b/diesel/src/lib.rs index fc55327e96d5..fd4a1a784e90 100644 --- a/diesel/src/lib.rs +++ b/diesel/src/lib.rs @@ -130,8 +130,8 @@ //! - `i-implement-a-third-party-backend-and-opt-into-breaking-changes`: This feature opens up some otherwise //! private API, that can be useful to implement a third party [`Backend`](crate::backend::Backend) //! or write a custom [`Connection`](crate::connection::Connection) implementation. **Do not use this feature for -//! any other usecase**. By enabling this feature you explicitly opt out diesel stability gurantees. We explicitly -//! reserve us the right to break API's exported under this feature flag in any upcomming minor version release. +//! any other usecase**. By enabling this feature you explicitly opt out diesel stability guarantees. We explicitly +//! reserve us the right to break API's exported under this feature flag in any upcoming minor version release. //! If you publish a crate depending on this feature flag consider to restrict the supported diesel version to the //! currently released minor version. //! - `serde_json`: This feature flag enables support for (de)serializing json values from the database using @@ -140,17 +140,19 @@ //! types provided by `chrono` //! - `uuid`: This feature flag enables support for (de)serializing uuid values from the database using types //! provided by `uuid` -//! - `network-address`: This feature flag enables support for (de)serializing IP and Macadresse values from the -//! database using types provided by `ipnetwork` -//! - `numeric`: This feature flag enables support for (de)support numeric values from the database using types +//! - `network-address`: This feature flag enables support for (de)serializing +//! IP values from the database using types provided by `ipnetwork`. +//! - `ipnet-address`: This feature flag enables support for (de)serializing IP +//! values from the database using types provided by `ipnet`. +//! - `numeric`: This feature flag enables support for (de)serializing numeric values from the database using types //! provided by `bigdecimal` //! - `r2d2`: This feature flag enables support for the `r2d2` connection pool implementation. //! - `extras`: This feature enables the feature flaged support for any third party crate. This implies the //! following feature flags: `serde_json`, `chrono`, `uuid`, `network-address`, `numeric`, `r2d2` //! - `with-deprecated`: This feature enables items marked as `#[deprecated]`. It is enabled by default. -//! disabling this feature explicitly opts out diesels stability gurantee. +//! disabling this feature explicitly opts out diesels stability guarantee. //! - `without-deprecated`: This feature disables any item marked as `#[deprecated]`. Enabling this feature -//! explicitly opts out the stability gurantee given by diesel. This feature overrides the `with-deprecated`. +//! explicitly opts out the stability guarantee given by diesel. This feature overrides the `with-deprecated`. //! Note that this may also remove items that are not shown as `#[deprecated]` in our documentation, due to //! various bugs in rustdoc. It can be used to check if you depend on any such hidden `#[deprecated]` item. //! @@ -178,7 +180,6 @@ clippy::redundant_field_names, clippy::type_complexity )] -#![cfg_attr(test, allow(clippy::option_map_unwrap_or, clippy::result_unwrap_used))] #![warn( clippy::unwrap_used, clippy::print_stdout, @@ -191,6 +192,7 @@ clippy::items_after_statements, clippy::used_underscore_binding )] +#![cfg_attr(test, allow(clippy::map_unwrap_or, clippy::unwrap_used))] extern crate diesel_derives; @@ -280,6 +282,7 @@ pub mod helper_types { use super::query_dsl::methods::*; use super::query_dsl::*; use super::query_source::{aliasing, joins}; + use crate::connection::DefaultLoadingMode; use crate::query_builder::select_clause::SelectClause; #[doc(inline)] @@ -454,8 +457,8 @@ pub mod helper_types { /// [`Iterator`](std::iter::Iterator) of [`QueryResult`](crate::result::QueryResult) /// /// See [`RunQueryDsl::load_iter`] for more information - pub type LoadIter<'conn, 'query, Q, Conn, U> = - >::Ret; + pub type LoadIter<'conn, 'query, Q, Conn, U, B = DefaultLoadingMode> = + >::Ret; /// Maps `F` to `Alias` /// diff --git a/diesel/src/macros/mod.rs b/diesel/src/macros/mod.rs index a5c80b97359b..910b21f3eca9 100644 --- a/diesel/src/macros/mod.rs +++ b/diesel/src/macros/mod.rs @@ -63,9 +63,9 @@ macro_rules! __diesel_internal_backend_specific_table_impls { type FromClause = $crate::query_builder::Only; type OnClause = <$crate::query_builder::Only as $crate::JoinTo<$table>>::OnClause; - fn join_target(rhs: $crate::query_builder::Only) -> (Self::FromClause, Self::OnClause) { - let (_, on_clause) = $crate::query_builder::Only::::join_target($table); - (rhs, on_clause) + fn join_target(__diesel_internal_rhs: $crate::query_builder::Only) -> (Self::FromClause, Self::OnClause) { + let (_, __diesel_internal_on_clause) = $crate::query_builder::Only::::join_target($table); + (__diesel_internal_rhs, __diesel_internal_on_clause) } } @@ -113,13 +113,15 @@ macro_rules! __diesel_column { $crate::internal::table_macro::StaticQueryFragmentInstance: $crate::query_builder::QueryFragment, { #[allow(non_snake_case)] - fn walk_ast<'b>(&'b self, mut __out: $crate::query_builder::AstPass<'_, 'b, DB>) -> $crate::result::QueryResult<()> + fn walk_ast<'b>(&'b self, mut __diesel_internal_out: $crate::query_builder::AstPass<'_, 'b, DB>) -> $crate::result::QueryResult<()> { - const FROM_CLAUSE: $crate::internal::table_macro::StaticQueryFragmentInstance
= $crate::internal::table_macro::StaticQueryFragmentInstance::new(); + if !__diesel_internal_out.should_skip_from() { + const FROM_CLAUSE: $crate::internal::table_macro::StaticQueryFragmentInstance
= $crate::internal::table_macro::StaticQueryFragmentInstance::new(); - FROM_CLAUSE.walk_ast(__out.reborrow())?; - __out.push_sql("."); - __out.push_identifier($sql_name) + FROM_CLAUSE.walk_ast(__diesel_internal_out.reborrow())?; + __diesel_internal_out.push_sql("."); + } + __diesel_internal_out.push_identifier($sql_name) } } @@ -195,9 +197,9 @@ macro_rules! __diesel_column { { type Output = $crate::dsl::Eq; - fn eq_all(self, rhs: T) -> Self::Output { + fn eq_all(self, __diesel_internal_rhs: T) -> Self::Output { use $crate::expression_methods::ExpressionMethods; - self.eq(rhs) + self.eq(__diesel_internal_rhs) } } @@ -805,8 +807,8 @@ macro_rules! __diesel_table_impl { DB: $crate::backend::Backend,
::Component: $crate::query_builder::QueryFragment { - fn walk_ast<'b>(&'b self, pass: $crate::query_builder::AstPass<'_, 'b, DB>) -> $crate::result::QueryResult<()> { -
::STATIC_COMPONENT.walk_ast(pass) + fn walk_ast<'b>(&'b self, __diesel_internal_pass: $crate::query_builder::AstPass<'_, 'b, DB>) -> $crate::result::QueryResult<()> { +
::STATIC_COMPONENT.walk_ast(__diesel_internal_pass) } } @@ -887,8 +889,8 @@ macro_rules! __diesel_table_impl { { type Out = $crate::query_source::AliasedField; - fn map(column: C, alias: &$crate::query_source::Alias) -> Self::Out { - alias.field(column) + fn map(__diesel_internal_column: C, __diesel_internal_alias: &$crate::query_source::Alias) -> Self::Out { + __diesel_internal_alias.field(__diesel_internal_column) } } @@ -904,9 +906,9 @@ macro_rules! __diesel_table_impl { type FromClause = $crate::internal::table_macro::Join; type OnClause = <$crate::internal::table_macro::Join as $crate::JoinTo
>::OnClause; - fn join_target(rhs: $crate::internal::table_macro::Join) -> (Self::FromClause, Self::OnClause) { - let (_, on_clause) = $crate::internal::table_macro::Join::join_target(table); - (rhs, on_clause) + fn join_target(__diesel_internal_rhs: $crate::internal::table_macro::Join) -> (Self::FromClause, Self::OnClause) { + let (_, __diesel_internal_on_clause) = $crate::internal::table_macro::Join::join_target(table); + (__diesel_internal_rhs, __diesel_internal_on_clause) } } @@ -916,9 +918,9 @@ macro_rules! __diesel_table_impl { type FromClause = $crate::internal::table_macro::JoinOn; type OnClause = <$crate::internal::table_macro::JoinOn as $crate::JoinTo
>::OnClause; - fn join_target(rhs: $crate::internal::table_macro::JoinOn) -> (Self::FromClause, Self::OnClause) { - let (_, on_clause) = $crate::internal::table_macro::JoinOn::join_target(table); - (rhs, on_clause) + fn join_target(__diesel_internal_rhs: $crate::internal::table_macro::JoinOn) -> (Self::FromClause, Self::OnClause) { + let (_, __diesel_internal_on_clause) = $crate::internal::table_macro::JoinOn::join_target(table); + (__diesel_internal_rhs, __diesel_internal_on_clause) } } @@ -929,9 +931,9 @@ macro_rules! __diesel_table_impl { type FromClause = $crate::internal::table_macro::SelectStatement<$crate::internal::table_macro::FromClause, S, D, W, O, L, Of, G>; type OnClause = <$crate::internal::table_macro::SelectStatement<$crate::internal::table_macro::FromClause, S, D, W, O, L, Of, G> as $crate::JoinTo
>::OnClause; - fn join_target(rhs: $crate::internal::table_macro::SelectStatement<$crate::internal::table_macro::FromClause, S, D, W, O, L, Of, G>) -> (Self::FromClause, Self::OnClause) { - let (_, on_clause) = $crate::internal::table_macro::SelectStatement::join_target(table); - (rhs, on_clause) + fn join_target(__diesel_internal_rhs: $crate::internal::table_macro::SelectStatement<$crate::internal::table_macro::FromClause, S, D, W, O, L, Of, G>) -> (Self::FromClause, Self::OnClause) { + let (_, __diesel_internal_on_clause) = $crate::internal::table_macro::SelectStatement::join_target(table); + (__diesel_internal_rhs, __diesel_internal_on_clause) } } @@ -941,9 +943,9 @@ macro_rules! __diesel_table_impl { { type FromClause = $crate::internal::table_macro::BoxedSelectStatement<'a, $crate::internal::table_macro::FromClause, ST, DB>; type OnClause = <$crate::internal::table_macro::BoxedSelectStatement<'a, $crate::internal::table_macro::FromClause, ST, DB> as $crate::JoinTo
>::OnClause; - fn join_target(rhs: $crate::internal::table_macro::BoxedSelectStatement<'a, $crate::internal::table_macro::FromClause, ST, DB>) -> (Self::FromClause, Self::OnClause) { - let (_, on_clause) = $crate::internal::table_macro::BoxedSelectStatement::join_target(table); - (rhs, on_clause) + fn join_target(__diesel_internal_rhs: $crate::internal::table_macro::BoxedSelectStatement<'a, $crate::internal::table_macro::FromClause, ST, DB>) -> (Self::FromClause, Self::OnClause) { + let (_, __diesel_internal_on_clause) = $crate::internal::table_macro::BoxedSelectStatement::join_target(table); + (__diesel_internal_rhs, __diesel_internal_on_clause) } } @@ -954,9 +956,9 @@ macro_rules! __diesel_table_impl { type FromClause = $crate::query_source::Alias; type OnClause = <$crate::query_source::Alias as $crate::JoinTo
>::OnClause; - fn join_target(rhs: $crate::query_source::Alias) -> (Self::FromClause, Self::OnClause) { - let (_, on_clause) = $crate::query_source::Alias::::join_target(table); - (rhs, on_clause) + fn join_target(__diesel_internal_rhs: $crate::query_source::Alias) -> (Self::FromClause, Self::OnClause) { + let (_, __diesel_internal_on_clause) = $crate::query_source::Alias::::join_target(table); + (__diesel_internal_rhs, __diesel_internal_on_clause) } } @@ -1015,13 +1017,17 @@ macro_rules! __diesel_table_impl {
::FromClause: $crate::query_builder::QueryFragment, { #[allow(non_snake_case)] - fn walk_ast<'b>(&'b self, mut __out: $crate::query_builder::AstPass<'_, 'b, DB>) -> $crate::result::QueryResult<()> + fn walk_ast<'b>(&'b self, mut __diesel_internal_out: $crate::query_builder::AstPass<'_, 'b, DB>) -> $crate::result::QueryResult<()> { use $crate::QuerySource; - const FROM_CLAUSE: $crate::internal::table_macro::StaticQueryFragmentInstance
= $crate::internal::table_macro::StaticQueryFragmentInstance::new(); - FROM_CLAUSE.walk_ast(__out.reborrow())?; - __out.push_sql(".*"); + if !__diesel_internal_out.should_skip_from() { + const FROM_CLAUSE: $crate::internal::table_macro::StaticQueryFragmentInstance
= $crate::internal::table_macro::StaticQueryFragmentInstance::new(); + + FROM_CLAUSE.walk_ast(__diesel_internal_out.reborrow())?; + __diesel_internal_out.push_sql("."); + } + __diesel_internal_out.push_sql("*"); Ok(()) } } @@ -1129,9 +1135,10 @@ macro_rules! __diesel_table_generate_static_query_fragment_for_table { /// /// The generated `ON` clause will always join to the primary key of the parent /// table. This macro removes the need to call [`.on`] explicitly, you will -/// still need to invoke [`allow_tables_to_appear_in_same_query!`] for these two tables to -/// be able to use the resulting query, unless you are using `diesel print-schema` -/// which will generate it for you. +/// still need to invoke +/// [`allow_tables_to_appear_in_same_query!`](crate::allow_tables_to_appear_in_same_query) +/// for these two tables to be able to use the resulting query, unless you are +/// using `diesel print-schema` which will generate it for you. /// /// If you are using `diesel print-schema`, an invocation of this macro /// will be generated for every foreign key in your database unless diff --git a/diesel/src/macros/ops.rs b/diesel/src/macros/ops.rs index 6f460644c736..626627ec2b84 100644 --- a/diesel/src/macros/ops.rs +++ b/diesel/src/macros/ops.rs @@ -14,8 +14,8 @@ macro_rules! operator_allowed { { type Output = $crate::internal::table_macro::ops::$op; - fn $fn_name(self, rhs: Rhs) -> Self::Output { - $crate::internal::table_macro::ops::$op::new(self, rhs.as_expression()) + fn $fn_name(self, __diesel_internal_rhs: Rhs) -> Self::Output { + $crate::internal::table_macro::ops::$op::new(self, __diesel_internal_rhs.as_expression()) } } }; diff --git a/diesel/src/mysql/backend.rs b/diesel/src/mysql/backend.rs index 7645cc6c2354..e04537e4efe8 100644 --- a/diesel/src/mysql/backend.rs +++ b/diesel/src/mysql/backend.rs @@ -89,7 +89,7 @@ impl SqlDialect for Mysql { type EmptyFromClauseSyntax = sql_dialect::from_clause_syntax::AnsiSqlFromClauseSyntax; type ExistsSyntax = sql_dialect::exists_syntax::AnsiSqlExistsSyntax; - type ArrayComparision = sql_dialect::array_comparision::AnsiSqlArrayComparison; + type ArrayComparison = sql_dialect::array_comparison::AnsiSqlArrayComparison; } impl DieselReserveSpecialization for Mysql {} diff --git a/diesel/src/mysql/connection/bind.rs b/diesel/src/mysql/connection/bind.rs index 2ca4c2d141e8..7ae8bd54dddd 100644 --- a/diesel/src/mysql/connection/bind.rs +++ b/diesel/src/mysql/connection/bind.rs @@ -846,8 +846,9 @@ mod tests { ).execute(conn) .unwrap(); - let stmt = conn.prepared_query(&crate::sql_query( - "SELECT + let stmt = crate::mysql::connection::prepared_query( + &crate::sql_query( + "SELECT tiny_int, small_int, medium_int, int_col, big_int, unsigned_int, zero_fill_int, numeric_col, decimal_col, float_col, double_col, bit_col, @@ -857,7 +858,10 @@ mod tests { ST_AsText(polygon_col), ST_AsText(multipoint_col), ST_AsText(multilinestring_col), ST_AsText(multipolygon_col), ST_AsText(geometry_collection), json_col FROM all_mysql_types", - )).unwrap(); + ), + &mut conn.statement_cache, + &mut conn.raw_connection, + ).unwrap(); let metadata = stmt.metadata().unwrap(); let mut output_binds = @@ -1289,8 +1293,6 @@ mod tests { #[test] fn check_json_bind() { - let conn = &mut crate::test_helpers::connection(); - table! { json_test { id -> Integer, @@ -1298,6 +1300,8 @@ mod tests { } } + let conn = &mut crate::test_helpers::connection(); + crate::sql_query("DROP TABLE IF EXISTS json_test CASCADE") .execute(conn) .unwrap(); diff --git a/diesel/src/mysql/connection/mod.rs b/diesel/src/mysql/connection/mod.rs index 7e5bc4746690..93f510d95632 100644 --- a/diesel/src/mysql/connection/mod.rs +++ b/diesel/src/mysql/connection/mod.rs @@ -8,9 +8,6 @@ use self::stmt::iterator::StatementIterator; use self::stmt::Statement; use self::url::ConnectionOptions; use super::backend::Mysql; -use crate::connection::commit_error_processor::{ - default_process_commit_error, CommitErrorOutcome, CommitErrorProcessor, -}; use crate::connection::statement_cache::{MaybeCached, StatementCache}; use crate::connection::*; use crate::expression::QueryMetadata; @@ -23,6 +20,81 @@ use crate::RunQueryDsl; #[allow(missing_debug_implementations, missing_copy_implementations)] /// A connection to a MySQL database. Connection URLs should be in the form /// `mysql://[user[:password]@]host/database_name` +/// +/// # Supported loading model implementations +/// +/// * [`DefaultLoadingMode`] +/// +/// As `MysqlConnection` only supports a single loading mode implementation +/// it is **not required** to explicitly specify a loading mode +/// when calling [`RunQueryDsl::load_iter()`] or [`LoadConnection::load`] +/// +/// ## DefaultLoadingMode +/// +/// `MysqlConnection` only supports a single loading mode, which loads +/// values row by row from the result set. +/// +/// ```rust +/// # include!("../../doctest_setup.rs"); +/// # +/// # fn main() { +/// # run_test().unwrap(); +/// # } +/// # +/// # fn run_test() -> QueryResult<()> { +/// # use schema::users; +/// # let connection = &mut establish_connection(); +/// use diesel::connection::DefaultLoadingMode; +/// { // scope to restrict the lifetime of the iterator +/// let iter1 = users::table.load_iter::<(i32, String), DefaultLoadingMode>(connection)?; +/// +/// for r in iter1 { +/// let (id, name) = r?; +/// println!("Id: {} Name: {}", id, name); +/// } +/// } +/// +/// // works without specifying the loading mode +/// let iter2 = users::table.load_iter::<(i32, String), _>(connection)?; +/// +/// for r in iter2 { +/// let (id, name) = r?; +/// println!("Id: {} Name: {}", id, name); +/// } +/// # Ok(()) +/// # } +/// ``` +/// +/// This mode does **not support** creating +/// multiple iterators using the same connection. +/// +/// ```compile_fail +/// # include!("../../doctest_setup.rs"); +/// # +/// # fn main() { +/// # run_test().unwrap(); +/// # } +/// # +/// # fn run_test() -> QueryResult<()> { +/// # use schema::users; +/// # let connection = &mut establish_connection(); +/// use diesel::connection::DefaultLoadingMode; +/// +/// let iter1 = users::table.load_iter::<(i32, String), DefaultLoadingMode>(connection)?; +/// let iter2 = users::table.load_iter::<(i32, String), DefaultLoadingMode>(connection)?; +/// +/// for r in iter1 { +/// let (id, name) = r?; +/// println!("Id: {} Name: {}", id, name); +/// } +/// +/// for r in iter2 { +/// let (id, name) = r?; +/// println!("Id: {} Name: {}", id, name); +/// } +/// # Ok(()) +/// # } +/// ``` pub struct MysqlConnection { raw_connection: RawConnection, transaction_state: AnsiTransactionManager, @@ -43,18 +115,6 @@ impl<'conn, 'query> ConnectionGatWorkaround<'conn, 'query, Mysql> for MysqlConne type Row = self::stmt::iterator::MysqlRow; } -impl CommitErrorProcessor for MysqlConnection { - fn process_commit_error(&self, error: Error) -> CommitErrorOutcome { - let state = match self.transaction_state.status { - TransactionManagerStatus::InError => { - return CommitErrorOutcome::Throw(Error::BrokenTransaction) - } - TransactionManagerStatus::Valid(ref v) => v, - }; - default_process_commit_error(state, error) - } -} - impl Connection for MysqlConnection { type Backend = Mysql; type TransactionManager = AnsiTransactionManager; @@ -83,60 +143,61 @@ impl Connection for MysqlConnection { Ok(conn) } - fn load<'conn, 'query, T>( - &'conn mut self, - source: T, - ) -> QueryResult> - where - T: Query + QueryFragment + QueryId + 'query, - Self::Backend: QueryMetadata, - { - let stmt = self.prepared_query(&source)?; - - let mut metadata = Vec::new(); - Mysql::row_metadata(&mut (), &mut metadata); - - StatementIterator::from_stmt(stmt, &metadata) - } - - #[doc(hidden)] fn execute_returning_count(&mut self, source: &T) -> QueryResult where T: QueryFragment + QueryId, { - let err = { - let stmt = self.prepared_query(source)?; - let res = unsafe { stmt.execute() }; - match res { - Ok(stmt_use) => return Ok(stmt_use.affected_rows()), - Err(e) => e, - } - }; - if let Error::DatabaseError(DatabaseErrorKind::SerializationFailure, msg) = err { - if let AnsiTransactionManager { - status: TransactionManagerStatus::Valid(ref mut valid), - } = self.transaction_state - { - valid.previous_error_relevant_for_rollback = Some(( - DatabaseErrorKind::SerializationFailure, - msg.message().to_owned(), - )) - } - Err(Error::DatabaseError( - DatabaseErrorKind::SerializationFailure, - msg, - )) - } else { - Err(err) - } + update_transaction_manager_status( + prepared_query(&source, &mut self.statement_cache, &mut self.raw_connection).and_then( + |stmt| { + let stmt_use = unsafe { stmt.execute() }?; + Ok(stmt_use.affected_rows()) + }, + ), + &mut self.transaction_state, + ) } - #[doc(hidden)] fn transaction_state(&mut self) -> &mut AnsiTransactionManager { &mut self.transaction_state } } +#[inline(always)] +fn update_transaction_manager_status( + query_result: QueryResult, + transaction_manager: &mut AnsiTransactionManager, +) -> QueryResult { + if let Err(Error::DatabaseError(DatabaseErrorKind::SerializationFailure, _)) = query_result { + transaction_manager + .status + .set_top_level_transaction_requires_rollback() + } + query_result +} + +impl LoadConnection for MysqlConnection { + fn load<'conn, 'query, T>( + &'conn mut self, + source: T, + ) -> QueryResult> + where + T: Query + QueryFragment + QueryId + 'query, + Self::Backend: QueryMetadata, + { + update_transaction_manager_status( + prepared_query(&source, &mut self.statement_cache, &mut self.raw_connection).and_then( + |stmt| { + let mut metadata = Vec::new(); + Mysql::row_metadata(&mut (), &mut metadata); + StatementIterator::from_stmt(stmt, &metadata) + }, + ), + &mut self.transaction_state, + ) + } +} + #[cfg(feature = "r2d2")] impl crate::r2d2::R2D2Connection for MysqlConnection { fn ping(&mut self) -> QueryResult<()> { @@ -144,33 +205,36 @@ impl crate::r2d2::R2D2Connection for MysqlConnection { } fn is_broken(&mut self) -> bool { - self.transaction_state - .status - .transaction_depth() - .map(|d| d.is_none()) - .unwrap_or(true) + match self.transaction_state.status.transaction_depth() { + // all transactions are closed + // so we don't consider this connection broken + Ok(None) => false, + // The transaction manager is in an error state + // or contains an open transaction + // Therefore we consider this connection broken + Err(_) | Ok(Some(_)) => true, + } } } -impl MysqlConnection { - fn prepared_query<'a, T: QueryFragment + QueryId>( - &'a mut self, - source: &'_ T, - ) -> QueryResult> { - let cache = &mut self.statement_cache; - let conn = &mut self.raw_connection; - - let mut stmt = cache.cached_statement(source, &Mysql, &[], |sql, _| conn.prepare(sql))?; - let mut bind_collector = RawBytesBindCollector::new(); - source.collect_binds(&mut bind_collector, &mut (), &Mysql)?; - let binds = bind_collector - .metadata - .into_iter() - .zip(bind_collector.binds); - stmt.bind(binds)?; - Ok(stmt) - } +fn prepared_query<'a, T: QueryFragment + QueryId>( + source: &'_ T, + statement_cache: &'a mut StatementCache, + raw_connection: &'a mut RawConnection, +) -> QueryResult> { + let mut stmt = statement_cache + .cached_statement(source, &Mysql, &[], |sql, _| raw_connection.prepare(sql))?; + let mut bind_collector = RawBytesBindCollector::new(); + source.collect_binds(&mut bind_collector, &mut (), &Mysql)?; + let binds = bind_collector + .metadata + .into_iter() + .zip(bind_collector.binds); + stmt.bind(binds)?; + Ok(stmt) +} +impl MysqlConnection { fn set_config_options(&mut self) -> QueryResult<()> { crate::sql_query("SET sql_mode=(SELECT CONCAT(@@sql_mode, ',PIPES_AS_CONCAT'))") .execute(self)?; diff --git a/diesel/src/mysql/connection/raw.rs b/diesel/src/mysql/connection/raw.rs index 697414840358..ba50ac40b9cc 100644 --- a/diesel/src/mysql/connection/raw.rs +++ b/diesel/src/mysql/connection/raw.rs @@ -59,18 +59,12 @@ impl RawConnection { // Make sure you don't use the fake one! ffi::mysql_real_connect( self.0.as_ptr(), - host.map(CStr::as_ptr).unwrap_or_else(|| ptr::null_mut()), + host.map(CStr::as_ptr).unwrap_or_else(ptr::null), user.as_ptr(), - password - .map(CStr::as_ptr) - .unwrap_or_else(|| ptr::null_mut()), - database - .map(CStr::as_ptr) - .unwrap_or_else(|| ptr::null_mut()), + password.map(CStr::as_ptr).unwrap_or_else(ptr::null), + database.map(CStr::as_ptr).unwrap_or_else(ptr::null), u32::from(port.unwrap_or(0)), - unix_socket - .map(CStr::as_ptr) - .unwrap_or_else(|| ptr::null_mut()), + unix_socket.map(CStr::as_ptr).unwrap_or_else(ptr::null), client_flags.bits().into(), ) }; diff --git a/diesel/src/mysql/connection/stmt/iterator.rs b/diesel/src/mysql/connection/stmt/iterator.rs index 63f7feef39fc..cb307d3d8b97 100644 --- a/diesel/src/mysql/connection/stmt/iterator.rs +++ b/diesel/src/mysql/connection/stmt/iterator.rs @@ -234,6 +234,7 @@ fn fun_with_row_iters() { } } + use crate::connection::LoadConnection; use crate::deserialize::{FromSql, FromSqlRow}; use crate::prelude::*; use crate::row::{Field, Row}; diff --git a/diesel/src/mysql/query_builder/mod.rs b/diesel/src/mysql/query_builder/mod.rs index 37d73cc9275e..75d54c674ee5 100644 --- a/diesel/src/mysql/query_builder/mod.rs +++ b/diesel/src/mysql/query_builder/mod.rs @@ -26,7 +26,7 @@ impl QueryBuilder for MysqlQueryBuilder { fn push_identifier(&mut self, identifier: &str) -> QueryResult<()> { self.push_sql("`"); - self.push_sql(&identifier.replace("`", "``")); + self.push_sql(&identifier.replace('`', "``")); self.push_sql("`"); Ok(()) } diff --git a/diesel/src/mysql/value.rs b/diesel/src/mysql/value.rs index 8053c2c99634..cc0d4cf5335e 100644 --- a/diesel/src/mysql/value.rs +++ b/diesel/src/mysql/value.rs @@ -4,7 +4,7 @@ use crate::deserialize; use std::error::Error; /// Raw mysql value as received from the database -#[derive(Copy, Clone, Debug)] +#[derive(Clone, Debug)] pub struct MysqlValue<'a> { raw: &'a [u8], tpe: MysqlType, diff --git a/diesel/src/pg/backend.rs b/diesel/src/pg/backend.rs index 79f833a898b6..ec56e04a76ec 100644 --- a/diesel/src/pg/backend.rs +++ b/diesel/src/pg/backend.rs @@ -140,7 +140,7 @@ impl SqlDialect for Pg { type EmptyFromClauseSyntax = sql_dialect::from_clause_syntax::AnsiSqlFromClauseSyntax; type ExistsSyntax = sql_dialect::exists_syntax::AnsiSqlExistsSyntax; - type ArrayComparision = PgStyleArrayComparision; + type ArrayComparison = PgStyleArrayComparision; } impl DieselReserveSpecialization for Pg {} diff --git a/diesel/src/pg/connection/cursor.rs b/diesel/src/pg/connection/cursor.rs index bb9a4eccc284..cab624041f74 100644 --- a/diesel/src/pg/connection/cursor.rs +++ b/diesel/src/pg/connection/cursor.rs @@ -1,41 +1,32 @@ -use std::marker::PhantomData; -use std::rc::Rc; - +use super::raw::RawConnection; use super::result::PgResult; use super::row::PgRow; -use super::PgConnection; +use std::rc::Rc; -/// The type returned by various [`Connection`] methods. -/// Acts as an iterator over `T`. #[allow(missing_debug_implementations)] -pub struct Cursor<'a> { +pub struct Cursor { current_row: usize, db_result: Rc, - // We referenze connection here so that - // we could possibly use the connection in future changes - // to cursor - // This may be required to conditionally implement - // loading items using libpqs single row mode - p: PhantomData<&'a mut PgConnection>, } -impl Cursor<'_> { - pub(super) fn new(db_result: PgResult) -> Self { - Cursor { +impl Cursor { + pub(super) fn new(result: PgResult, conn: &mut RawConnection) -> crate::QueryResult { + let next_res = conn.get_next_result()?; + debug_assert!(next_res.is_none()); + Ok(Self { current_row: 0, - db_result: Rc::new(db_result), - p: PhantomData, - } + db_result: Rc::new(result), + }) } } -impl ExactSizeIterator for Cursor<'_> { +impl ExactSizeIterator for Cursor { fn len(&self) -> usize { self.db_result.num_rows() - self.current_row } } -impl Iterator for Cursor<'_> { +impl Iterator for Cursor { type Item = crate::QueryResult; fn next(&mut self) -> Option { @@ -66,112 +57,349 @@ impl Iterator for Cursor<'_> { } } -#[test] -fn fun_with_row_iters() { - crate::table! { - #[allow(unused_parens)] - users(id) { - id -> Integer, - name -> Text, +/// The type returned by various [`Connection`] methods. +/// Acts as an iterator over `T`. +#[allow(missing_debug_implementations)] +pub struct RowByRowCursor<'a> { + first_row: bool, + db_result: Rc, + conn: &'a mut super::ConnectionAndTransactionManager, +} + +impl<'a> RowByRowCursor<'a> { + pub(super) fn new( + db_result: PgResult, + conn: &'a mut super::ConnectionAndTransactionManager, + ) -> Self { + RowByRowCursor { + first_row: true, + db_result: Rc::new(db_result), + conn, + } + } +} + +impl Iterator for RowByRowCursor<'_> { + type Item = crate::QueryResult; + + fn next(&mut self) -> Option { + if !self.first_row { + let get_next_result = super::update_transaction_manager_status( + self.conn.raw_connection.get_next_result(), + self.conn, + ); + match get_next_result { + Ok(Some(res)) => { + // we try to reuse the existing allocation here + if let Some(old_res) = Rc::get_mut(&mut self.db_result) { + *old_res = res; + } else { + self.db_result = Rc::new(res); + } + } + Ok(None) => { + return None; + } + Err(e) => return Some(Err(e)), + } + } + // This contains either 1 (for a row containing data) or 0 (for the last one) rows + if self.db_result.num_rows() > 0 { + debug_assert_eq!(self.db_result.num_rows(), 1); + self.first_row = false; + Some(Ok(self.db_result.clone().get_row(0))) + } else { + None + } + } +} + +impl Drop for RowByRowCursor<'_> { + fn drop(&mut self) { + loop { + let res = super::update_transaction_manager_status( + self.conn.raw_connection.get_next_result(), + self.conn, + ); + if matches!(res, Err(_) | Ok(None)) { + break; + } } } +} + +#[cfg(test)] +mod tests { + use crate::connection::DefaultLoadingMode; + use crate::pg::PgRowByRowLoadingMode; + + #[test] + fn fun_with_row_iters() { + crate::table! { + #[allow(unused_parens)] + users(id) { + id -> Integer, + name -> Text, + } + } - use crate::deserialize::{FromSql, FromSqlRow}; - use crate::pg::Pg; - use crate::prelude::*; - use crate::row::{Field, Row}; - use crate::sql_types; - - let conn = &mut crate::test_helpers::connection(); - - crate::sql_query( - "CREATE TABLE IF NOT EXISTS users(id INTEGER PRIMARY KEY, name TEXT NOT NULL);", - ) - .execute(conn) - .unwrap(); - - crate::insert_into(users::table) - .values(vec![ - (users::id.eq(1), users::name.eq("Sean")), - (users::id.eq(2), users::name.eq("Tess")), - ]) + use crate::connection::LoadConnection; + use crate::deserialize::{FromSql, FromSqlRow}; + use crate::pg::Pg; + use crate::prelude::*; + use crate::row::{Field, Row}; + use crate::sql_types; + + let conn = &mut crate::test_helpers::connection(); + + crate::sql_query( + "CREATE TABLE IF NOT EXISTS users(id INTEGER PRIMARY KEY, name TEXT NOT NULL);", + ) .execute(conn) .unwrap(); - let query = users::table.select((users::id, users::name)); + crate::insert_into(users::table) + .values(vec![ + (users::id.eq(1), users::name.eq("Sean")), + (users::id.eq(2), users::name.eq("Tess")), + ]) + .execute(conn) + .unwrap(); + + let query = users::table.select((users::id, users::name)); + + let expected = vec![(1, String::from("Sean")), (2, String::from("Tess"))]; + + let row_iter = LoadConnection::::load(conn, &query).unwrap(); + for (row, expected) in row_iter.zip(&expected) { + let row = row.unwrap(); - let expected = vec![(1, String::from("Sean")), (2, String::from("Tess"))]; + let deserialized = <(i32, String) as FromSqlRow< + (sql_types::Integer, sql_types::Text), + _, + >>::build_from_row(&row) + .unwrap(); - let row_iter = conn.load(&query).unwrap(); - for (row, expected) in row_iter.zip(&expected) { - let row = row.unwrap(); + assert_eq!(&deserialized, expected); + } + + { + let collected_rows = LoadConnection::::load(conn, &query) + .unwrap() + .collect::>(); - let deserialized = <(i32, String) as FromSqlRow< - (sql_types::Integer, sql_types::Text), - _, - >>::build_from_row(&row) + for (row, expected) in collected_rows.iter().zip(&expected) { + let deserialized = row + .as_ref() + .map(|row| { + <(i32, String) as FromSqlRow< + (sql_types::Integer, sql_types::Text), + _, + >>::build_from_row(row).unwrap() + }) + .unwrap(); + + assert_eq!(&deserialized, expected); + } + } + + let mut row_iter = LoadConnection::::load(conn, &query).unwrap(); + + let first_row = row_iter.next().unwrap().unwrap(); + let first_fields = (first_row.get(0).unwrap(), first_row.get(1).unwrap()); + let first_values = (first_fields.0.value(), first_fields.1.value()); + + let second_row = row_iter.next().unwrap().unwrap(); + let second_fields = (second_row.get(0).unwrap(), second_row.get(1).unwrap()); + let second_values = (second_fields.0.value(), second_fields.1.value()); + + assert!(row_iter.next().is_none()); + + assert_eq!( + >::from_nullable_sql(first_values.0).unwrap(), + expected[0].0 + ); + assert_eq!( + >::from_nullable_sql(first_values.1).unwrap(), + expected[0].1 + ); + + assert_eq!( + >::from_nullable_sql(second_values.0).unwrap(), + expected[1].0 + ); + assert_eq!( + >::from_nullable_sql(second_values.1).unwrap(), + expected[1].1 + ); + + let first_fields = (first_row.get(0).unwrap(), first_row.get(1).unwrap()); + let first_values = (first_fields.0.value(), first_fields.1.value()); + + assert_eq!( + >::from_nullable_sql(first_values.0).unwrap(), + expected[0].0 + ); + assert_eq!( + >::from_nullable_sql(first_values.1).unwrap(), + expected[0].1 + ); + } + + #[test] + fn loading_modes_return_the_same_result() { + use crate::prelude::*; + + crate::table! { + #[allow(unused_parens)] + users(id) { + id -> Integer, + name -> Text, + } + } + + let conn = &mut crate::test_helpers::connection(); + + crate::sql_query( + "CREATE TABLE IF NOT EXISTS users(id INTEGER PRIMARY KEY, name TEXT NOT NULL);", + ) + .execute(conn) .unwrap(); - assert_eq!(&deserialized, expected); + crate::insert_into(users::table) + .values(vec![ + (users::id.eq(1), users::name.eq("Sean")), + (users::id.eq(2), users::name.eq("Tess")), + ]) + .execute(conn) + .unwrap(); + + let users_by_default_mode = users::table + .select(users::name) + .load_iter::(conn) + .unwrap() + .collect::>>() + .unwrap(); + let users_row_by_row = users::table + .select(users::name) + .load_iter::(conn) + .unwrap() + .collect::>>() + .unwrap(); + assert_eq!(users_by_default_mode, users_row_by_row); + assert_eq!(users_by_default_mode, vec!["Sean", "Tess"]); } - { - let collected_rows = conn.load(&query).unwrap().collect::>(); - - for (row, expected) in collected_rows.iter().zip(&expected) { - let deserialized = row - .as_ref() - .map(|row| { - <(i32, String) as FromSqlRow< - (sql_types::Integer, sql_types::Text), - _, - >>::build_from_row(row).unwrap() - }) - .unwrap(); + #[test] + fn fun_with_row_iters_row_by_row() { + crate::table! { + #[allow(unused_parens)] + users(id) { + id -> Integer, + name -> Text, + } + } + + use crate::connection::LoadConnection; + use crate::deserialize::{FromSql, FromSqlRow}; + use crate::pg::Pg; + use crate::prelude::*; + use crate::row::{Field, Row}; + use crate::sql_types; + + let conn = &mut crate::test_helpers::connection(); + + crate::sql_query( + "CREATE TABLE IF NOT EXISTS users(id INTEGER PRIMARY KEY, name TEXT NOT NULL);", + ) + .execute(conn) + .unwrap(); + + crate::insert_into(users::table) + .values(vec![ + (users::id.eq(1), users::name.eq("Sean")), + (users::id.eq(2), users::name.eq("Tess")), + ]) + .execute(conn) + .unwrap(); + + let query = users::table.select((users::id, users::name)); + + let expected = vec![(1, String::from("Sean")), (2, String::from("Tess"))]; + + let row_iter = LoadConnection::::load(conn, &query).unwrap(); + for (row, expected) in row_iter.zip(&expected) { + let row = row.unwrap(); + + let deserialized = <(i32, String) as FromSqlRow< + (sql_types::Integer, sql_types::Text), + _, + >>::build_from_row(&row) + .unwrap(); assert_eq!(&deserialized, expected); } - } - let mut row_iter = conn.load(&query).unwrap(); - - let first_row = row_iter.next().unwrap().unwrap(); - let first_fields = (first_row.get(0).unwrap(), first_row.get(1).unwrap()); - let first_values = (first_fields.0.value(), first_fields.1.value()); - - let second_row = row_iter.next().unwrap().unwrap(); - let second_fields = (second_row.get(0).unwrap(), second_row.get(1).unwrap()); - let second_values = (second_fields.0.value(), second_fields.1.value()); - - assert!(row_iter.next().is_none()); - - assert_eq!( - >::from_nullable_sql(first_values.0).unwrap(), - expected[0].0 - ); - assert_eq!( - >::from_nullable_sql(first_values.1).unwrap(), - expected[0].1 - ); - - assert_eq!( - >::from_nullable_sql(second_values.0).unwrap(), - expected[1].0 - ); - assert_eq!( - >::from_nullable_sql(second_values.1).unwrap(), - expected[1].1 - ); - - let first_fields = (first_row.get(0).unwrap(), first_row.get(1).unwrap()); - let first_values = (first_fields.0.value(), first_fields.1.value()); - - assert_eq!( - >::from_nullable_sql(first_values.0).unwrap(), - expected[0].0 - ); - assert_eq!( - >::from_nullable_sql(first_values.1).unwrap(), - expected[0].1 - ); + { + let collected_rows = LoadConnection::::load(conn, &query) + .unwrap() + .collect::>(); + + for (row, expected) in collected_rows.iter().zip(&expected) { + let deserialized = row + .as_ref() + .map(|row| { + <(i32, String) as FromSqlRow< + (sql_types::Integer, sql_types::Text), + _, + >>::build_from_row(row).unwrap() + }) + .unwrap(); + + assert_eq!(&deserialized, expected); + } + } + + let mut row_iter = LoadConnection::::load(conn, &query).unwrap(); + + let first_row = row_iter.next().unwrap().unwrap(); + let first_fields = (first_row.get(0).unwrap(), first_row.get(1).unwrap()); + let first_values = (first_fields.0.value(), first_fields.1.value()); + + let second_row = row_iter.next().unwrap().unwrap(); + let second_fields = (second_row.get(0).unwrap(), second_row.get(1).unwrap()); + let second_values = (second_fields.0.value(), second_fields.1.value()); + + assert!(row_iter.next().is_none()); + + assert_eq!( + >::from_nullable_sql(first_values.0).unwrap(), + expected[0].0 + ); + assert_eq!( + >::from_nullable_sql(first_values.1).unwrap(), + expected[0].1 + ); + + assert_eq!( + >::from_nullable_sql(second_values.0).unwrap(), + expected[1].0 + ); + assert_eq!( + >::from_nullable_sql(second_values.1).unwrap(), + expected[1].1 + ); + + let first_fields = (first_row.get(0).unwrap(), first_row.get(1).unwrap()); + let first_values = (first_fields.0.value(), first_fields.1.value()); + + assert_eq!( + >::from_nullable_sql(first_values.0).unwrap(), + expected[0].0 + ); + assert_eq!( + >::from_nullable_sql(first_values.1).unwrap(), + expected[0].1 + ); + } } diff --git a/diesel/src/pg/connection/mod.rs b/diesel/src/pg/connection/mod.rs index c1a6cf3c8b44..90a4fc57ba89 100644 --- a/diesel/src/pg/connection/mod.rs +++ b/diesel/src/pg/connection/mod.rs @@ -4,14 +4,11 @@ mod result; mod row; mod stmt; -use std::ffi::CString; -use std::os::raw as libc; - use self::cursor::*; +use self::private::ConnectionAndTransactionManager; use self::raw::{PgTransactionStatus, RawConnection}; use self::result::PgResult; use self::stmt::Statement; -use crate::connection::commit_error_processor::{CommitErrorOutcome, CommitErrorProcessor}; use crate::connection::statement_cache::{MaybeCached, StatementCache}; use crate::connection::*; use crate::expression::QueryMetadata; @@ -22,17 +19,104 @@ use crate::query_builder::*; use crate::result::ConnectionError::CouldntSetupConfiguration; use crate::result::*; use crate::RunQueryDsl; +use std::ffi::CString; +use std::os::raw as libc; /// The connection string expected by `PgConnection::establish` /// should be a PostgreSQL connection string, as documented at /// +/// +/// # Supported loading model implementations +/// +/// * [`DefaultLoadingMode`] +/// * [`PgRowByRowLoadingMode`] +/// +/// If you are unsure which loading mode is the correct one for your application, +/// you likely want to use the `DefaultLoadingMode` as that one offers +/// generally better performance. +/// +/// Due to the fact that `PgConnection` supports multiple loading modes +/// it is **required** to always specify the used loading mode +/// when calling [`RunQueryDsl::load_iter`] +/// +/// ## `DefaultLoadingMode` +/// +/// By using this mode `PgConnection` defaults to loading all response values at **once** +/// and only performs deserialization afterward for the `DefaultLoadingMode`. +/// Generally this mode will be more performant as it. +/// +/// This loading mode allows users to perform hold more than one iterator at once using +/// the same connection: +/// ```rust +/// # include!("../../doctest_setup.rs"); +/// # +/// # fn main() { +/// # run_test().unwrap(); +/// # } +/// # +/// # fn run_test() -> QueryResult<()> { +/// # use schema::users; +/// # let connection = &mut establish_connection(); +/// use diesel::connection::DefaultLoadingMode; +/// +/// let iter1 = users::table.load_iter::<(i32, String), DefaultLoadingMode>(connection)?; +/// let iter2 = users::table.load_iter::<(i32, String), DefaultLoadingMode>(connection)?; +/// +/// for r in iter1 { +/// let (id, name) = r?; +/// println!("Id: {} Name: {}", id, name); +/// } +/// +/// for r in iter2 { +/// let (id, name) = r?; +/// println!("Id: {} Name: {}", id, name); +/// } +/// # Ok(()) +/// # } +/// ``` +/// +/// ## `PgRowByRowLoadingMode` +/// +/// By using this mode `PgConnection` defaults to loading each row of the result set +/// sepreatly. This might be desired for huge result sets. +/// +/// This loading mode **prevents** creating more than one iterator at once using +/// the same connection. The following code is **not** allowed: +/// +/// ```compile_fail +/// # include!("../../doctest_setup.rs"); +/// # +/// # fn main() { +/// # run_test().unwrap(); +/// # } +/// # +/// # fn run_test() -> QueryResult<()> { +/// # use schema::users; +/// # let connection = &mut establish_connection(); +/// use diesel::pg::PgRowByRowLoadingMode; +/// +/// let iter1 = users::table.load_iter::<(i32, String), PgRowByRowLoadingMode>(connection)?; +/// // creating a second iterator generates an compiler error +/// let iter2 = users::table.load_iter::<(i32, String), PgRowByRowLoadingMode>(connection)?; +/// +/// for r in iter1 { +/// let (id, name) = r?; +/// println!("Id: {} Name: {}", id, name); +/// } +/// +/// for r in iter2 { +/// let (id, name) = r?; +/// println!("Id: {} Name: {}", id, name); +/// } +/// # Ok(()) +/// # } +/// ``` #[allow(missing_debug_implementations)] #[cfg(feature = "postgres")] pub struct PgConnection { - raw_connection: RawConnection, - transaction_state: AnsiTransactionManager, statement_cache: StatementCache, metadata_cache: PgMetadataCache, + connection_and_transaction_manager: ConnectionAndTransactionManager, } unsafe impl Send for PgConnection {} @@ -40,76 +124,42 @@ unsafe impl Send for PgConnection {} impl SimpleConnection for PgConnection { fn batch_execute(&mut self, query: &str) -> QueryResult<()> { let query = CString::new(query)?; - let inner_result = unsafe { self.raw_connection.exec(query.as_ptr()) }; - PgResult::new(inner_result?, &self.raw_connection)?; + let inner_result = unsafe { + self.connection_and_transaction_manager + .raw_connection + .exec(query.as_ptr()) + }; + update_transaction_manager_status( + inner_result.and_then(|raw_result| { + PgResult::new( + raw_result, + &self.connection_and_transaction_manager.raw_connection, + ) + }), + &mut self.connection_and_transaction_manager, + )?; Ok(()) } } -impl<'conn, 'query> ConnectionGatWorkaround<'conn, 'query, Pg> for PgConnection { - type Cursor = Cursor<'conn>; +/// A [`PgConnection`] specific loading mode to load rows one by one +/// +/// See the documentation of [`PgConnection`] for details +#[derive(Debug, Copy, Clone)] +pub struct PgRowByRowLoadingMode; + +impl<'conn, 'query> ConnectionGatWorkaround<'conn, 'query, Pg, DefaultLoadingMode> + for PgConnection +{ + type Cursor = Cursor; type Row = self::row::PgRow; } -impl CommitErrorProcessor for PgConnection { - fn process_commit_error(&self, error: Error) -> CommitErrorOutcome { - let transaction_depth = match self.transaction_state.status.transaction_depth() { - Ok(d) => d, - Err(e) => return CommitErrorOutcome::Throw(e), - }; - let transaction_status = self.raw_connection.transaction_status(); - if transaction_status == PgTransactionStatus::Unknown { - return CommitErrorOutcome::ThrowAndMarkManagerAsBroken(error); - } - if matches!( - error, - Error::DatabaseError(DatabaseErrorKind::ClosedConnection, _) - ) { - return CommitErrorOutcome::Throw(error); - } - if let Some(transaction_depth) = transaction_depth { - match error { - Error::DatabaseError(DatabaseErrorKind::ReadOnlyTransaction, _) - | Error::DatabaseError(DatabaseErrorKind::SerializationFailure, _) - if transaction_depth.get() == 1 => - { - CommitErrorOutcome::RollbackAndThrow(error) - } - Error::DatabaseError(DatabaseErrorKind::Unknown, _) - if transaction_status == PgTransactionStatus::InError - && transaction_depth.get() > 1 => - { - CommitErrorOutcome::RollbackAndThrow(error) - } - Error::AlreadyInTransaction - | Error::DatabaseError(DatabaseErrorKind::CheckViolation, _) - | Error::DatabaseError(DatabaseErrorKind::ClosedConnection, _) - | Error::DatabaseError(DatabaseErrorKind::ForeignKeyViolation, _) - | Error::DatabaseError(DatabaseErrorKind::NotNullViolation, _) - | Error::DatabaseError(DatabaseErrorKind::UnableToSendCommand, _) - | Error::DatabaseError(DatabaseErrorKind::UniqueViolation, _) - | Error::DatabaseError(DatabaseErrorKind::Unknown, _) - | Error::DatabaseError(DatabaseErrorKind::ReadOnlyTransaction, _) - | Error::DatabaseError(DatabaseErrorKind::SerializationFailure, _) - | Error::DeserializationError(_) - | Error::InvalidCString(_) - | Error::NotFound - | Error::QueryBuilderError(_) - | Error::RollbackError(_) - | Error::NotInTransaction - | Error::RollbackTransaction - | Error::SerializationError(_) - | Error::BrokenTransaction - | Error::CommitTransactionFailed { .. } => CommitErrorOutcome::Throw(error), - } - } else { - unreachable!( - "Calling commit_error_processor outside of a transaction is implementation error.\ - If you ever see this error message outside implementing a custom transaction manager\ - please open a new issue at diesels issue tracker." - ) - } - } +impl<'conn, 'query> ConnectionGatWorkaround<'conn, 'query, Pg, PgRowByRowLoadingMode> + for PgConnection +{ + type Cursor = RowByRowCursor<'conn>; + type Row = self::row::PgRow; } impl Connection for PgConnection { @@ -119,8 +169,10 @@ impl Connection for PgConnection { fn establish(database_url: &str) -> ConnectionResult { RawConnection::establish(database_url).and_then(|raw_conn| { let mut conn = PgConnection { - raw_connection: raw_conn, - transaction_state: AnsiTransactionManager::default(), + connection_and_transaction_manager: ConnectionAndTransactionManager { + raw_connection: raw_conn, + transaction_state: AnsiTransactionManager::default(), + }, statement_cache: StatementCache::new(), metadata_cache: PgMetadataCache::new(), }; @@ -129,38 +181,50 @@ impl Connection for PgConnection { Ok(conn) }) } - - fn load<'conn, 'query, T>( - &'conn mut self, - source: T, - ) -> QueryResult> - where - T: Query + QueryFragment + QueryId + 'query, - Self::Backend: QueryMetadata, - { - self.with_prepared_query(&source, |stmt, params, conn| { - let result = stmt.execute(conn, ¶ms)?; - let cursor = Cursor::new(result); - - Ok(cursor) - }) - } - - #[doc(hidden)] fn execute_returning_count(&mut self, source: &T) -> QueryResult where T: QueryFragment + QueryId, { - self.with_prepared_query(source, |query, params, conn| { - query.execute(conn, ¶ms).map(|r| r.rows_affected()) - }) + update_transaction_manager_status( + self.with_prepared_query(source, |query, params, conn| { + let res = query + .execute(&mut conn.raw_connection, ¶ms, false) + .map(|r| r.rows_affected()); + // according to https://www.postgresql.org/docs/current/libpq-async.html + // `PQgetResult` needs to be called till a null pointer is returned + while conn.raw_connection.get_next_result()?.is_some() {} + res + }), + &mut self.connection_and_transaction_manager, + ) } fn transaction_state(&mut self) -> &mut AnsiTransactionManager where Self: Sized, { - &mut self.transaction_state + &mut self.connection_and_transaction_manager.transaction_state + } +} + +impl LoadConnection for PgConnection +where + Self: self::private::PgLoadingMode, +{ + fn load<'conn, 'query, T>( + &'conn mut self, + source: T, + ) -> QueryResult> + where + T: Query + QueryFragment + QueryId + 'query, + Self::Backend: QueryMetadata, + { + self.with_prepared_query(&source, |stmt, params, conn| { + use self::private::PgLoadingMode; + let result = stmt.execute(&mut conn.raw_connection, ¶ms, Self::USE_ROW_BY_ROW_MODE); + let result = update_transaction_manager_status(result, conn)?; + Self::get_cursor(conn, result) + }) } } @@ -170,6 +234,45 @@ impl GetPgMetadataCache for PgConnection { } } +#[inline(always)] +fn update_transaction_manager_status( + query_result: QueryResult, + conn: &mut ConnectionAndTransactionManager, +) -> QueryResult { + if let Err(Error::DatabaseError { .. }) = query_result { + /// avoid monomorphizing for every result type - this part will not be inlined + fn non_generic_inner(conn: &mut ConnectionAndTransactionManager) { + let raw_conn: &mut RawConnection = &mut conn.raw_connection; + let tm: &mut AnsiTransactionManager = &mut conn.transaction_state; + if tm.status.is_not_broken_and_in_transaction() { + // libpq keeps track of the transaction status internally, and that is accessible + // via `transaction_status`. We can use that to update the AnsiTransactionManager + // status + match raw_conn.transaction_status() { + PgTransactionStatus::InError => { + tm.status.set_top_level_transaction_requires_rollback() + } + PgTransactionStatus::Unknown => tm.status.set_in_error(), + PgTransactionStatus::Idle => { + // This may repair the transaction manager + tm.status = TransactionManagerStatus::Valid(Default::default()) + } + PgTransactionStatus::InTransaction => { + let transaction_status = &mut tm.status; + if !matches!(transaction_status, TransactionManagerStatus::Valid(valid_tm) if valid_tm.transaction_depth().is_some()) + { + transaction_status.set_in_error() + } + } + PgTransactionStatus::Active => {} + } + } + } + non_generic_inner(conn) + } + query_result +} + #[cfg(feature = "r2d2")] impl crate::r2d2::R2D2Connection for PgConnection { fn ping(&mut self) -> QueryResult<()> { @@ -177,11 +280,20 @@ impl crate::r2d2::R2D2Connection for PgConnection { } fn is_broken(&mut self) -> bool { - self.transaction_state + match self + .connection_and_transaction_manager + .transaction_state .status .transaction_depth() - .map(|d| d.is_none()) - .unwrap_or(true) + { + // all transactions are closed + // so we don't consider this connection broken + Ok(None) => false, + // The transaction manager is in an error state + // or contains an open transaction + // Therefore we consider this connection broken + Err(_) | Ok(Some(_)) => true, + } } } @@ -219,7 +331,7 @@ impl PgConnection { f: impl FnOnce( MaybeCached<'_, Statement>, Vec>>, - &'conn mut RawConnection, + &'conn mut ConnectionAndTransactionManager, ) -> QueryResult, ) -> QueryResult { let mut bind_collector = RawBytesBindCollector::::new(); @@ -229,23 +341,24 @@ impl PgConnection { let cache_len = self.statement_cache.len(); let cache = &mut self.statement_cache; - let raw_conn = &mut self.raw_connection; + let conn = &mut self.connection_and_transaction_manager.raw_connection; let query = cache.cached_statement(source, &Pg, &metadata, |sql, _| { let query_name = if source.is_safe_to_cache_prepared(&Pg)? { Some(format!("__diesel_stmt_{}", cache_len)) } else { None }; - Statement::prepare(raw_conn, sql, query_name.as_deref(), &metadata) + Statement::prepare(conn, sql, query_name.as_deref(), &metadata) }); - f(query?, binds, raw_conn) + f(query?, binds, &mut self.connection_and_transaction_manager) } fn set_config_options(&mut self) -> QueryResult<()> { crate::sql_query("SET TIME ZONE 'UTC'").execute(self)?; crate::sql_query("SET CLIENT_ENCODING TO 'UTF8'").execute(self)?; - self.raw_connection + self.connection_and_transaction_manager + .raw_connection .set_notice_processor(noop_notice_processor); Ok(()) } @@ -253,6 +366,54 @@ impl PgConnection { extern "C" fn noop_notice_processor(_: *mut libc::c_void, _message: *const libc::c_char) {} +mod private { + use super::*; + + #[allow(missing_debug_implementations)] + pub struct ConnectionAndTransactionManager { + pub(super) raw_connection: RawConnection, + pub(super) transaction_state: AnsiTransactionManager, + } + + pub trait PgLoadingMode + where + for<'conn, 'query> Self: ConnectionGatWorkaround<'conn, 'query, Pg, B>, + { + const USE_ROW_BY_ROW_MODE: bool; + + fn get_cursor<'conn, 'query>( + raw_connection: &'conn mut ConnectionAndTransactionManager, + result: PgResult, + ) -> QueryResult<>::Cursor>; + } + + impl PgLoadingMode for PgConnection { + const USE_ROW_BY_ROW_MODE: bool = false; + + fn get_cursor<'conn, 'query>( + conn: &'conn mut ConnectionAndTransactionManager, + result: PgResult, + ) -> QueryResult< + >::Cursor, + > { + update_transaction_manager_status(Cursor::new(result, &mut conn.raw_connection), conn) + } + } + + impl PgLoadingMode for PgConnection { + const USE_ROW_BY_ROW_MODE: bool = true; + + fn get_cursor<'conn, 'query>( + raw_connection: &'conn mut ConnectionAndTransactionManager, + result: PgResult, + ) -> QueryResult< + >::Cursor, + > { + Ok(RowByRowCursor::new(result, raw_connection)) + } + } +} + #[cfg(test)] mod tests { extern crate dotenvy; @@ -270,12 +431,8 @@ mod tests { let query = crate::sql_query("SELECT not_existent FROM also_not_there;").execute(connection); - if let Err(err) = query { - if let DatabaseError(_, string) = err { - assert_eq!(Some(26), string.statement_position()); - } else { - unreachable!(); - } + if let Err(DatabaseError(_, string)) = query { + assert_eq!(Some(26), string.statement_position()); } else { unreachable!(); } @@ -364,14 +521,14 @@ mod tests { let insert = query .insert_into(users::table) .into_columns((users::id, users::name)); - assert_eq!(true, insert.execute(connection).is_ok()); + assert!(insert.execute(connection).is_ok()); assert_eq!(1, connection.statement_cache.len()); let query = users::table.filter(users::id.eq(42)).into_boxed(); let insert = query .insert_into(users::table) .into_columns((users::id, users::name)); - assert_eq!(true, insert.execute(connection).is_ok()); + assert!(insert.execute(connection).is_ok()); assert_eq!(2, connection.statement_cache.len()); } @@ -389,7 +546,7 @@ mod tests { let insert = crate::insert_into(users::table).values((users::id.eq(42), users::name.eq("Foo"))); - assert_eq!(true, insert.execute(connection).is_ok()); + assert!(insert.execute(connection).is_ok()); assert_eq!(1, connection.statement_cache.len()); } @@ -407,7 +564,7 @@ mod tests { let insert = crate::insert_into(users::table) .values(vec![(users::id.eq(42), users::name.eq("Foo"))]); - assert_eq!(true, insert.execute(connection).is_ok()); + assert!(insert.execute(connection).is_ok()); assert_eq!(0, connection.statement_cache.len()); } @@ -425,7 +582,7 @@ mod tests { let insert = crate::insert_into(users::table).values([(users::id.eq(42), users::name.eq("Foo"))]); - assert_eq!(true, insert.execute(connection).is_ok()); + assert!(insert.execute(connection).is_ok()); assert_eq!(1, connection.statement_cache.len()); } @@ -506,7 +663,7 @@ mod tests { assert!(query_result.is_err()); assert_eq!( PgTransactionStatus::InError, - conn.raw_connection.transaction_status() + conn.connection_and_transaction_manager.raw_connection.transaction_status() ); query_result }); @@ -518,7 +675,9 @@ mod tests { ); assert_eq!( PgTransactionStatus::Idle, - conn.raw_connection.transaction_status() + conn.connection_and_transaction_manager + .raw_connection + .transaction_status() ); } @@ -564,14 +723,16 @@ mod tests { assert!(query_result.is_ok()); assert_eq!( PgTransactionStatus::InTransaction, - conn.raw_connection.transaction_status() + conn.connection_and_transaction_manager.raw_connection.transaction_status() ); query_result }); assert!(result.is_ok()); assert_eq!( PgTransactionStatus::Idle, - conn.raw_connection.transaction_status() + conn.connection_and_transaction_manager + .raw_connection + .transaction_status() ); assert_eq!( None, @@ -582,10 +743,13 @@ mod tests { } #[test] + // This function uses collect with an side effect (spawning threads) + // so this is a false positive from clippy + #[allow(clippy::needless_collect)] fn postgres_transaction_depth_is_tracked_properly_on_serialization_failure() { use crate::pg::connection::raw::PgTransactionStatus; use crate::result::DatabaseErrorKind::SerializationFailure; - use crate::result::Error::{self, DatabaseError}; + use crate::result::Error::DatabaseError; use crate::*; use std::sync::{Arc, Barrier}; use std::thread; @@ -655,7 +819,7 @@ mod tests { }); assert_eq!( PgTransactionStatus::Idle, - conn.raw_connection.transaction_status() + conn.connection_and_transaction_manager.raw_connection.transaction_status() ); assert_eq!(None, >::transaction_manager_status_mut(conn).transaction_depth().expect("Transaction depth")); @@ -673,26 +837,26 @@ mod tests { assert!(matches!(results[0], Ok(_)), "Got {:?} instead", results); assert!( - matches!( - &results[1], - Err(Error::CommitTransactionFailed { - ref commit_error, .. - }) if matches!(&**commit_error, DatabaseError(SerializationFailure, _)) - ), + matches!(&results[1], Err(DatabaseError(SerializationFailure, _))), "Got {:?} instead", results ); assert_eq!( PgTransactionStatus::Idle, - conn.raw_connection.transaction_status() + conn.connection_and_transaction_manager + .raw_connection + .transaction_status() ); } #[test] + // This function uses collect with an side effect (spawning threads) + // so this is a false positive from clippy + #[allow(clippy::needless_collect)] fn postgres_transaction_depth_is_tracked_properly_on_nested_serialization_failure() { use crate::pg::connection::raw::PgTransactionStatus; use crate::result::DatabaseErrorKind::SerializationFailure; - use crate::result::Error::{self, DatabaseError}; + use crate::result::Error::DatabaseError; use crate::*; use std::sync::{Arc, Barrier}; use std::thread; @@ -765,13 +929,13 @@ mod tests { assert_eq!(NonZeroU32::new(1), >::transaction_manager_status_mut(conn).transaction_depth().expect("Transaction depth")); assert_eq!( PgTransactionStatus::InTransaction, - conn.raw_connection.transaction_status() + conn.connection_and_transaction_manager.raw_connection.transaction_status() ); r }); assert_eq!( PgTransactionStatus::Idle, - conn.raw_connection.transaction_status() + conn.connection_and_transaction_manager.raw_connection.transaction_status() ); assert_eq!(None, >::transaction_manager_status_mut(conn).transaction_depth().expect("Transaction depth")); @@ -789,18 +953,15 @@ mod tests { assert!(matches!(results[0], Ok(_)), "Got {:?} instead", results); assert!( - matches!( - &results[1], - Err(Error::CommitTransactionFailed { - ref commit_error, .. - }) if matches!(&**commit_error, DatabaseError(SerializationFailure, _)) - ), + matches!(&results[1], Err(DatabaseError(SerializationFailure, _))), "Got {:?} instead", results ); assert_eq!( PgTransactionStatus::Idle, - conn.raw_connection.transaction_status() + conn.connection_and_transaction_manager + .raw_connection + .transaction_status() ); } @@ -835,7 +996,7 @@ mod tests { assert!(result.is_ok()); assert_eq!( PgTransactionStatus::InTransaction, - conn.raw_connection.transaction_status() + conn.connection_and_transaction_manager.raw_connection.transaction_status() ); Ok(()) }); @@ -847,7 +1008,9 @@ mod tests { ); assert_eq!( PgTransactionStatus::Idle, - conn.raw_connection.transaction_status() + conn.connection_and_transaction_manager + .raw_connection + .transaction_status() ); assert!(result.is_err()); } @@ -909,7 +1072,7 @@ mod tests { assert!(result.is_ok()); assert_eq!( PgTransactionStatus::InTransaction, - conn.raw_connection.transaction_status() + conn.connection_and_transaction_manager.raw_connection.transaction_status() ); Ok(()) }); @@ -921,7 +1084,9 @@ mod tests { ); assert_eq!( PgTransactionStatus::Idle, - conn.raw_connection.transaction_status() + conn.connection_and_transaction_manager + .raw_connection + .transaction_status() ); assert!(result.is_err()); } @@ -990,7 +1155,7 @@ mod tests { assert!(inner_result.is_err()); assert_eq!( PgTransactionStatus::InTransaction, - conn.raw_connection.transaction_status() + conn.connection_and_transaction_manager.raw_connection.transaction_status() ); Ok(()) }); @@ -1002,7 +1167,9 @@ mod tests { ); assert_eq!( PgTransactionStatus::Idle, - conn.raw_connection.transaction_status() + conn.connection_and_transaction_manager + .raw_connection + .transaction_status() ); assert!(result.is_ok(), "Expected success, got {:?}", result); } @@ -1046,7 +1213,7 @@ mod tests { assert!(inner_result.is_err()); assert_eq!( PgTransactionStatus::InTransaction, - conn.raw_connection.transaction_status() + conn.connection_and_transaction_manager.raw_connection.transaction_status() ); assert_eq!( NonZeroU32::new(1), @@ -1064,7 +1231,9 @@ mod tests { ); assert_eq!( PgTransactionStatus::Idle, - conn.raw_connection.transaction_status() + conn.connection_and_transaction_manager + .raw_connection + .transaction_status() ); assert!(result.is_ok()); } diff --git a/diesel/src/pg/connection/raw.rs b/diesel/src/pg/connection/raw.rs index f13a6e7555da..a7483cfa9bac 100644 --- a/diesel/src/pg/connection/raw.rs +++ b/diesel/src/pg/connection/raw.rs @@ -10,6 +10,8 @@ use std::{ptr, str}; use crate::result::*; +use super::result::PgResult; + #[allow(missing_debug_implementations, missing_copy_implementations)] pub(super) struct RawConnection { internal_connection: NonNull, @@ -61,7 +63,7 @@ impl RawConnection { RawResult::new(PQexec(self.internal_connection.as_ptr(), query), self) } - pub(super) unsafe fn exec_prepared( + pub(super) unsafe fn send_query_prepared( &self, stmt_name: *const libc::c_char, param_count: libc::c_int, @@ -69,8 +71,8 @@ impl RawConnection { param_lengths: *const libc::c_int, param_formats: *const libc::c_int, result_format: libc::c_int, - ) -> QueryResult { - let ptr = PQexecPrepared( + ) -> QueryResult<()> { + let res = PQsendQueryPrepared( self.internal_connection.as_ptr(), stmt_name, param_count, @@ -79,7 +81,14 @@ impl RawConnection { param_formats, result_format, ); - RawResult::new(ptr, self) + if res == 1 { + Ok(()) + } else { + Err(Error::DatabaseError( + DatabaseErrorKind::UnableToSendCommand, + Box::new(self.last_error_message()), + )) + } } pub(super) unsafe fn prepare( @@ -106,6 +115,28 @@ impl RawConnection { pub(super) fn get_status(&self) -> ConnStatusType { unsafe { PQstatus(self.internal_connection.as_ptr()) } } + + pub(crate) fn get_next_result(&self) -> Result, Error> { + let res = unsafe { PQgetResult(self.internal_connection.as_ptr()) }; + if res.is_null() { + Ok(None) + } else { + let raw = RawResult::new(res, self)?; + Ok(Some(PgResult::new(raw, self)?)) + } + } + + pub(crate) fn enable_row_by_row_mode(&self) -> QueryResult<()> { + let res = unsafe { PQsetSingleRowMode(self.internal_connection.as_ptr()) }; + if res == 1 { + Ok(()) + } else { + Err(Error::DatabaseError( + DatabaseErrorKind::Unknown, + Box::new(self.last_error_message()), + )) + } + } } /// Represents the current in-transaction status of the connection diff --git a/diesel/src/pg/connection/result.rs b/diesel/src/pg/connection/result.rs index f751b861ad76..3036c5d7ec1d 100644 --- a/diesel/src/pg/connection/result.rs +++ b/diesel/src/pg/connection/result.rs @@ -12,7 +12,8 @@ use super::row::PgRow; use crate::result::{DatabaseErrorInformation, DatabaseErrorKind, Error, QueryResult}; use crate::util::OnceCell; -pub(crate) struct PgResult { +#[allow(missing_debug_implementations)] +pub struct PgResult { internal_result: RawResult, column_count: usize, row_count: usize, @@ -27,7 +28,9 @@ impl PgResult { pub(super) fn new(internal_result: RawResult, conn: &RawConnection) -> QueryResult { let result_status = unsafe { PQresultStatus(internal_result.as_ptr()) }; match result_status { - ExecStatusType::PGRES_COMMAND_OK | ExecStatusType::PGRES_TUPLES_OK => { + ExecStatusType::PGRES_SINGLE_TUPLE + | ExecStatusType::PGRES_COMMAND_OK + | ExecStatusType::PGRES_TUPLES_OK => { let column_count = unsafe { PQnfields(internal_result.as_ptr()) as usize }; let row_count = unsafe { PQntuples(internal_result.as_ptr()) as usize }; Ok(PgResult { diff --git a/diesel/src/pg/connection/stmt/mod.rs b/diesel/src/pg/connection/stmt/mod.rs index 8587b80019de..816eeab96eb7 100644 --- a/diesel/src/pg/connection/stmt/mod.rs +++ b/diesel/src/pg/connection/stmt/mod.rs @@ -20,6 +20,7 @@ impl Statement { &self, raw_connection: &mut RawConnection, param_data: &[Option>], + row_by_row: bool, ) -> QueryResult { let params_pointer = param_data .iter() @@ -33,8 +34,8 @@ impl Statement { .iter() .map(|data| data.as_ref().map(|d| d.len() as libc::c_int).unwrap_or(0)) .collect::>(); - let internal_res = unsafe { - raw_connection.exec_prepared( + unsafe { + raw_connection.send_query_prepared( self.name.as_ptr(), params_pointer.len() as libc::c_int, params_pointer.as_ptr(), @@ -42,9 +43,11 @@ impl Statement { self.param_formats.as_ptr(), 1, ) - }; - - PgResult::new(internal_res?, raw_connection) + }?; + if row_by_row { + raw_connection.enable_row_by_row_mode()?; + } + Ok(raw_connection.get_next_result()?.expect("Is never none")) } pub(super) fn prepare( diff --git a/diesel/src/pg/expression/expression_methods.rs b/diesel/src/pg/expression/expression_methods.rs index 939e1403e7ca..e6ceed27fa4d 100644 --- a/diesel/src/pg/expression/expression_methods.rs +++ b/diesel/src/pg/expression/expression_methods.rs @@ -10,7 +10,8 @@ use crate::dsl; use crate::expression::grouped::Grouped; use crate::expression::operators::{Asc, Desc}; use crate::expression::{AsExpression, Expression, IntoSql, TypedExpressionType}; -use crate::sql_types::{Array, Inet, Integer, Jsonb, SqlType, Text, VarChar}; +use crate::pg::expression::expression_methods::private::BinaryOrNullableBinary; +use crate::sql_types::{Array, Binary, Inet, Integer, Jsonb, SqlType, Text, VarChar}; use crate::EscapeExpressionMethods; /// PostgreSQL specific methods which are present on all expressions. @@ -2177,9 +2178,179 @@ where { } +/// PostgreSQL specific methods present on Binary expressions. +#[cfg(feature = "postgres_backend")] +pub trait PgBinaryExpressionMethods: Expression + Sized { + /// Concatenates two PostgreSQL byte arrays using the `||` operator. + /// + /// # Example + /// + /// ```rust + /// # include!("../../doctest_setup.rs"); + /// # + /// # table! { + /// # users { + /// # id -> Integer, + /// # name -> Binary, + /// # hair_color -> Nullable, + /// # } + /// # } + /// # + /// # fn main() { + /// # use self::users::dsl::*; + /// # use diesel::insert_into; + /// # + /// # let connection = &mut connection_no_data(); + /// # diesel::sql_query("CREATE TABLE users ( + /// # id INTEGER PRIMARY KEY, + /// # name BYTEA NOT NULL, + /// # hair_color BYTEA + /// # )").execute(connection).unwrap(); + /// # + /// # insert_into(users) + /// # .values(&vec![ + /// # (id.eq(1), name.eq("Sean".as_bytes()), Some(hair_color.eq(Some("Green".as_bytes())))), + /// # (id.eq(2), name.eq("Tess".as_bytes()), None), + /// # ]) + /// # .execute(connection) + /// # .unwrap(); + /// # + /// let names = users.select(name.concat(" the Greatest".as_bytes())).load(connection); + /// let expected_names = vec![ + /// b"Sean the Greatest".to_vec(), + /// b"Tess the Greatest".to_vec() + /// ]; + /// assert_eq!(Ok(expected_names), names); + /// + /// // If the value is nullable, the output will be nullable + /// let names = users.select(hair_color.concat("ish".as_bytes())).load(connection); + /// let expected_names = vec![ + /// Some(b"Greenish".to_vec()), + /// None, + /// ]; + /// assert_eq!(Ok(expected_names), names); + /// # } + /// ``` + fn concat(self, other: T) -> dsl::ConcatBinary + where + Self::SqlType: SqlType, + T: AsExpression, + { + Grouped(ConcatBinary::new(self, other.as_expression())) + } + + /// Creates a PostgreSQL binary `LIKE` expression. + /// + /// This method is case sensitive. There is no case-insensitive + /// equivalent as of PostgreSQL 14. + /// + /// # Examples + /// + /// ```rust + /// # include!("../../doctest_setup.rs"); + /// # + /// # table! { + /// # users { + /// # id -> Integer, + /// # name -> Binary, + /// # } + /// # } + /// # + /// # fn main() { + /// # use self::users::dsl::*; + /// # use diesel::insert_into; + /// # + /// # let connection = &mut connection_no_data(); + /// # diesel::sql_query("CREATE TABLE users ( + /// # id INTEGER PRIMARY KEY, + /// # name BYTEA NOT NULL + /// # )").execute(connection).unwrap(); + /// # + /// # insert_into(users) + /// # .values(&vec![ + /// # (id.eq(1), name.eq("Sean".as_bytes())), + /// # (id.eq(2), name.eq("Tess".as_bytes())) + /// # ]) + /// # .execute(connection) + /// # .unwrap(); + /// # + /// let starts_with_s = users + /// .select(name) + /// .filter(name.like(b"S%".to_vec())) + /// .load(connection); + /// assert_eq!(Ok(vec![b"Sean".to_vec()]), starts_with_s); + /// # } + /// ``` + fn like(self, other: T) -> dsl::LikeBinary + where + Self::SqlType: SqlType, + T: AsExpression, + { + Grouped(LikeBinary::new(self, other.as_expression())) + } + + /// Creates a PostgreSQL binary `LIKE` expression. + /// + /// This method is case sensitive. There is no case-insensitive + /// equivalent as of PostgreSQL 14. + /// + /// # Examples + /// + /// ```rust + /// # include!("../../doctest_setup.rs"); + /// # + /// # table! { + /// # users { + /// # id -> Integer, + /// # name -> Binary, + /// # } + /// # } + /// # + /// # fn main() { + /// # use self::users::dsl::*; + /// # use diesel::insert_into; + /// # + /// # let connection = &mut connection_no_data(); + /// # diesel::sql_query("CREATE TABLE users ( + /// # id INTEGER PRIMARY KEY, + /// # name BYTEA NOT NULL + /// # )").execute(connection).unwrap(); + /// # + /// # insert_into(users) + /// # .values(&vec![ + /// # (id.eq(1), name.eq("Sean".as_bytes())), + /// # (id.eq(2), name.eq("Tess".as_bytes())) + /// # ]) + /// # .execute(connection) + /// # .unwrap(); + /// # + /// let starts_with_s = users + /// .select(name) + /// .filter(name.not_like(b"S%".to_vec())) + /// .load(connection); + /// assert_eq!(Ok(vec![b"Tess".to_vec()]), starts_with_s); + /// # } + /// ``` + fn not_like(self, other: T) -> dsl::NotLikeBinary + where + Self::SqlType: SqlType, + T: AsExpression, + { + Grouped(NotLikeBinary::new(self, other.as_expression())) + } +} + +#[doc(hidden)] +impl PgBinaryExpressionMethods for T +where + T: Expression, + T::SqlType: BinaryOrNullableBinary, +{ +} + mod private { use crate::sql_types::{ - Array, Cidr, Inet, Integer, Json, Jsonb, Nullable, Range, SqlType, Text, + Array, Binary, Cidr, Inet, Integer, Json, Jsonb, Nullable, Range, SqlType, Text, }; use crate::{Expression, IntoSql}; @@ -2361,4 +2532,9 @@ mod private { pub trait TextOrInteger {} impl TextOrInteger for Text {} impl TextOrInteger for Integer {} + + pub trait BinaryOrNullableBinary {} + + impl BinaryOrNullableBinary for Binary {} + impl BinaryOrNullableBinary for Nullable {} } diff --git a/diesel/src/pg/expression/extensions/interval_dsl.rs b/diesel/src/pg/expression/extensions/interval_dsl.rs index 3d2c0d52c06f..c9fb66eee254 100644 --- a/diesel/src/pg/expression/extensions/interval_dsl.rs +++ b/diesel/src/pg/expression/extensions/interval_dsl.rs @@ -242,6 +242,9 @@ impl IntervalDsl for f64 { } #[cfg(test)] +// those macros define nested function +// that's fine for this test code +#[allow(clippy::items_after_statements)] mod tests { extern crate dotenvy; extern crate quickcheck; diff --git a/diesel/src/pg/expression/helper_types.rs b/diesel/src/pg/expression/helper_types.rs index 374058ad5db9..be3a4f3f74c2 100644 --- a/diesel/src/pg/expression/helper_types.rs +++ b/diesel/src/pg/expression/helper_types.rs @@ -1,7 +1,7 @@ use crate::dsl::{AsExpr, AsExprOf, SqlTypeOf}; use crate::expression::grouped::Grouped; use crate::pg::types::sql_types::Array; -use crate::sql_types::{Inet, Integer, Jsonb, VarChar}; +use crate::sql_types::{Binary, Inet, Integer, Jsonb, VarChar}; /// The return type of [`lhs.ilike(rhs)`](super::expression_methods::PgTextExpressionMethods::ilike) #[cfg(feature = "postgres_backend")] @@ -170,3 +170,17 @@ pub type RetrieveByPathAsTextJson = #[cfg(feature = "postgres_backend")] pub type RemoveByPathFromJsonb = Grouped>>>; + +/// The return type of [`lhs.remove_by_path(rhs)`](super::expression_methods::PgBinaryExpressionMethods::concat) +#[cfg(feature = "postgres_backend")] +pub type ConcatBinary = + Grouped>>; + +/// The return type of [`lhs.remove_by_path(rhs)`](super::expression_methods::PgBinaryExpressionMethods::like) +#[cfg(feature = "postgres_backend")] +pub type LikeBinary = Grouped>>; + +/// The return type of [`lhs.remove_by_path(rhs)`](super::expression_methods::PgBinaryExpressionMethods::not_like) +#[cfg(feature = "postgres_backend")] +pub type NotLikeBinary = + Grouped>>; diff --git a/diesel/src/pg/expression/operators.rs b/diesel/src/pg/expression/operators.rs index 83db89d46eb1..ea99bf376ecf 100644 --- a/diesel/src/pg/expression/operators.rs +++ b/diesel/src/pg/expression/operators.rs @@ -5,7 +5,7 @@ use crate::pg::Pg; use crate::query_builder::update_statement::changeset::AssignmentTarget; use crate::query_builder::{AstPass, QueryFragment, QueryId}; use crate::sql_types::{ - Array, Bigint, Bool, DieselNumericOps, Inet, Integer, Jsonb, SqlType, Text, + Array, Bigint, Binary, Bool, DieselNumericOps, Inet, Integer, Jsonb, SqlType, Text, }; use crate::{Column, QueryResult}; @@ -49,6 +49,9 @@ __diesel_infix_operator!( ); infix_operator!(RetrieveByPathAsTextJson, " #>> ", Text, backend: Pg); infix_operator!(RemoveByPathFromJsonb, " #-", Jsonb, backend: Pg); +infix_operator!(ConcatBinary, " || ", Binary, backend: Pg); +infix_operator!(LikeBinary, " LIKE ", backend: Pg); +infix_operator!(NotLikeBinary, " NOT LIKE ", backend: Pg); #[derive(Debug, Clone, Copy, QueryId, DieselNumericOps, ValidGrouping)] #[doc(hidden)] diff --git a/diesel/src/pg/metadata_lookup.rs b/diesel/src/pg/metadata_lookup.rs index 4f9a9d95edf2..8165bb125acd 100644 --- a/diesel/src/pg/metadata_lookup.rs +++ b/diesel/src/pg/metadata_lookup.rs @@ -9,6 +9,7 @@ use super::backend::{FailedToLookupTypeError, InnerPgTypeMetadata}; use super::{Pg, PgTypeMetadata}; +use crate::connection::{DefaultLoadingMode, LoadConnection}; use crate::prelude::*; use std::borrow::Cow; @@ -31,7 +32,7 @@ pub trait PgMetadataLookup { impl PgMetadataLookup for T where - T: Connection + GetPgMetadataCache, + T: Connection + GetPgMetadataCache + LoadConnection, { fn lookup_type(&mut self, type_name: &str, schema: Option<&str>) -> PgTypeMetadata { let cache_key = PgMetadataCacheKey { @@ -75,7 +76,7 @@ pub trait GetPgMetadataCache { fn get_metadata_cache(&mut self) -> &mut PgMetadataCache; } -fn lookup_type>( +fn lookup_type + LoadConnection>( cache_key: &PgMetadataCacheKey<'_>, conn: &mut T, ) -> QueryResult { diff --git a/diesel/src/pg/mod.rs b/diesel/src/pg/mod.rs index 3c9b0b5b7976..0c1f872dfc32 100644 --- a/diesel/src/pg/mod.rs +++ b/diesel/src/pg/mod.rs @@ -18,7 +18,7 @@ mod value; pub use self::backend::{Pg, PgTypeMetadata}; #[cfg(feature = "postgres")] -pub use self::connection::PgConnection; +pub use self::connection::{PgConnection, PgRowByRowLoadingMode}; #[doc(inline)] pub use self::metadata_lookup::PgMetadataLookup; #[doc(inline)] diff --git a/diesel/src/pg/query_builder/distinct_on.rs b/diesel/src/pg/query_builder/distinct_on.rs index cab64bec9c99..06bf8ffe744a 100644 --- a/diesel/src/pg/query_builder/distinct_on.rs +++ b/diesel/src/pg/query_builder/distinct_on.rs @@ -23,6 +23,33 @@ where T::SqlType: SingleValue, { } +impl ValidOrderingForDistinct> for OrderClause> +where + T: Expression, + T::SqlType: SingleValue, +{ +} +impl ValidOrderingForDistinct> for OrderClause> +where + T: Expression, + T::SqlType: SingleValue, +{ +} + +impl ValidOrderingForDistinct> + for OrderClause<(crate::helper_types::Desc,)> +where + T: Expression, + T::SqlType: SingleValue, +{ +} +impl ValidOrderingForDistinct> + for OrderClause<(crate::helper_types::Asc,)> +where + T: Expression, + T::SqlType: SingleValue, +{ +} impl QueryFragment for DistinctOnClause where diff --git a/diesel/src/pg/query_builder/mod.rs b/diesel/src/pg/query_builder/mod.rs index 0d088630e051..0889fe6283f7 100644 --- a/diesel/src/pg/query_builder/mod.rs +++ b/diesel/src/pg/query_builder/mod.rs @@ -55,7 +55,7 @@ impl QueryBuilder for PgQueryBuilder { #[test] fn check_sql_query_increments_bind_count() { - use crate::query_builder::{AstPass, QueryFragment}; + use crate::query_builder::{AstPass, AstPassToSqlOptions, QueryFragment}; use crate::sql_types::*; let query = crate::sql_query("SELECT $1, $2, $3") @@ -66,7 +66,8 @@ fn check_sql_query_increments_bind_count() { let mut query_builder = PgQueryBuilder::default(); { - let ast_pass = AstPass::::to_sql(&mut query_builder, &Pg); + let mut options = AstPassToSqlOptions::default(); + let ast_pass = AstPass::::to_sql(&mut query_builder, &mut options, &Pg); query.walk_ast(ast_pass).unwrap(); } diff --git a/diesel/src/pg/transaction.rs b/diesel/src/pg/transaction.rs index 41110cd4b8a8..d679416484f4 100644 --- a/diesel/src/pg/transaction.rs +++ b/diesel/src/pg/transaction.rs @@ -1,6 +1,5 @@ #![allow(dead_code)] use crate::backend::Backend; -use crate::connection::commit_error_processor::CommitErrorProcessor; use crate::connection::{AnsiTransactionManager, TransactionManager}; use crate::pg::Pg; use crate::prelude::*; @@ -28,7 +27,7 @@ pub struct TransactionBuilder<'a, C> { impl<'a, C> TransactionBuilder<'a, C> where - C: Connection + CommitErrorProcessor, + C: Connection, { pub(crate) fn new(connection: &'a mut C) -> Self { Self { @@ -269,21 +268,16 @@ where /// the closure returns `Ok(_)`, it will be rolled back if it returns `Err(_)`. /// For both cases the original result value will be returned from this function. /// - /// If the transaction fails to commit due to a `SerializationFailure` or a - /// `ReadOnlyTransaction` a rollback will be attempted. In this case a - /// [`Error::CommitTransactionFailed`](crate::result::Error::CommitTransactionFailed) - /// error is returned, which contains details about the original error and - /// the success of the rollback attempt. - /// If the rollback failed the connection should be considered broken + /// If the transaction fails to commit and requires a rollback according to Postgres, + /// (e.g. serialization failure) a rollback will be attempted. + /// If the rollback fails, the error will be returned in a + /// [`Error::RollbackErrorOnCommit`](crate::result::Error::RollbackErrorOnCommit), + /// from which you will be able to extract both the original commit error and + /// the rollback error. + /// In addition, the connection will be considered broken /// as it contains a uncommitted unabortable open transaction. Any further /// interaction with the transaction system will result in an returned error - /// in this cases. - /// - /// If the closure returns an `Err(_)` and the rollback fails the function - /// will return a [`Error::RollbackError`](crate::result::Error::RollbackError) - /// wrapping the error generated by the rollback operation instead. - /// In this case the connection should be considered broken as it contains - /// an unabortable open transaction. + /// in this case. pub fn run(&mut self, f: F) -> Result where F: FnOnce(&mut C) -> Result, @@ -299,10 +293,16 @@ where AnsiTransactionManager::commit_transaction(&mut *self.connection)?; Ok(value) } - Err(e) => { - AnsiTransactionManager::rollback_transaction(&mut *self.connection) - .map_err(|e| Error::RollbackError(Box::new(e)))?; - Err(e) + Err(user_error) => { + match AnsiTransactionManager::rollback_transaction(&mut *self.connection) { + Ok(()) => Err(user_error), + Err(Error::BrokenTransactionManager) => { + // In this case we are probably more interested by the + // original error, which likely caused this + Err(user_error) + } + Err(rollback_error) => Err(rollback_error.into()), + } } } } diff --git a/diesel/src/pg/types/array.rs b/diesel/src/pg/types/array.rs index 948560240df0..e80eb3216810 100644 --- a/diesel/src/pg/types/array.rs +++ b/diesel/src/pg/types/array.rs @@ -64,6 +64,9 @@ use crate::expression::AsExpression; macro_rules! array_as_expression { ($ty:ty, $sql_type:ty) => { #[cfg(feature = "postgres_backend")] + // this simplifies the macro implemntation + // as some macro calls use this lifetime + #[allow(clippy::extra_unused_lifetimes)] impl<'a, 'b, ST: 'static, T> AsExpression<$sql_type> for $ty { type Expression = Bound<$sql_type, Self>; diff --git a/diesel/src/pg/types/date_and_time/std_time.rs b/diesel/src/pg/types/date_and_time/std_time.rs index bd1880142da7..a25ec6c5b154 100644 --- a/diesel/src/pg/types/date_and_time/std_time.rs +++ b/diesel/src/pg/types/date_and_time/std_time.rs @@ -31,7 +31,7 @@ impl FromSql for SystemTime { fn from_sql(bytes: PgValue<'_>) -> deserialize::Result { let usecs_passed = >::from_sql(bytes)?; let before_epoch = usecs_passed < 0; - let time_passed = usecs_to_duration(usecs_passed.abs() as u64); + let time_passed = usecs_to_duration(usecs_passed.unsigned_abs()); if before_epoch { Ok(pg_epoch() - time_passed) diff --git a/diesel/src/pg/types/ipnet_address.rs b/diesel/src/pg/types/ipnet_address.rs new file mode 100644 index 000000000000..10a5e6b8c84d --- /dev/null +++ b/diesel/src/pg/types/ipnet_address.rs @@ -0,0 +1,271 @@ +extern crate libc; + +use ipnet::{IpNet, Ipv4Net, Ipv6Net}; +use std::io::prelude::*; +use std::net::{Ipv4Addr, Ipv6Addr}; + +use crate::deserialize::{self, FromSql, FromSqlRow}; +use crate::pg::{Pg, PgValue}; +#[cfg(test)] +use crate::query_builder::bind_collector::ByteWrapper; +use crate::serialize::{self, IsNull, Output, ToSql}; +use crate::sql_types::{Cidr, Inet}; + +#[cfg(windows)] +const AF_INET: u8 = 2; +// Maybe not used, but defining to follow Rust's libstd/net/sys +#[cfg(target_os = "redox")] +const AF_INET: u8 = 1; +#[cfg(not(any(windows, target_os = "redox")))] +const AF_INET: u8 = libc::AF_INET as u8; + +const PGSQL_AF_INET: u8 = AF_INET; +const PGSQL_AF_INET6: u8 = AF_INET + 1; + +#[allow(dead_code)] +mod foreign_derives { + use super::*; + use crate::expression::AsExpression; + + #[derive(AsExpression, FromSqlRow)] + #[diesel(foreign_derive)] + #[diesel(sql_type = Inet)] + #[diesel(sql_type = Cidr)] + struct IpNetworkProxy(IpNet); +} + +macro_rules! err { + () => { + Err("invalid network address format".into()) + }; + ($msg:expr) => { + Err(format!("invalid network address format. {}", $msg).into()) + }; +} + +macro_rules! assert_or_error { + ($cond:expr) => { + if !$cond { + return err!(); + } + }; + + ($cond:expr, $msg:expr) => { + if !$cond { + return err!($msg); + } + }; +} + +macro_rules! impl_Sql { + ($ty: ty, $net_type: expr) => { + #[cfg(all(feature = "postgres_backend", feature = "ipnet-address"))] + impl FromSql<$ty, Pg> for IpNet { + fn from_sql(value: PgValue<'_>) -> deserialize::Result { + // https://github.com/postgres/postgres/blob/55c3391d1e6a201b5b891781d21fe682a8c64fe6/src/include/utils/inet.h#L23-L28 + let bytes = value.as_bytes(); + assert_or_error!(4 <= bytes.len(), "input is too short."); + let af = bytes[0]; + let prefix = bytes[1]; + let net_type = bytes[2]; + let len = bytes[3]; + assert_or_error!( + net_type == $net_type, + format!("returned type isn't a {}", stringify!($ty)) + ); + if af == PGSQL_AF_INET { + assert_or_error!(bytes.len() == 8); + assert_or_error!(len == 4, "the data isn't the size of ipv4"); + let b = &bytes[4..]; + let addr = Ipv4Addr::new(b[0], b[1], b[2], b[3]); + let inet = Ipv4Net::new(addr, prefix)?; + Ok(IpNet::V4(inet)) + } else if af == PGSQL_AF_INET6 { + assert_or_error!(bytes.len() == 20); + assert_or_error!(len == 16, "the data isn't the size of ipv6"); + let b = &bytes[4..]; + let addr = Ipv6Addr::from([ + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], + b[12], b[13], b[14], b[15], + ]); + let inet = Ipv6Net::new(addr, prefix)?; + Ok(IpNet::V6(inet)) + } else { + err!() + } + } + } + + #[cfg(all(feature = "postgres_backend", feature = "ipnet-address"))] + impl ToSql<$ty, Pg> for IpNet { + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result { + let net_type = $net_type; + match *self { + IpNet::V4(ref net) => { + let mut data = [0u8; 8]; + let af = PGSQL_AF_INET; + let prefix = net.prefix_len(); + let len: u8 = 4; + let addr = net.network().octets(); + data[0] = af; + data[1] = prefix; + data[2] = net_type; + data[3] = len; + data[4..].copy_from_slice(&addr); + out.write_all(&data).map(|_| IsNull::No).map_err(Into::into) + } + IpNet::V6(ref net) => { + let mut data = [0u8; 20]; + let af = PGSQL_AF_INET6; + let prefix = net.prefix_len(); + let len: u8 = 16; + let addr = net.network().octets(); + data[0] = af; + data[1] = prefix; + data[2] = net_type; + data[3] = len; + data[4..].copy_from_slice(&addr); + out.write_all(&data).map(|_| IsNull::No).map_err(Into::into) + } + } + } + } + }; +} + +impl_Sql!(Inet, 0); +impl_Sql!(Cidr, 1); + +#[test] +fn v4address_to_sql() { + macro_rules! test_to_sql { + ($ty:ty, $net_type:expr) => { + let mut buffer = Vec::new(); + { + let mut bytes = Output::test(ByteWrapper(&mut buffer)); + let test_address = + IpNet::V4(Ipv4Net::new(Ipv4Addr::new(127, 0, 0, 1), 32).unwrap()); + ToSql::<$ty, Pg>::to_sql(&test_address, &mut bytes).unwrap(); + } + assert_eq!(buffer, vec![PGSQL_AF_INET, 32, $net_type, 4, 127, 0, 0, 1]); + }; + } + + test_to_sql!(Inet, 0); + test_to_sql!(Cidr, 1); +} + +#[test] +fn some_v4address_from_sql() { + macro_rules! test_some_address_from_sql { + ($ty:tt) => { + let input_address = IpNet::V4(Ipv4Net::new(Ipv4Addr::new(127, 0, 0, 1), 32).unwrap()); + let mut buffer = Vec::new(); + { + let mut bytes = Output::test(ByteWrapper(&mut buffer)); + ToSql::<$ty, Pg>::to_sql(&input_address, &mut bytes).unwrap(); + } + let output_address = FromSql::<$ty, Pg>::from_sql(PgValue::for_test(&buffer)).unwrap(); + assert_eq!(input_address, output_address); + }; + } + + test_some_address_from_sql!(Cidr); + test_some_address_from_sql!(Inet); +} + +#[test] +fn v6address_to_sql() { + macro_rules! test_to_sql { + ($ty:ty, $net_type:expr) => { + let mut buffer = Vec::new(); + { + let mut bytes = Output::test(ByteWrapper(&mut buffer)); + let test_address = + IpNet::V6(Ipv6Net::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 64).unwrap()); + ToSql::<$ty, Pg>::to_sql(&test_address, &mut bytes).unwrap(); + } + assert_eq!( + buffer, + vec![ + PGSQL_AF_INET6, + 64, + $net_type, + 16, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + ] + ); + }; + } + + test_to_sql!(Inet, 0); + test_to_sql!(Cidr, 1); +} + +#[test] +fn some_v6address_from_sql() { + macro_rules! test_some_address_from_sql { + ($ty:tt) => { + let input_address = + IpNet::V6(Ipv6Net::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 64).unwrap()); + let mut buffer = Vec::new(); + { + let mut bytes = Output::test(ByteWrapper(&mut buffer)); + ToSql::<$ty, Pg>::to_sql(&input_address, &mut bytes).unwrap(); + } + let output_address = FromSql::<$ty, Pg>::from_sql(PgValue::for_test(&buffer)).unwrap(); + assert_eq!(input_address, output_address); + }; + } + + test_some_address_from_sql!(Inet); + test_some_address_from_sql!(Cidr); +} + +#[test] +fn bad_address_from_sql() { + macro_rules! bad_address_from_sql { + ($ty:tt) => { + let address: Result = + FromSql::<$ty, Pg>::from_sql(PgValue::for_test(&[7, PGSQL_AF_INET, 0])); + assert_eq!( + address.unwrap_err().to_string(), + "invalid network address format. input is too short." + ); + }; + } + + bad_address_from_sql!(Inet); + bad_address_from_sql!(Cidr); +} + +#[test] +fn no_address_from_sql() { + macro_rules! test_no_address_from_sql { + ($ty:ty) => { + let address: Result = FromSql::<$ty, Pg>::from_nullable_sql(None); + assert_eq!( + address.unwrap_err().to_string(), + "Unexpected null for non-null column" + ); + }; + } + + test_no_address_from_sql!(Inet); + test_no_address_from_sql!(Cidr); +} diff --git a/diesel/src/pg/types/mod.rs b/diesel/src/pg/types/mod.rs index 2935795d633f..4e0632e0487e 100644 --- a/diesel/src/pg/types/mod.rs +++ b/diesel/src/pg/types/mod.rs @@ -6,6 +6,8 @@ pub(in crate::pg) mod date_and_time; #[doc(hidden)] pub(in crate::pg) mod floats; mod integers; +#[cfg(feature = "ipnet-address")] +mod ipnet_address; #[cfg(feature = "serde_json")] mod json; mod mac_addr; @@ -86,8 +88,7 @@ pub mod sql_types { /// The [`Array`] SQL type. /// /// This wraps another type to represent a SQL array of that type. - /// Multidimensional arrays are not supported, - /// nor are arrays containing null. + /// Multidimensional arrays are not supported. /// /// ### [`ToSql`] impls /// @@ -405,23 +406,24 @@ pub mod sql_types { /// Alias for `MacAddr` to be able to use it with `infer_schema`. pub type Macaddr = MacAddr; - /// The [`INET`](https://www.postgresql.org/docs/current/static/datatype-net-types.html) SQL type. This type can only be used with `feature = "network-address"` + /// The [`INET`](https://www.postgresql.org/docs/current/static/datatype-net-types.html) SQL type. This type can only be used with `feature = "network-address"` or `feature = "ipnet-address"`. /// /// ### [`ToSql`] impls /// - /// - [`ipnetwork::IpNetwork`][IpNetwork] + #[cfg_attr(feature = "ipnetwork", doc = " - [`ipnetwork::IpNetwork`][IpNetwork]")] + #[cfg_attr(feature = "ipnet", doc = " - [`ipnet::IpNet`][IpNet]")] + #[cfg_attr(not(any(feature = "ipnetwork", feature = "ipnet")), doc = "N/A")] /// /// ### [`FromSql`] impls /// - /// - [`ipnetwork::IpNetwork`][IpNetwork] + #[cfg_attr(feature = "ipnetwork", doc = " - [`ipnetwork::IpNetwork`][IpNetwork]")] + #[cfg_attr(feature = "ipnet", doc = " - [`ipnet::IpNet`][IpNet]")] + #[cfg_attr(not(any(feature = "ipnetwork", feature = "ipnet")), doc = "N/A")] /// /// [`ToSql`]: crate::serialize::ToSql /// [`FromSql`]: crate::deserialize::FromSql #[cfg_attr(feature = "ipnetwork", doc = " [IpNetwork]: ipnetwork::IpNetwork")] - #[cfg_attr( - not(feature = "ipnetwork"), - doc = " [IpNetwork]: https://docs.rs/ipnetwork/*/ipnetwork/enum.IpNetwork.html" - )] + #[cfg_attr(feature = "ipnet", doc = " [IpNet]: ipnet::IpNet")] /// /// # Examples /// @@ -435,9 +437,8 @@ pub mod sql_types { /// } /// } /// - /// # #[cfg(feature = "network-address")] + /// # #[cfg(any(feature = "network-address", feature = "ipnet-address"))] /// # fn main() -> Result<(), Box> { - /// use ipnetwork::IpNetwork; /// /// # use diesel::insert_into; /// # use self::clients::dsl::*; @@ -446,7 +447,8 @@ pub mod sql_types { /// # id SERIAL PRIMARY KEY, /// # ip_address INET NOT NULL /// # )").execute(connection)?; - /// let addr = "10.1.9.32/32".parse::()?; + /// // Parsing "ipnet::IpNet" would also work. + /// let addr = "10.1.9.32/32".parse::()?; /// let inserted_address = insert_into(clients) /// .values(ip_address.eq(&addr)) /// .returning(ip_address) @@ -455,7 +457,7 @@ pub mod sql_types { /// # Ok(()) /// # } /// # - /// # #[cfg(not(feature = "network-address"))] + /// # #[cfg(not(any(feature = "network-address", feature = "ipnet-address")))] /// # fn main() {} /// ``` #[cfg(feature = "postgres_backend")] @@ -463,23 +465,24 @@ pub mod sql_types { #[diesel(postgres_type(oid = 869, array_oid = 1041))] pub struct Inet; - /// The [`CIDR`](https://www.postgresql.org/docs/postgresql/static/datatype-net-types.html) SQL type. This type can only be used with `feature = "network-address"` + /// The [`CIDR`](https://www.postgresql.org/docs/postgresql/static/datatype-net-types.html) SQL type. This type can only be used with `feature = "network-address"` or `feature = "ipnet-address"`. /// /// ### [`ToSql`] impls /// - /// - [`ipnetwork::IpNetwork`][IpNetwork] + #[cfg_attr(feature = "ipnetwork", doc = " - [`ipnetwork::IpNetwork`][IpNetwork]")] + #[cfg_attr(feature = "ipnet", doc = " - [`ipnet::IpNet`][IpNet]")] + #[cfg_attr(not(any(feature = "ipnetwork", feature = "ipnet")), doc = "N/A")] /// /// ### [`FromSql`] impls /// - /// - [`ipnetwork::IpNetwork`][IpNetwork] + #[cfg_attr(feature = "ipnetwork", doc = " - [`ipnetwork::IpNetwork`][IpNetwork]")] + #[cfg_attr(feature = "ipnet", doc = " - [`ipnet::IpNet`][IpNet]")] + #[cfg_attr(not(any(feature = "ipnetwork", feature = "ipnet")), doc = "N/A")] /// /// [`ToSql`]: crate::serialize::ToSql /// [`FromSql`]: crate::deserialize::FromSql #[cfg_attr(feature = "ipnetwork", doc = " [IpNetwork]: ipnetwork::IpNetwork")] - #[cfg_attr( - not(feature = "ipnetwork"), - doc = " [IpNetwork]: https://docs.rs/ipnetwork/*/ipnetwork/enum.IpNetwork.html" - )] + #[cfg_attr(feature = "ipnet", doc = " [IpNet]: ipnet::IpNet")] /// /// # Examples /// @@ -493,9 +496,8 @@ pub mod sql_types { /// } /// } /// - /// # #[cfg(feature = "network-address")] + /// # #[cfg(any(feature = "network-address", feature = "ipnet-address"))] /// # fn main() -> Result<(), Box> { - /// use ipnetwork::IpNetwork; /// /// # use diesel::insert_into; /// # use self::clients::dsl::*; @@ -504,7 +506,8 @@ pub mod sql_types { /// # id SERIAL PRIMARY KEY, /// # ip_address CIDR NOT NULL /// # )").execute(connection)?; - /// let addr = "10.1.9.32/32".parse::()?; + /// // Parsing "ipnet::IpNet" would also work. + /// let addr = "10.1.9.32/32".parse::()?; /// let inserted_addr = insert_into(clients) /// .values(ip_address.eq(&addr)) /// .returning(ip_address) @@ -512,7 +515,7 @@ pub mod sql_types { /// assert_eq!(addr, inserted_addr); /// # Ok(()) /// # } - /// # #[cfg(not(feature = "network-address"))] + /// # #[cfg(not(any(feature = "network-address", feature = "ipnet-address")))] /// # fn main() {} /// ``` #[cfg(feature = "postgres_backend")] diff --git a/diesel/src/query_builder/ast_pass.rs b/diesel/src/query_builder/ast_pass.rs index 1d45c61bb28b..a67fb350d0ec 100644 --- a/diesel/src/query_builder/ast_pass.rs +++ b/diesel/src/query_builder/ast_pass.rs @@ -38,9 +38,13 @@ where DB: Backend, 'b: 'a, { - pub(crate) fn to_sql(query_builder: &'a mut DB::QueryBuilder, backend: &'b DB) -> Self { + pub(crate) fn to_sql( + query_builder: &'a mut DB::QueryBuilder, + options: &'a mut AstPassToSqlOptions, + backend: &'b DB, + ) -> Self { AstPass { - internals: AstPassInternals::ToSql(query_builder), + internals: AstPassInternals::ToSql(query_builder, options), backend, } } @@ -84,6 +88,13 @@ where } } + #[cfg(feature = "sqlite")] + pub(crate) fn skip_from(&mut self, value: bool) { + if let AstPassInternals::ToSql(_, ref mut options) = self.internals { + options.skip_from = value + } + } + /// Call this method whenever you pass an instance of `AstPass` by value. /// /// Effectively copies `self`, with a narrower lifetime. When passing a @@ -95,7 +106,9 @@ where /// implicitly if you were passing a mutable reference pub fn reborrow(&'_ mut self) -> AstPass<'_, 'b, DB> { let internals = match self.internals { - AstPassInternals::ToSql(ref mut builder) => AstPassInternals::ToSql(&mut **builder), + AstPassInternals::ToSql(ref mut builder, ref mut options) => { + AstPassInternals::ToSql(&mut **builder, &mut **options) + } AstPassInternals::CollectBinds { ref mut collector, ref mut metadata_lookup, @@ -167,7 +180,7 @@ where /// ``` pub fn push_sql(&mut self, sql: &str) { match self.internals { - AstPassInternals::ToSql(ref mut builder) => builder.push_sql(sql), + AstPassInternals::ToSql(ref mut builder, _) => builder.push_sql(sql), AstPassInternals::IsNoop(ref mut result) => **result = false, _ => {} } @@ -179,7 +192,7 @@ where /// the query is being constructed for. pub fn push_identifier(&mut self, identifier: &str) -> QueryResult<()> { match self.internals { - AstPassInternals::ToSql(ref mut builder) => builder.push_identifier(identifier)?, + AstPassInternals::ToSql(ref mut builder, _) => builder.push_identifier(identifier)?, AstPassInternals::IsNoop(ref mut result) => **result = false, _ => {} } @@ -197,7 +210,7 @@ where U: ToSql, { match self.internals { - AstPassInternals::ToSql(ref mut out) => out.push_bind_param(), + AstPassInternals::ToSql(ref mut out, _) => out.push_bind_param(), AstPassInternals::CollectBinds { ref mut collector, ref mut metadata_lookup, @@ -220,7 +233,7 @@ where AstPassInternals::CollectBinds { .. } | AstPassInternals::DebugBinds(..) => { self.push_bind_param(bind)? } - AstPassInternals::ToSql(ref mut out) => { + AstPassInternals::ToSql(ref mut out, _) => { out.push_bind_param_value_only(); } _ => {} @@ -240,6 +253,23 @@ where pub fn backend(&self) -> &DB { self.backend } + + /// Get if the query should be rendered with from clauses or not + #[cfg_attr( + not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"), + doc(hidden) + )] // This is used by the `__diesel_column` macro + #[cfg_attr( + doc_cfg, + doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")) + )] + pub fn should_skip_from(&self) -> bool { + if let AstPassInternals::ToSql(_, ref options) = self.internals { + options.skip_from + } else { + false + } + } } #[allow(missing_debug_implementations)] @@ -255,7 +285,7 @@ where DB::MetadataLookup: 'a, 'b: 'a, { - ToSql(&'a mut DB::QueryBuilder), + ToSql(&'a mut DB::QueryBuilder, &'a mut AstPassToSqlOptions), CollectBinds { collector: &'a mut >::BindCollector, metadata_lookup: &'a mut DB::MetadataLookup, @@ -264,3 +294,15 @@ where DebugBinds(&'a mut Vec<&'b dyn fmt::Debug>), IsNoop(&'a mut bool), } + +#[diesel_derives::__diesel_public_if( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" +)] +#[allow(missing_debug_implementations)] +#[allow(missing_copy_implementations)] +#[derive(Default)] +/// This is used to pass down additional settings to the `AstPass` +/// when rendering the sql string. +pub(crate) struct AstPassToSqlOptions { + skip_from: bool, +} diff --git a/diesel/src/query_builder/combination_clause.rs b/diesel/src/query_builder/combination_clause.rs index 5435bd395e37..23a7002e0a6c 100644 --- a/diesel/src/query_builder/combination_clause.rs +++ b/diesel/src/query_builder/combination_clause.rs @@ -26,8 +26,8 @@ where pub struct CombinationClause { combinator: Combinator, duplicate_rule: Rule, - source: Source, - rhs: RhsParenthesisWrapper, + source: ParenthesisWrapper, + rhs: ParenthesisWrapper, } impl CombinationClause { @@ -41,8 +41,8 @@ impl CombinationClause QueryFragment where Combinator: QueryFragment, Rule: QueryFragment, - Source: QueryFragment, - RhsParenthesisWrapper: QueryFragment, + ParenthesisWrapper: QueryFragment, + ParenthesisWrapper: QueryFragment, DB: Backend + SupportsCombinationClause + DieselReserveSpecialization, { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> { @@ -215,7 +215,7 @@ pub trait SupportsCombinationClause {} #[derive(Debug, Copy, Clone, QueryId)] /// Wrapper used to wrap rhs sql in parenthesis when supported by backend -pub struct RhsParenthesisWrapper(T); +pub struct ParenthesisWrapper(T); #[cfg(feature = "postgres")] mod postgres { @@ -224,7 +224,7 @@ mod postgres { use crate::query_builder::{AstPass, QueryFragment}; use crate::QueryResult; - impl> QueryFragment for RhsParenthesisWrapper { + impl> QueryFragment for ParenthesisWrapper { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> { out.push_sql("("); self.0.walk_ast(out.reborrow())?; @@ -248,7 +248,7 @@ mod mysql { use crate::query_builder::{AstPass, QueryFragment}; use crate::QueryResult; - impl> QueryFragment for RhsParenthesisWrapper { + impl> QueryFragment for ParenthesisWrapper { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Mysql>) -> QueryResult<()> { out.push_sql("("); self.0.walk_ast(out.reborrow())?; @@ -268,9 +268,15 @@ mod sqlite { use crate::sqlite::Sqlite; use crate::QueryResult; - impl> QueryFragment for RhsParenthesisWrapper { - fn walk_ast<'b>(&'b self, out: AstPass<'_, 'b, Sqlite>) -> QueryResult<()> { - self.0.walk_ast(out) // SQLite does not support parenthesis around Ths + impl> QueryFragment for ParenthesisWrapper { + fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Sqlite>) -> QueryResult<()> { + // SQLite does not support parenthesis around Ths + // we can emulate this by construct a fake outer + // SELECT * FROM (inner_query) statement + out.push_sql("SELECT * FROM ("); + self.0.walk_ast(out.reborrow())?; + out.push_sql(")"); + Ok(()) } } diff --git a/diesel/src/query_builder/functions.rs b/diesel/src/query_builder/functions.rs index 153ef0b9e07a..5ef19cad96ab 100644 --- a/diesel/src/query_builder/functions.rs +++ b/diesel/src/query_builder/functions.rs @@ -588,8 +588,7 @@ pub fn replace_into(target: T) -> IncompleteReplaceStatement { /// # #[cfg(not(feature = "postgres"))] /// // Checkout the documentation of your database for the correct /// // bind placeholder -/// let users = sql_query("SELECT * FROM users WHERE id > ? AND name <> ?") -/// # ; +/// let users = sql_query("SELECT * FROM users WHERE id > ? AND name <> ?"); /// let users = users /// .bind::(1) /// .bind::("Tess") diff --git a/diesel/src/query_builder/insert_statement/insert_with_default_for_sqlite.rs b/diesel/src/query_builder/insert_statement/insert_with_default_for_sqlite.rs index 23b9960c7bcc..0cc5d2811596 100644 --- a/diesel/src/query_builder/insert_statement/insert_with_default_for_sqlite.rs +++ b/diesel/src/query_builder/insert_statement/insert_with_default_for_sqlite.rs @@ -357,7 +357,7 @@ macro_rules! tuple_impls { } macro_rules! impl_contains_defaultable_value { - ( + ( @build start_ts = [$($ST: ident,)*], ts = [$T1: ident,], diff --git a/diesel/src/query_builder/mod.rs b/diesel/src/query_builder/mod.rs index 7cbad028dff2..456e3fe279c3 100644 --- a/diesel/src/query_builder/mod.rs +++ b/diesel/src/query_builder/mod.rs @@ -26,7 +26,7 @@ pub(crate) mod locking_clause; pub(crate) mod nodes; pub(crate) mod offset_clause; pub(crate) mod order_clause; -mod returning_clause; +pub(crate) mod returning_clause; pub(crate) mod select_clause; pub(crate) mod select_statement; mod sql_query; @@ -86,6 +86,12 @@ pub use self::insert_statement::DefaultValues; #[doc(inline)] pub use self::returning_clause::ReturningClause; +#[doc(inline)] +#[diesel_derives::__diesel_public_if( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" +)] +pub(crate) use self::ast_pass::AstPassToSqlOptions; + #[doc(inline)] #[diesel_derives::__diesel_public_if( feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" @@ -203,7 +209,8 @@ pub trait QueryFragment { feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" )] fn to_sql(&self, out: &mut DB::QueryBuilder, backend: &DB) -> QueryResult<()> { - self.walk_ast(AstPass::to_sql(out, backend)) + let mut options = AstPassToSqlOptions::default(); + self.walk_ast(AstPass::to_sql(out, &mut options, backend)) } /// Serializes all bind parameters in this query. @@ -333,10 +340,10 @@ pub trait AsQuery { } impl AsQuery for T { - type SqlType = ::SqlType; - type Query = Self; + type SqlType = ::SqlType; + type Query = T; - fn as_query(self) -> Self::Query { + fn as_query(self) -> ::Query { self } } diff --git a/diesel/src/query_builder/select_statement/dsl_impls.rs b/diesel/src/query_builder/select_statement/dsl_impls.rs index 8353f1c4d150..b73338089da3 100644 --- a/diesel/src/query_builder/select_statement/dsl_impls.rs +++ b/diesel/src/query_builder/select_statement/dsl_impls.rs @@ -604,7 +604,7 @@ where } } -impl<'a, F, S, D, W, O, LOf, G, H> SelectNullableDsl +impl SelectNullableDsl for SelectStatement, D, W, O, LOf, G, H> { type Output = SelectStatement>, D, W, O, LOf, G, H>; @@ -624,7 +624,7 @@ impl<'a, F, S, D, W, O, LOf, G, H> SelectNullableDsl } } -impl<'a, F, D, W, O, LOf, G, H> SelectNullableDsl +impl SelectNullableDsl for SelectStatement, D, W, O, LOf, G, H> where F: AsQuerySource, diff --git a/diesel/src/query_dsl/load_dsl.rs b/diesel/src/query_dsl/load_dsl.rs index a601beb671c3..046304b2fb6e 100644 --- a/diesel/src/query_dsl/load_dsl.rs +++ b/diesel/src/query_dsl/load_dsl.rs @@ -1,7 +1,7 @@ use self::private::LoadIter; use super::RunQueryDsl; use crate::backend::Backend; -use crate::connection::{Connection, ConnectionGatWorkaround}; +use crate::connection::{Connection, ConnectionGatWorkaround, DefaultLoadingMode, LoadConnection}; use crate::deserialize::FromSqlRow; use crate::expression::QueryMetadata; use crate::query_builder::{AsQuery, QueryFragment, QueryId}; @@ -20,25 +20,29 @@ pub(crate) use self::private::{CompatibleType, LoadQueryGatWorkaround}; /// to call `load` from generic code. /// /// [`RunQueryDsl`]: crate::RunQueryDsl -pub trait LoadQuery<'query, Conn, U>: RunQueryDsl +pub trait LoadQuery<'query, Conn, U, B = DefaultLoadingMode>: RunQueryDsl where - for<'a> Self: LoadQueryGatWorkaround<'a, 'query, Conn, U>, + for<'a> Self: LoadQueryGatWorkaround<'a, 'query, Conn, U, B>, { /// Load this query + #[diesel_derives::__diesel_public_if( + feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes" + )] fn internal_load<'conn>( self, conn: &'conn mut Conn, - ) -> QueryResult<>::Ret>; + ) -> QueryResult<>::Ret>; } /// The return type of [`LoadQuery::internal_load()`] /// /// Users should thread this type as `impl Iterator>` -pub type LoadRet<'conn, 'query, Q, C, U> = >::Ret; +pub type LoadRet<'conn, 'query, Q, C, U, B = DefaultLoadingMode> = + >::Ret; -impl<'conn, 'query, Conn, T, U, DB> LoadQueryGatWorkaround<'conn, 'query, Conn, U> for T +impl<'conn, 'query, Conn, T, U, DB, B> LoadQueryGatWorkaround<'conn, 'query, Conn, U, B> for T where - Conn: Connection, + Conn: Connection + ConnectionGatWorkaround<'conn, 'query, DB, B>, T: AsQuery + RunQueryDsl, T::Query: QueryFragment + QueryId, T::SqlType: CompatibleType, @@ -47,17 +51,16 @@ where >::SqlType: 'static, { type Ret = LoadIter< - 'conn, U, - >::Cursor, + >::Cursor, >::SqlType, DB, >; } -impl<'query, Conn, T, U, DB> LoadQuery<'query, Conn, U> for T +impl<'query, Conn, T, U, DB, B> LoadQuery<'query, Conn, U, B> for T where - Conn: Connection, + Conn: Connection + LoadConnection, T: AsQuery + RunQueryDsl, T::Query: QueryFragment + QueryId + 'query, T::SqlType: CompatibleType, @@ -68,7 +71,7 @@ where fn internal_load<'conn>( self, conn: &'conn mut Conn, - ) -> QueryResult<>::Ret> { + ) -> QueryResult<>::Ret> { Ok(LoadIter { cursor: conn.load(self.as_query())?, _marker: Default::default(), @@ -90,13 +93,15 @@ pub trait ExecuteDsl, DB: Backend = QueryResult; } +use crate::result::Error; + impl ExecuteDsl for T where Conn: Connection, DB: Backend, T: QueryFragment + QueryId, { - fn execute(query: Self, conn: &mut Conn) -> QueryResult { + fn execute(query: T, conn: &mut Conn) -> Result { conn.execute_returning_count(&query) } } @@ -109,6 +114,7 @@ where // * LoadIter as it's an implementation detail mod private { use crate::backend::Backend; + use crate::connection::DefaultLoadingMode; use crate::deserialize::FromSqlRow; use crate::expression::select_by::SelectBy; use crate::expression::{Expression, TypedExpressionType}; @@ -119,17 +125,17 @@ mod private { feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes", cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes") )] - pub trait LoadQueryGatWorkaround<'conn, 'query, Conn, U> { + pub trait LoadQueryGatWorkaround<'conn, 'query, Conn, U, B = DefaultLoadingMode> { type Ret: Iterator>; } #[allow(missing_debug_implementations)] - pub struct LoadIter<'a, U, C, ST, DB> { + pub struct LoadIter { pub(super) cursor: C, - pub(super) _marker: std::marker::PhantomData<&'a (ST, U, DB)>, + pub(super) _marker: std::marker::PhantomData<(ST, U, DB)>, } - impl<'a, C, U, ST, DB, R> LoadIter<'a, U, C, ST, DB> + impl<'a, C, U, ST, DB, R> LoadIter where DB: Backend, C: Iterator>, @@ -146,7 +152,7 @@ mod private { } } - impl<'a, C, U, ST, DB, R> Iterator for LoadIter<'a, U, C, ST, DB> + impl<'a, C, U, ST, DB, R> Iterator for LoadIter where DB: Backend, C: Iterator>, @@ -182,7 +188,7 @@ mod private { } } - impl<'a, C, U, ST, DB, R> ExactSizeIterator for LoadIter<'a, U, C, ST, DB> + impl<'a, C, U, ST, DB, R> ExactSizeIterator for LoadIter where DB: Backend, C: ExactSizeIterator + Iterator>, @@ -195,8 +201,8 @@ mod private { } #[cfg_attr( - feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes", - cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes") + doc_cfg, + doc(cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")) )] pub trait CompatibleType { type SqlType; diff --git a/diesel/src/query_dsl/mod.rs b/diesel/src/query_dsl/mod.rs index a61ed930aef8..f77187a501eb 100644 --- a/diesel/src/query_dsl/mod.rs +++ b/diesel/src/query_dsl/mod.rs @@ -331,10 +331,9 @@ pub trait QueryDsl: Sized { /// [`.on`]: JoinOnDsl::on() /// /// You can join to as many tables as you'd like in a query, with the - /// restriction that no table can appear in the query more than once. The reason - /// for this restriction is that one of the appearances would require aliasing, - /// and we do not currently have a fleshed out story for dealing with table - /// aliases. + /// restriction that no table can appear in the query more than once. For + /// tables that appear more than once in a single query the usage of [`alias!`](crate::alias!) + /// is required. /// /// You will also need to call [`allow_tables_to_appear_in_same_query!`]. /// If you are using `diesel print-schema`, this will @@ -350,8 +349,11 @@ pub trait QueryDsl: Sized { /// /// ```sql /// SELECT * FROM users - /// INNER JOIN posts ON posts.user_id = users.id - /// INNER JOIN comments ON comments.post_id = posts.id + /// INNER JOIN ( + /// posts + /// INNER JOIN comments ON comments.post_id = posts.id + /// ) ON posts.user_id = users.id + /// /// ``` /// /// While the second query would deserialize into `(User, Post, Comment)` and @@ -363,6 +365,14 @@ pub trait QueryDsl: Sized { /// INNER JOIN comments ON comments.user_id = users.id /// ``` /// + /// The exact generated SQL may change in future diesel version as long as the + /// generated query continues to produce same results. The currently generated + /// SQL is referred as ["explicit join"](https://www.postgresql.org/docs/current/explicit-joins.html) + /// by the PostgreSQL documentation and may have implications on the chosen query plan + /// for large numbers of joins in the same query. Checkout the documentation of the + /// [`join_collapse_limit` paramater](https://www.postgresql.org/docs/current/runtime-config-query.html#GUC-JOIN-COLLAPSE-LIMIT) + /// to control this behaviour. + /// /// [associations]: crate::associations /// [`allow_tables_to_appear_in_same_query!`]: crate::allow_tables_to_appear_in_same_query! /// @@ -1501,6 +1511,17 @@ pub trait RunQueryDsl: Sized { /// /// When using the query builder, the return type can be /// a tuple of the values, or a struct which implements [`Queryable`]. + /// This type is specified by the first generic type of this function. + /// + /// The second generic type paramater specifies the so called loading mode, + /// which describes how the connection implementation loads data from the database. + /// All connections should provide a implementaiton for + /// [`DefaultLoadingMode`](crate::connection::DefaultLoadingMode). + /// + /// They may provide additional modes. Checkout the documentation of the concrete + /// connection types for details. For connection implementations that provide + /// more than one loading mode it is **required** to specify this generic paramater. + /// This is currently true for `PgConnection`. /// /// When this method is called on [`sql_query`], /// the return type can only be a struct which implements [`QueryableByName`] @@ -1528,8 +1549,10 @@ pub trait RunQueryDsl: Sized { /// # use diesel::insert_into; /// # use schema::users::dsl::*; /// # let connection = &mut establish_connection(); + /// use diesel::connection::DefaultLoadingMode; + /// /// let data = users.select(name) - /// .load_iter::(connection)? + /// .load_iter::(connection)? /// .collect::>>()?; /// assert_eq!(vec!["Sean", "Tess"], data); /// # Ok(()) @@ -1549,8 +1572,10 @@ pub trait RunQueryDsl: Sized { /// # use diesel::insert_into; /// # use schema::users::dsl::*; /// # let connection = &mut establish_connection(); + /// use diesel::connection::DefaultLoadingMode; + /// /// let data = users - /// .load_iter::<(i32, String)>(connection)? + /// .load_iter::<(i32, String), DefaultLoadingMode>(connection)? /// .collect::>>()?; /// let expected_data = vec![ /// (1, String::from("Sean")), @@ -1580,8 +1605,10 @@ pub trait RunQueryDsl: Sized { /// # use diesel::insert_into; /// # use schema::users::dsl::*; /// # let connection = &mut establish_connection(); + /// use diesel::connection::DefaultLoadingMode; + /// /// let data = users - /// .load_iter::(connection)? + /// .load_iter::(connection)? /// .collect::>>()?; /// let expected_data = vec![ /// User { id: 1, name: String::from("Sean") }, @@ -1591,13 +1618,13 @@ pub trait RunQueryDsl: Sized { /// # Ok(()) /// # } /// ``` - fn load_iter<'conn, 'query: 'conn, U>( + fn load_iter<'conn, 'query: 'conn, U, B>( self, conn: &'conn mut Conn, - ) -> QueryResult> + ) -> QueryResult> where U: 'conn, - Self: LoadQuery<'query, Conn, U> + 'conn, + Self: LoadQuery<'query, Conn, U, B> + 'conn, { self.internal_load(conn) } diff --git a/diesel/src/query_source/aliasing/alias.rs b/diesel/src/query_source/aliasing/alias.rs index 1b256ec25a64..7167dbd59093 100644 --- a/diesel/src/query_source/aliasing/alias.rs +++ b/diesel/src/query_source/aliasing/alias.rs @@ -13,7 +13,7 @@ use std::marker::PhantomData; #[derive(Debug, Clone, Copy, Default)] /// Represents an alias within diesel's query builder /// -/// See [alias!] for more details. +/// See [`alias!`](crate::alias) for more details. pub struct Alias { pub(crate) source: S, } diff --git a/diesel/src/query_source/aliasing/aliased_field.rs b/diesel/src/query_source/aliasing/aliased_field.rs index 0058885b5bbd..b8dc1c34b04f 100644 --- a/diesel/src/query_source/aliasing/aliased_field.rs +++ b/diesel/src/query_source/aliasing/aliased_field.rs @@ -16,7 +16,7 @@ use std::marker::PhantomData; #[derive(Debug, Clone, Copy)] /// Represents an aliased field (column) within diesel's query builder /// -/// See [alias!] for more details. +/// See [`alias!`](crate::alias) for more details. pub struct AliasedField { pub(super) _alias_source: PhantomData, pub(super) _field: F, diff --git a/diesel/src/query_source/mod.rs b/diesel/src/query_source/mod.rs index 1a1a07eebbeb..aaf1be08060f 100644 --- a/diesel/src/query_source/mod.rs +++ b/diesel/src/query_source/mod.rs @@ -32,6 +32,9 @@ pub trait QuerySource { /// The actual `FROM` clause of this type. This is typically only called in /// `QueryFragment` implementations. + // from here is something different than from in rust + // as this literally refercs to SQL from. + #[allow(clippy::wrong_self_convention)] fn from_clause(&self) -> Self::FromClause; /// The default select clause of this type, which should be used if no /// select clause was explicitly specified. This should always be a tuple of diff --git a/diesel/src/r2d2.rs b/diesel/src/r2d2.rs index ddac7c96e818..1256832d0a83 100644 --- a/diesel/src/r2d2.rs +++ b/diesel/src/r2d2.rs @@ -78,16 +78,18 @@ //! //! When used inside a pool, if an individual connection becomes //! broken (as determined by the [R2D2Connection::is_broken] method) -//! then `r2d2` will close and return the connection to the DB. +//! then, when the connection goes out of scope, `r2d2` will close +//! and return the connection to the DB. //! //! `diesel` determines broken connections by whether or not the current //! thread is panicking or if individual `Connection` structs are //! broken (determined by the `is_broken()` method). Generically, these //! are left to individual backends to implement themselves. //! -//! For SQLite, PG, and MySQL backends, specifically, `is_broken()` -//! is determined by whether or not the `TransactionManagerStatus` (as a part -//! of the `AnsiTransactionManager` struct) is in an `InError` state. +//! For SQLite, PG, and MySQL backends `is_broken()` is determined +//! by whether or not the `TransactionManagerStatus` (as a part +//! of the `AnsiTransactionManager` struct) is in an `InError` state +//! or contains an open transaction when the connection goes out of scope. //! pub use r2d2::*; @@ -103,9 +105,9 @@ use std::fmt; use std::marker::PhantomData; use crate::backend::Backend; -use crate::connection::commit_error_processor::{CommitErrorOutcome, CommitErrorProcessor}; use crate::connection::{ - ConnectionGatWorkaround, SimpleConnection, TransactionManager, TransactionManagerStatus, + ConnectionGatWorkaround, LoadConnection, LoadRowIter, SimpleConnection, TransactionManager, + TransactionManagerStatus, }; use crate::expression::QueryMetadata; use crate::prelude::*; @@ -209,24 +211,14 @@ where } } -impl<'conn, 'query, DB, M> ConnectionGatWorkaround<'conn, 'query, DB> for PooledConnection +impl<'conn, 'query, DB, M, B> ConnectionGatWorkaround<'conn, 'query, DB, B> for PooledConnection where M: ManageConnection, - M::Connection: Connection, + M::Connection: Connection + ConnectionGatWorkaround<'conn, 'query, DB, B>, DB: Backend, { - type Cursor = >::Cursor; - type Row = >::Row; -} - -impl CommitErrorProcessor for PooledConnection -where - M: ManageConnection, - M::Connection: R2D2Connection + CommitErrorProcessor + Send + 'static, -{ - fn process_commit_error(&self, error: crate::result::Error) -> CommitErrorOutcome { - (&**self).process_commit_error(error) - } + type Cursor = >::Cursor; + type Row = >::Row; } impl Connection for PooledConnection @@ -244,17 +236,6 @@ where ))) } - fn load<'conn, 'query, T>( - &'conn mut self, - source: T, - ) -> QueryResult<>::Cursor> - where - T: Query + QueryFragment + QueryId + 'query, - Self::Backend: QueryMetadata, - { - (&mut **self).load(source) - } - fn execute_returning_count(&mut self, source: &T) -> QueryResult where T: QueryFragment + QueryId, @@ -273,6 +254,23 @@ where } } +impl LoadConnection for PooledConnection +where + M: ManageConnection, + M::Connection: LoadConnection + R2D2Connection, +{ + fn load<'conn, 'query, T>( + &'conn mut self, + source: T, + ) -> QueryResult> + where + T: Query + QueryFragment + QueryId + 'query, + Self::Backend: QueryMetadata, + { + (&mut **self).load(source) + } +} + #[doc(hidden)] #[allow(missing_debug_implementations)] pub struct PoolTransactionManager(std::marker::PhantomData); @@ -327,6 +325,28 @@ where } } +#[derive(QueryId)] +pub(crate) struct CheckConnectionQuery; + +impl QueryFragment for CheckConnectionQuery +where + DB: Backend, +{ + fn walk_ast<'b>( + &'b self, + mut pass: crate::query_builder::AstPass<'_, 'b, DB>, + ) -> QueryResult<()> { + pass.push_sql("SELECT 1"); + Ok(()) + } +} + +impl Query for CheckConnectionQuery { + type SqlType = crate::sql_types::Integer; +} + +impl RunQueryDsl for CheckConnectionQuery {} + #[cfg(test)] mod tests { use std::sync::mpsc; @@ -394,26 +414,118 @@ mod tests { let query = select("foo".into_sql::()); assert_eq!("foo", query.get_result::(&mut conn).unwrap()); } -} -#[derive(QueryId)] -pub(crate) struct CheckConnectionQuery; + #[test] + fn check_pool_does_actually_hold_connections() { + use std::sync::atomic::{AtomicU32, Ordering}; + + #[derive(Debug)] + struct TestEventHandler { + acquire_count: Arc, + release_count: Arc, + checkin_count: Arc, + checkout_count: Arc, + } -impl QueryFragment for CheckConnectionQuery -where - DB: Backend, -{ - fn walk_ast<'b>( - &'b self, - mut pass: crate::query_builder::AstPass<'_, 'b, DB>, - ) -> QueryResult<()> { - pass.push_sql("SELECT 1"); - Ok(()) - } -} + impl r2d2::HandleEvent for TestEventHandler { + fn handle_acquire(&self, _event: r2d2::event::AcquireEvent) { + self.acquire_count.fetch_add(1, Ordering::Relaxed); + } + fn handle_release(&self, _event: r2d2::event::ReleaseEvent) { + self.release_count.fetch_add(1, Ordering::Relaxed); + } + fn handle_checkout(&self, _event: r2d2::event::CheckoutEvent) { + self.checkout_count.fetch_add(1, Ordering::Relaxed); + } + fn handle_checkin(&self, _event: r2d2::event::CheckinEvent) { + self.checkin_count.fetch_add(1, Ordering::Relaxed); + } + } -impl Query for CheckConnectionQuery { - type SqlType = crate::sql_types::Integer; -} + let acquire_count = Arc::new(AtomicU32::new(0)); + let release_count = Arc::new(AtomicU32::new(0)); + let checkin_count = Arc::new(AtomicU32::new(0)); + let checkout_count = Arc::new(AtomicU32::new(0)); -impl RunQueryDsl for CheckConnectionQuery {} + let handler = Box::new(TestEventHandler { + acquire_count: acquire_count.clone(), + release_count: release_count.clone(), + checkin_count: checkin_count.clone(), + checkout_count: checkout_count.clone(), + }); + + let manager = ConnectionManager::::new(database_url()); + let pool = Pool::builder() + .max_size(1) + .test_on_check_out(true) + .event_handler(handler) + .build(manager) + .unwrap(); + + assert_eq!(acquire_count.load(Ordering::Relaxed), 1); + assert_eq!(release_count.load(Ordering::Relaxed), 0); + assert_eq!(checkin_count.load(Ordering::Relaxed), 0); + assert_eq!(checkout_count.load(Ordering::Relaxed), 0); + + // check that we reuse connections with the pool + { + let conn = pool.get().unwrap(); + + assert_eq!(acquire_count.load(Ordering::Relaxed), 1); + assert_eq!(release_count.load(Ordering::Relaxed), 0); + assert_eq!(checkin_count.load(Ordering::Relaxed), 0); + assert_eq!(checkout_count.load(Ordering::Relaxed), 1); + std::mem::drop(conn); + } + + assert_eq!(acquire_count.load(Ordering::Relaxed), 1); + assert_eq!(release_count.load(Ordering::Relaxed), 0); + assert_eq!(checkin_count.load(Ordering::Relaxed), 1); + assert_eq!(checkout_count.load(Ordering::Relaxed), 1); + + // check that we remove a connection with open transactions from the pool + { + let mut conn = pool.get().unwrap(); + + assert_eq!(acquire_count.load(Ordering::Relaxed), 1); + assert_eq!(release_count.load(Ordering::Relaxed), 0); + assert_eq!(checkin_count.load(Ordering::Relaxed), 1); + assert_eq!(checkout_count.load(Ordering::Relaxed), 2); + + ::TransactionManager::begin_transaction(&mut *conn) + .unwrap(); + } + + // we are not interested in the acquire count here + // as the pool opens a new connection in the background + // that could lead to this test failing if that happens to fast + // (which is sometimes the case for sqlite) + //assert_eq!(acquire_count.load(Ordering::Relaxed), 1); + assert_eq!(release_count.load(Ordering::Relaxed), 1); + assert_eq!(checkin_count.load(Ordering::Relaxed), 2); + assert_eq!(checkout_count.load(Ordering::Relaxed), 2); + + // check that we remove a connection from the pool that was + // open during panicing + #[allow(unreachable_code, unused_variables)] + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let conn = pool.get(); + assert_eq!(acquire_count.load(Ordering::Relaxed), 2); + assert_eq!(release_count.load(Ordering::Relaxed), 1); + assert_eq!(checkin_count.load(Ordering::Relaxed), 2); + assert_eq!(checkout_count.load(Ordering::Relaxed), 3); + panic!(); + std::mem::drop(conn); + })) + .unwrap_err(); + + // we are not interested in the acquire count here + // as the pool opens a new connection in the background + // that could lead to this test failing if that happens to fast + // (which is sometimes the case for sqlite) + //assert_eq!(acquire_count.load(Ordering::Relaxed), 2); + assert_eq!(release_count.load(Ordering::Relaxed), 2); + assert_eq!(checkin_count.load(Ordering::Relaxed), 3); + assert_eq!(checkout_count.load(Ordering::Relaxed), 3); + } +} diff --git a/diesel/src/result.rs b/diesel/src/result.rs index 7a0a12925fdf..f864fb136365 100644 --- a/diesel/src/result.rs +++ b/diesel/src/result.rs @@ -62,11 +62,21 @@ pub enum Error { /// by PostgreSQL. SerializationError(Box), - /// An error occurred during the rollback of a transaction. + /// An error occurred when attempting rollback of a transaction subsequently to a failed + /// commit attempt. /// - /// An example of when this error would be returned is if a rollback has - /// already be called on the current transaction. - RollbackError(Box), + /// When a commit attempt fails and Diesel beleives that it can attempt a rollback to return + /// the connection back in a usable state (out of that transaction), it attempts it then + /// returns the original error. + /// + /// If that fails, you get this. + RollbackErrorOnCommit { + /// The error that was encoutered when attempting the rollback + rollback_error: Box, + /// If the rollback attempt resulted from a failed attempt to commit the transaction, + /// you will find the related error here. + commit_error: Box, + }, /// Roll back the current transaction. /// @@ -84,20 +94,8 @@ pub enum Error { /// when no transaction was open NotInTransaction, - /// Transaction broken, likely due to a broken connection. No other operations are possible. - BrokenTransaction, - - /// Commiting a transaction failed - /// - /// The transaction manager will try to perform - /// a rollback in such cases. Indications about the success - /// of this can be extracted from this error variant - CommitTransactionFailed { - /// Failure message of the commit attempt - commit_error: Box, - /// Outcome of the rollback attempt - rollback_result: Box>, - }, + /// Transaction manager broken, likely due to a broken connection. No other operations are possible. + BrokenTransactionManager, } #[derive(Debug, Clone, Copy)] @@ -314,9 +312,22 @@ impl Display for Error { Error::QueryBuilderError(ref e) => e.fmt(f), Error::DeserializationError(ref e) => e.fmt(f), Error::SerializationError(ref e) => e.fmt(f), - Error::RollbackError(ref e) => e.fmt(f), - Error::RollbackTransaction => write!(f, "The current transaction was aborted"), - Error::BrokenTransaction => write!(f, "The current transaction is broken"), + Error::RollbackErrorOnCommit { + ref rollback_error, + ref commit_error, + } => { + write!( + f, + "Transaction rollback failed: {} \ + (rollback attempted because of failure to commit: {})", + &**rollback_error, &**commit_error + )?; + Ok(()) + } + Error::RollbackTransaction => { + write!(f, "You have asked diesel to rollback the transaction") + } + Error::BrokenTransactionManager => write!(f, "The transaction manager is broken"), Error::AlreadyInTransaction => write!( f, "Cannot perform this operation while a transaction is open", @@ -324,20 +335,6 @@ impl Display for Error { Error::NotInTransaction => { write!(f, "Cannot perform this operation outside of a transaction",) } - Error::CommitTransactionFailed { - ref commit_error, - ref rollback_result, - } => { - write!( - f, - "Commiting the current transaction failed: {}", - commit_error - )?; - match &**rollback_result { - Ok(()) => write!(f, " Rollback attempt was succesful"), - Err(e) => write!(f, " Rollback attempt failed with {}", e), - } - } } } } diff --git a/diesel/src/sqlite/backend.rs b/diesel/src/sqlite/backend.rs index 0e9014a6855f..f2550de61aae 100644 --- a/diesel/src/sqlite/backend.rs +++ b/diesel/src/sqlite/backend.rs @@ -57,9 +57,9 @@ impl SqlDialect for Sqlite { #[cfg(not(feature = "returning_clauses_for_sqlite_3_35"))] type ReturningClause = sql_dialect::returning_clause::DoesNotSupportReturningClause; #[cfg(feature = "returning_clauses_for_sqlite_3_35")] - type ReturningClause = sql_dialect::returning_clause::PgLikeReturningClause; + type ReturningClause = SqliteReturningClause; - type OnConflictClause = SqliteOnConflictClaues; + type OnConflictClause = SqliteOnConflictClause; type InsertWithDefaultKeyword = sql_dialect::default_keyword_for_insert::DoesNotSupportDefaultKeyword; @@ -68,16 +68,21 @@ impl SqlDialect for Sqlite { type EmptyFromClauseSyntax = sql_dialect::from_clause_syntax::AnsiSqlFromClauseSyntax; type ExistsSyntax = sql_dialect::exists_syntax::AnsiSqlExistsSyntax; - type ArrayComparision = sql_dialect::array_comparision::AnsiSqlArrayComparison; + type ArrayComparison = sql_dialect::array_comparison::AnsiSqlArrayComparison; } impl DieselReserveSpecialization for Sqlite {} impl TrustedBackend for Sqlite {} #[derive(Debug, Copy, Clone)] -pub struct SqliteOnConflictClaues; +pub struct SqliteOnConflictClause; -impl sql_dialect::on_conflict_clause::SupportsOnConflictClause for SqliteOnConflictClaues {} +impl sql_dialect::on_conflict_clause::SupportsOnConflictClause for SqliteOnConflictClause {} #[derive(Debug, Copy, Clone)] pub struct SqliteBatchInsert; + +#[derive(Debug, Copy, Clone)] +pub struct SqliteReturningClause; + +impl sql_dialect::returning_clause::SupportsReturningClause for SqliteReturningClause {} diff --git a/diesel/src/sqlite/connection/functions.rs b/diesel/src/sqlite/connection/functions.rs index 6fed6485f4f2..1fdf00043a77 100644 --- a/diesel/src/sqlite/connection/functions.rs +++ b/diesel/src/sqlite/connection/functions.rs @@ -96,6 +96,9 @@ where Args::build_from_row(&row).map_err(Error::DeserializationError) } +// clippy is wrong here, the let binding is required +// for lifetime reasons +#[allow(clippy::let_unit_value)] pub(super) fn process_sql_function_result( result: &'_ Ret, ) -> QueryResult> diff --git a/diesel/src/sqlite/connection/mod.rs b/diesel/src/sqlite/connection/mod.rs index 3d85958519ba..509e463fb6f6 100644 --- a/diesel/src/sqlite/connection/mod.rs +++ b/diesel/src/sqlite/connection/mod.rs @@ -18,9 +18,6 @@ use self::raw::RawConnection; use self::statement_iterator::*; use self::stmt::{Statement, StatementUse}; use super::SqliteAggregateFunction; -use crate::connection::commit_error_processor::{ - default_process_commit_error, CommitErrorOutcome, CommitErrorProcessor, -}; use crate::connection::statement_cache::StatementCache; use crate::connection::*; use crate::deserialize::{FromSqlRow, StaticallySizedRow}; @@ -37,6 +34,83 @@ use crate::sqlite::Sqlite; /// - File paths (`test.db`) /// - [URIs](https://sqlite.org/uri.html) (`file://test.db`) /// - Special identifiers (`:memory:`) +/// +/// # Supported loading model implementations +/// +/// * [`DefaultLoadingMode`] +/// +/// As `SqliteConnection` only supports a single loading mode implementation +/// it is **not required** to explicitly specify a loading mode +/// when calling [`RunQueryDsl::load_iter()`] or [`LoadConnection::load`] +/// +/// [`RunQueryDsl::load_iter()`]: crate::query_dsl::RunQueryDsl::load_iter +/// +/// ## DefaultLoadingMode +/// +/// `SqliteConnection` only supports a single loading mode, which loads +/// values row by row from the result set. +/// +/// ```rust +/// # include!("../../doctest_setup.rs"); +/// # +/// # fn main() { +/// # run_test().unwrap(); +/// # } +/// # +/// # fn run_test() -> QueryResult<()> { +/// # use schema::users; +/// # let connection = &mut establish_connection(); +/// use diesel::connection::DefaultLoadingMode; +/// { // scope to restrict the lifetime of the iterator +/// let iter1 = users::table.load_iter::<(i32, String), DefaultLoadingMode>(connection)?; +/// +/// for r in iter1 { +/// let (id, name) = r?; +/// println!("Id: {} Name: {}", id, name); +/// } +/// } +/// +/// // works without specifying the loading mode +/// let iter2 = users::table.load_iter::<(i32, String), _>(connection)?; +/// +/// for r in iter2 { +/// let (id, name) = r?; +/// println!("Id: {} Name: {}", id, name); +/// } +/// # Ok(()) +/// # } +/// ``` +/// +/// This mode does **not support** creating +/// multiple iterators using the same connection. +/// +/// ```compile_fail +/// # include!("../../doctest_setup.rs"); +/// # +/// # fn main() { +/// # run_test().unwrap(); +/// # } +/// # +/// # fn run_test() -> QueryResult<()> { +/// # use schema::users; +/// # let connection = &mut establish_connection(); +/// use diesel::connection::DefaultLoadingMode; +/// +/// let iter1 = users::table.load_iter::<(i32, String), DefaultLoadingMode>(connection)?; +/// let iter2 = users::table.load_iter::<(i32, String), DefaultLoadingMode>(connection)?; +/// +/// for r in iter1 { +/// let (id, name) = r?; +/// println!("Id: {} Name: {}", id, name); +/// } +/// +/// for r in iter2 { +/// let (id, name) = r?; +/// println!("Id: {} Name: {}", id, name); +/// } +/// # Ok(()) +/// # } +/// ``` #[allow(missing_debug_implementations)] #[cfg(feature = "sqlite")] pub struct SqliteConnection { @@ -64,18 +138,6 @@ impl<'conn, 'query> ConnectionGatWorkaround<'conn, 'query, Sqlite> for SqliteCon type Row = self::row::SqliteRow<'conn, 'query>; } -impl CommitErrorProcessor for SqliteConnection { - fn process_commit_error(&self, error: Error) -> CommitErrorOutcome { - let state = match self.transaction_state.status { - TransactionManagerStatus::InError => { - return CommitErrorOutcome::Throw(Error::BrokenTransaction) - } - TransactionManagerStatus::Valid(ref v) => v, - }; - default_process_commit_error(state, error) - } -} - impl Connection for SqliteConnection { type Backend = Sqlite; type TransactionManager = AnsiTransactionManager; @@ -100,20 +162,6 @@ impl Connection for SqliteConnection { Ok(conn) } - fn load<'conn, 'query, T>( - &'conn mut self, - source: T, - ) -> QueryResult> - where - T: Query + QueryFragment + QueryId + 'query, - Self::Backend: QueryMetadata, - { - let statement_use = self.prepared_query(source)?; - - Ok(StatementIterator::new(statement_use)) - } - - #[doc(hidden)] fn execute_returning_count(&mut self, source: &T) -> QueryResult where T: QueryFragment + QueryId, @@ -132,6 +180,21 @@ impl Connection for SqliteConnection { } } +impl LoadConnection for SqliteConnection { + fn load<'conn, 'query, T>( + &'conn mut self, + source: T, + ) -> QueryResult> + where + T: Query + QueryFragment + QueryId + 'query, + Self::Backend: QueryMetadata, + { + let statement_use = self.prepared_query(source)?; + + Ok(StatementIterator::new(statement_use)) + } +} + #[cfg(feature = "r2d2")] impl crate::r2d2::R2D2Connection for crate::sqlite::SqliteConnection { fn ping(&mut self) -> QueryResult<()> { @@ -141,11 +204,15 @@ impl crate::r2d2::R2D2Connection for crate::sqlite::SqliteConnection { } fn is_broken(&mut self) -> bool { - self.transaction_state - .status - .transaction_depth() - .map(|d| d.is_none()) - .unwrap_or(true) + match self.transaction_state.status.transaction_depth() { + // all transactions are closed + // so we don't consider this connection broken + Ok(None) => false, + // The transaction manager is in an error state + // or contains an open transaction + // Therefore we consider this connection broken + Err(_) | Ok(Some(_)) => true, + } } } @@ -453,7 +520,7 @@ mod tests { #[test] fn register_noarg_function() { let connection = &mut SqliteConnection::establish(":memory:").unwrap(); - answer::register_impl(&connection, || 42).unwrap(); + answer::register_impl(connection, || 42).unwrap(); let answer = crate::select(answer()).get_result::(connection); assert_eq!(Ok(42), answer); @@ -462,7 +529,7 @@ mod tests { #[test] fn register_nondeterministic_noarg_function() { let connection = &mut SqliteConnection::establish(":memory:").unwrap(); - answer::register_nondeterministic_impl(&connection, || 42).unwrap(); + answer::register_nondeterministic_impl(connection, || 42).unwrap(); let answer = crate::select(answer()).get_result::(connection); assert_eq!(Ok(42), answer); diff --git a/diesel/src/sqlite/connection/raw.rs b/diesel/src/sqlite/connection/raw.rs index d7571b58f8f0..247f65abaa73 100644 --- a/diesel/src/sqlite/connection/raw.rs +++ b/diesel/src/sqlite/connection/raw.rs @@ -475,7 +475,6 @@ extern "C" fn run_aggregator_final_function for SqliteQueryBuilder { fn push_identifier(&mut self, identifier: &str) -> QueryResult<()> { self.push_sql("`"); - self.push_sql(&identifier.replace("`", "``")); + self.push_sql(&identifier.replace('`', "``")); self.push_sql("`"); Ok(()) } diff --git a/diesel/src/sqlite/query_builder/returning.rs b/diesel/src/sqlite/query_builder/returning.rs new file mode 100644 index 000000000000..fa626f840d14 --- /dev/null +++ b/diesel/src/sqlite/query_builder/returning.rs @@ -0,0 +1,18 @@ +use crate::backend::Backend; +use crate::query_builder::returning_clause::ReturningClause; +use crate::query_builder::{AstPass, QueryFragment}; +use crate::result::QueryResult; +use crate::sqlite::backend::SqliteReturningClause; + +impl QueryFragment for ReturningClause +where + DB: Backend, + Expr: QueryFragment, +{ + fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> { + out.skip_from(true); + out.push_sql(" RETURNING "); + self.0.walk_ast(out.reborrow())?; + Ok(()) + } +} diff --git a/diesel/src/type_impls/option.rs b/diesel/src/type_impls/option.rs index 929255311f10..b61615a8309e 100644 --- a/diesel/src/type_impls/option.rs +++ b/diesel/src/type_impls/option.rs @@ -5,6 +5,7 @@ use crate::expression::*; use crate::query_builder::QueryId; use crate::serialize::{self, IsNull, Output, ToSql}; use crate::sql_types::{is_nullable, HasSqlType, Nullable, SingleValue, SqlType}; +use crate::NullableExpressionMethods; impl HasSqlType> for DB where @@ -109,15 +110,25 @@ where } } -#[cfg(all(test, feature = "postgres"))] -use crate::pg::Pg; -#[cfg(all(test, feature = "postgres"))] -use crate::sql_types; +impl Selectable for Option +where + DB: Backend, + T: Selectable, + crate::dsl::Nullable: Expression, +{ + type SelectExpression = crate::dsl::Nullable; + + fn construct_selection() -> Self::SelectExpression { + T::construct_selection().nullable() + } +} #[test] #[cfg(feature = "postgres")] fn option_to_sql() { + use crate::pg::Pg; use crate::query_builder::bind_collector::ByteWrapper; + use crate::sql_types; type Type = sql_types::Nullable; diff --git a/diesel/src/type_impls/tuples.rs b/diesel/src/type_impls/tuples.rs index df6af762ed78..ade4495375e5 100644 --- a/diesel/src/type_impls/tuples.rs +++ b/diesel/src/type_impls/tuples.rs @@ -5,8 +5,8 @@ use crate::deserialize::{ }; use crate::expression::{ is_contained_in_group_by, AppearsOnTable, AsExpression, AsExpressionList, Expression, - IsContainedInGroupBy, QueryMetadata, Selectable, SelectableExpression, TypedExpressionType, - ValidGrouping, + IsContainedInGroupBy, MixedAggregates, QueryMetadata, Selectable, SelectableExpression, + TypedExpressionType, ValidGrouping, }; use crate::insertable::{CanInsertInSingleQuery, InsertValues, Insertable, InsertableOptionHelper}; use crate::query_builder::*; @@ -114,12 +114,6 @@ macro_rules! tuple_impls { const HAS_STATIC_QUERY_ID: bool = $($T::HAS_STATIC_QUERY_ID &&)+ true; } - const _: () = { - #[derive(ValidGrouping)] - #[diesel(foreign_derive)] - struct TupleWrapper<$($T,)*>(($($T,)*)); - }; - impl_valid_grouping_for_tuple_of_columns!($($T,)*); impl<$($T,)+ Tab> UndecoratedInsertRecord for ($($T,)+) @@ -453,49 +447,23 @@ macro_rules! impl_from_sql_row { } macro_rules! impl_valid_grouping_for_tuple_of_columns { - ( - @build - start_ts = [$($ST: ident,)*], - ts = [$T1: ident,], - bounds = [$($bounds: tt)*], - is_aggregate = [$($is_aggregate: tt)*], - ) => { - impl<$($ST,)* Col> IsContainedInGroupByfor ($($ST,)*) - where Col: Column, - $($ST: IsContainedInGroupBy,)* - $($bounds)* - <$T1 as IsContainedInGroupBy>::Output: is_contained_in_group_by::IsAny<$($is_aggregate)*>, + ($T1: ident, $($T: ident,)+) => { + impl<$T1, $($T,)* __GroupByClause> ValidGrouping<__GroupByClause> for ($T1, $($T,)*) + where + $T1: ValidGrouping<__GroupByClause>, + ($($T,)*): ValidGrouping<__GroupByClause>, + $T1::IsAggregate: MixedAggregates<<($($T,)*) as ValidGrouping<__GroupByClause>>::IsAggregate>, { - type Output = <<$T1 as IsContainedInGroupBy>::Output as is_contained_in_group_by::IsAny<$($is_aggregate)*>>::Output; - } - }; - ( - @build - start_ts = [$($ST: ident,)*], - ts = [$T1: ident, $($T: ident,)+], - bounds = [$($bounds: tt)*], - is_aggregate = [$($is_aggregate: tt)*], - ) => { - impl_valid_grouping_for_tuple_of_columns! { - @build - start_ts = [$($ST,)*], - ts = [$($T,)*], - bounds = [ - $($bounds)* - <$T1 as IsContainedInGroupBy>::Output: is_contained_in_group_by::IsAny<$($is_aggregate)*>, - ], - is_aggregate = [ - <<$T1 as IsContainedInGroupBy>::Output as is_contained_in_group_by::IsAny<$($is_aggregate)*>>::Output - ], + type IsAggregate = <$T1::IsAggregate as MixedAggregates<<($($T,)*) as ValidGrouping<__GroupByClause>>::IsAggregate>>::Output; } - }; - ($T1: ident, $($T: ident,)+) => { - impl_valid_grouping_for_tuple_of_columns! { - @build - start_ts = [$T1, $($T,)*], - ts = [$($T,)*], - bounds = [], - is_aggregate = [<$T1 as IsContainedInGroupBy>::Output], + + impl<$T1, $($T,)* Col> IsContainedInGroupByfor ($T1, $($T,)*) + where Col: Column, + ($($T,)*): IsContainedInGroupBy, + $T1: IsContainedInGroupBy, + $T1::Output: is_contained_in_group_by::IsAny<<($($T,)*) as IsContainedInGroupBy>::Output> + { + type Output = <$T1::Output as is_contained_in_group_by::IsAny<<($($T,)*) as IsContainedInGroupBy>::Output>>::Output; } }; ($T1: ident,) => { @@ -505,6 +473,12 @@ macro_rules! impl_valid_grouping_for_tuple_of_columns { { type Output = <$T1 as IsContainedInGroupBy>::Output; } + + impl<$T1, __GroupByClause> ValidGrouping<__GroupByClause> for ($T1,) + where $T1: ValidGrouping<__GroupByClause> + { + type IsAggregate = $T1::IsAggregate; + } }; } diff --git a/diesel_bench/Cargo.toml b/diesel_bench/Cargo.toml index 3985a3adcffe..887f6d38d79a 100644 --- a/diesel_bench/Cargo.toml +++ b/diesel_bench/Cargo.toml @@ -22,7 +22,7 @@ quaint = {version = "=0.2.0-alpha.13", optional = true} serde = {version = "1", optional = true, features = ["derive"]} sea-orm = {version = "0.7", optional = true, features = ["runtime-tokio-rustls"]} futures = {version = "0.3", optional = true} -diesel-async = {git = "https://github.com/weiznich/diesel_async", rev = "61183dc252e85935d3605f1e3206cd7065d456de", optional = true, default-features = false} +#diesel-async = {git = "https://github.com/weiznich/diesel_async", rev = "61183dc252e85935d3605f1e3206cd7065d456de", optional = true, default-features = false} criterion-perf-events = { version = "0.2", optional = true} perfcnt = {version = "0.8", optional = true} @@ -54,4 +54,4 @@ fast_run = [] [replace] "quaint:0.2.0-alpha.13" = {git = "https://github.com/prisma/quaint", rev = "e077df3"} -"https://github.com/diesel-rs/diesel#2.0.0-rc.0" = { path = "../diesel"} +#"https://github.com/diesel-rs/diesel#2.0.0-rc.0" = { path = "../diesel"} diff --git a/diesel_cli/Cargo.toml b/diesel_cli/Cargo.toml index 854705ebea27..b5edc6196897 100644 --- a/diesel_cli/Cargo.toml +++ b/diesel_cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "diesel_cli" -version = "2.0.0-rc.0" +version = "2.0.0-rc.1" license = "MIT OR Apache-2.0" description = "Provides the CLI for the Diesel crate" readme = "README.md" @@ -11,7 +11,7 @@ keywords = ["diesel", "migrations", "cli"] autotests = false edition = "2018" include = ["src/**/*", "LICENSE-*", "README.md"] -rust-version = "1.54.0" +rust-version = "1.56.0" [[bin]] name = "diesel" @@ -20,25 +20,25 @@ doc = false [dependencies] chrono = { version = "0.4.19", default-features = false, features = ["clock", "std"] } -clap = "3.1" -clap_complete = "3.1" +clap = "3.2.1" +clap_complete = "3.2" dotenvy = "0.15" heck = "0.4.0" serde = { version = "1.0.0", features = ["derive"] } toml = "0.5" url = { version = "2.1.0", optional = true } -libsqlite3-sys = { version = ">=0.17.2, <0.25.0", optional = true } +libsqlite3-sys = { version = ">=0.17.2, <0.26.0", optional = true } diffy = "0.2.0" regex = "1.0.6" serde_regex = "1.1" [dependencies.diesel] -version = "~2.0.0-rc.0" +version = "~2.0.0-rc.1" path = "../diesel" default-features = false [dependencies.diesel_migrations] -version = "~2.0.0-rc.0" +version = "~2.0.0-rc.1" path = "../diesel_migrations/" [dev-dependencies] diff --git a/diesel_cli/src/cli.rs b/diesel_cli/src/cli.rs index 52057793a8ca..e86aeb1ce864 100644 --- a/diesel_cli/src/cli.rs +++ b/diesel_cli/src/cli.rs @@ -1,11 +1,9 @@ -use crate::validators::num::*; -use clap::{Arg, Command}; +use clap::{ + builder::{EnumValueParser, PossibleValuesParser}, + Arg, Command, +}; use clap_complete::Shell; -fn str_as_char(str: &str) -> char { - str.chars().next().unwrap() -} - pub fn build_cli() -> Command<'static> { let database_arg = Arg::new("DATABASE_URL") .long("database-url") @@ -29,7 +27,7 @@ pub fn build_cli() -> Command<'static> { .arg( Arg::new("REVERT_ALL") .long("all") - .short(str_as_char("a")) + .short('a') .help("Reverts previously run migration files.") .takes_value(false) .conflicts_with("REVERT_NUMBER"), @@ -37,7 +35,7 @@ pub fn build_cli() -> Command<'static> { .arg( Arg::new("REVERT_NUMBER") .long("number") - .short(str_as_char("n")) + .short('n') .help("Reverts the last `n` migration files.") .long_help( "When this option is specified the last `n` migration files \ @@ -45,7 +43,7 @@ pub fn build_cli() -> Command<'static> { ) .default_value("1") .takes_value(true) - .validator(is_positive_int) + .value_parser(clap::value_parser!(u64)) .conflicts_with("REVERT_ALL"), ), ) @@ -58,7 +56,7 @@ pub fn build_cli() -> Command<'static> { .arg( Arg::new("REDO_ALL") .long("all") - .short(str_as_char("a")) + .short('a') .help("Reverts and re-runs all migrations.") .long_help( "When this option is specified all migrations \ @@ -71,7 +69,7 @@ pub fn build_cli() -> Command<'static> { .arg( Arg::new("REDO_NUMBER") .long("number") - .short(str_as_char("n")) + .short('n') .help("Redo the last `n` migration files.") .long_help( "When this option is specified the last `n` migration files \ @@ -79,7 +77,7 @@ pub fn build_cli() -> Command<'static> { ) .default_value("1") .takes_value(true) - .validator(is_positive_int) + .value_parser(clap::value_parser!(u64)) .conflicts_with("REDO_ALL"), ), ) @@ -111,10 +109,21 @@ pub fn build_cli() -> Command<'static> { ) .takes_value(true), ) + .arg( + Arg::new("MIGRATION_NO_DOWN_FILE") + .short('u') // only Up + .long("no-down") + .help( + "Don't generate a down.sql file. \ + You won't be able to run migration `revert` or `redo`.", + ) + .takes_value(false), + ) .arg( Arg::new("MIGRATION_FORMAT") .long("format") - .possible_values(&["sql", "barrel"]) + .value_parser(PossibleValuesParser::new(["sql"])) + .takes_value(true) .default_value("sql") .takes_value(true) .help("The format of the migration to be generated."), @@ -154,7 +163,7 @@ pub fn build_cli() -> Command<'static> { Arg::new("SHELL") .index(1) .required(true) - .possible_values(Shell::possible_values()), + .value_parser(EnumValueParser::::new()), ); let infer_schema_subcommand = Command::new("print-schema") @@ -162,7 +171,7 @@ pub fn build_cli() -> Command<'static> { .arg( Arg::new("schema") .long("schema") - .short(str_as_char("s")) + .short('s') .takes_value(true) .help("The name of the schema."), ) @@ -171,19 +180,19 @@ pub fn build_cli() -> Command<'static> { .index(1) .takes_value(true) .multiple_values(true) - .multiple_occurrences(true) + .action(clap::ArgAction::Append) .help("Table names to filter (default only-tables if not empty)."), ) .arg( Arg::new("only-tables") - .short(str_as_char("o")) + .short('o') .long("only-tables") .help("Only include tables from table-name that matches regexp.") .conflicts_with("except-tables"), ) .arg( Arg::new("except-tables") - .short(str_as_char("e")) + .short('e') .long("except-tables") .help("Exclude tables from table-name that matches regex.") .conflicts_with("only-tables"), @@ -198,12 +207,13 @@ pub fn build_cli() -> Command<'static> { .long("column-sorting") .help("Sort order for table columns.") .takes_value(true) - .possible_values(&["ordinal_position", "name"]), + .value_parser(PossibleValuesParser::new(["ordinal_position", "name"])), ) .arg( Arg::new("patch-file") .long("patch-file") .takes_value(true) + .value_parser(clap::value_parser!(std::path::PathBuf)) .help("A unified diff file to be applied to the final schema."), ) .arg( @@ -211,7 +221,7 @@ pub fn build_cli() -> Command<'static> { .long("import-types") .takes_value(true) .multiple_values(true) - .multiple_occurrences(true) + .action(clap::ArgAction::Append) .number_of_values(1) .help("A list of types to import for every table, separated by commas."), ) @@ -222,6 +232,7 @@ pub fn build_cli() -> Command<'static> { ); let config_arg = Arg::new("CONFIG_FILE") + .value_parser(clap::value_parser!(std::path::PathBuf)) .long("config-file") .help( "The location of the configuration file to use. Falls back to the \ @@ -269,5 +280,6 @@ fn migration_dir_arg<'a>() -> Arg<'a> { current directory and its parents.", ) .takes_value(true) + .value_parser(clap::value_parser!(std::path::PathBuf)) .global(true) } diff --git a/diesel_cli/src/config.rs b/diesel_cli/src/config.rs index 8d97386b928b..0c4224fdf545 100644 --- a/diesel_cli/src/config.rs +++ b/diesel_cli/src/config.rs @@ -22,8 +22,8 @@ pub struct Config { impl Config { pub fn file_path(matches: &ArgMatches) -> PathBuf { matches - .value_of("CONFIG_FILE") - .map(PathBuf::from) + .get_one::("CONFIG_FILE") + .cloned() .or_else(|| env::var_os("DIESEL_CONFIG_FILE").map(PathBuf::from)) .unwrap_or_else(|| find_project_root().unwrap_or_default().join("diesel.toml")) } diff --git a/diesel_cli/src/database.rs b/diesel_cli/src/database.rs index ab5ab25be33e..c8f646328814 100644 --- a/diesel_cli/src/database.rs +++ b/diesel_cli/src/database.rs @@ -122,13 +122,6 @@ impl InferConnection { } macro_rules! call_with_conn { - ( - $database_url:expr, - $($func:ident)::+ - ) => { - call_with_conn!($database_url, $($func)::+ ()) - }; - ( $database_url:expr, $($func:ident)::+ ($($args:expr),*) @@ -353,8 +346,8 @@ pub fn schema_table_exists(database_url: &str) -> DatabaseResult { pub fn database_url(matches: &ArgMatches) -> String { matches - .value_of("DATABASE_URL") - .map(|s| s.into()) + .get_one::("DATABASE_URL") + .cloned() .or_else(|| env::var("DATABASE_URL").ok()) .unwrap_or_else(|| handle_error(DatabaseError::DatabaseUrlMissing)) } diff --git a/diesel_cli/src/infer_schema_internals/inference.rs b/diesel_cli/src/infer_schema_internals/inference.rs index ae74e0147999..4b6b5da313ff 100644 --- a/diesel_cli/src/infer_schema_internals/inference.rs +++ b/diesel_cli/src/infer_schema_internals/inference.rs @@ -97,7 +97,7 @@ fn get_column_information( } }; if let Err(NotFound) = column_info { - Err(format!("no table exists named {}", table.to_string()).into()) + Err(format!("no table exists named {}", table).into()) } else { column_info.map_err(Into::into) } @@ -137,7 +137,7 @@ pub(crate) fn get_primary_keys( Err(format!( "Diesel only supports tables with primary keys. \ Table {} has no primary key", - table.to_string() + table, ) .into()) } else { diff --git a/diesel_cli/src/infer_schema_internals/information_schema.rs b/diesel_cli/src/infer_schema_internals/information_schema.rs index cf4b2ef22503..aeff76e95627 100644 --- a/diesel_cli/src/infer_schema_internals/information_schema.rs +++ b/diesel_cli/src/infer_schema_internals/information_schema.rs @@ -2,9 +2,10 @@ use std::borrow::Cow; use std::error::Error; use diesel::backend::Backend; +use diesel::connection::LoadConnection; use diesel::deserialize::{FromSql, FromSqlRow}; use diesel::dsl::*; -use diesel::expression::{is_aggregate, MixedAggregates, QueryMetadata, ValidGrouping}; +use diesel::expression::{is_aggregate, QueryMetadata, ValidGrouping}; #[cfg(feature = "mysql")] use diesel::mysql::Mysql; #[cfg(feature = "postgres")] @@ -36,7 +37,7 @@ pub trait UsesInformationSchema: Backend { fn default_schema(conn: &mut C) -> QueryResult where - C: Connection, + C: LoadConnection, String: FromSql; } @@ -76,7 +77,7 @@ impl UsesInformationSchema for Mysql { fn default_schema(conn: &mut C) -> QueryResult where - C: Connection, + C: LoadConnection, String: FromSql, { select(database()).get_result(conn) @@ -149,7 +150,7 @@ pub fn get_table_data<'a, Conn>( column_sorting: &ColumnSorting, ) -> QueryResult> where - Conn: Connection, + Conn: LoadConnection, Conn::Backend: UsesInformationSchema, ColumnInformation: FromSqlRow< SqlTypeOf<( @@ -160,10 +161,12 @@ where )>, Conn::Backend, >, - is_aggregate::No: MixedAggregates< - <::TypeSchema as ValidGrouping<()>>::IsAggregate, - Output = is_aggregate::No, - >, + ( + columns::column_name, + ::TypeColumn, + ::TypeSchema, + columns::__is_nullable, + ): ValidGrouping<()>, String: FromSql, Option: FromSql, Conn::Backend>, Order< @@ -230,7 +233,7 @@ where pub fn get_primary_keys<'a, Conn>(conn: &mut Conn, table: &'a TableName) -> QueryResult> where - Conn: Connection, + Conn: LoadConnection, Conn::Backend: UsesInformationSchema, String: FromSql, Order< @@ -280,7 +283,7 @@ pub fn load_table_names<'a, Conn>( schema_name: Option<&'a str>, ) -> Result, Box> where - Conn: Connection, + Conn: LoadConnection, Conn::Backend: UsesInformationSchema + 'static, String: FromSql, Filter< @@ -557,7 +560,7 @@ mod tests { let id = ColumnInformation::new("id", "int4", pg_catalog.clone(), false); let text_col = ColumnInformation::new("text_col", "varchar", pg_catalog.clone(), true); let not_null = ColumnInformation::new("not_null", "text", pg_catalog.clone(), false); - let array_col = ColumnInformation::new("array_col", "_varchar", pg_catalog.clone(), false); + let array_col = ColumnInformation::new("array_col", "_varchar", pg_catalog, false); assert_eq!( Ok(vec![id, text_col, not_null]), get_table_data(&mut connection, &table_1, &ColumnSorting::OrdinalPosition) @@ -592,14 +595,14 @@ mod tests { let table_3 = TableName::new("table_3", "test_schema"); let fk_one = ForeignKeyConstraint { child_table: table_2.clone(), - parent_table: table_1.clone(), + parent_table: table_1, foreign_key: "fk_one".into(), foreign_key_rust_name: "fk_one".into(), primary_key: "id".into(), }; let fk_two = ForeignKeyConstraint { - child_table: table_3.clone(), - parent_table: table_2.clone(), + child_table: table_3, + parent_table: table_2, foreign_key: "fk_two".into(), foreign_key_rust_name: "fk_two".into(), primary_key: "id".into(), diff --git a/diesel_cli/src/infer_schema_internals/sqlite.rs b/diesel_cli/src/infer_schema_internals/sqlite.rs index 49e8636d9522..144cdd50393f 100644 --- a/diesel_cli/src/infer_schema_internals/sqlite.rs +++ b/diesel_cli/src/infer_schema_internals/sqlite.rs @@ -31,7 +31,7 @@ table! { seq -> Integer, _table -> VarChar, from -> VarChar, - to -> VarChar, + to -> Nullable, on_update -> VarChar, on_delete -> VarChar, _match -> VarChar, @@ -71,20 +71,37 @@ pub fn load_foreign_key_constraints( .into_iter() .map(|child_table| { let query = format!("PRAGMA FOREIGN_KEY_LIST('{}')", child_table.sql_name); - Ok(sql::(&query) + sql::(&query) .load::(connection)? .into_iter() .map(|row| { let parent_table = TableName::from_name(row.parent_table); - ForeignKeyConstraint { + let primary_key = if let Some(primary_key) = row.primary_key { + primary_key + } else { + let mut primary_keys = get_primary_keys(connection, &parent_table)?; + if primary_keys.len() == 1 { + primary_keys + .pop() + .expect("There is exactly one primary key in this list") + } else { + return Err(diesel::result::Error::DatabaseError( + diesel::result::DatabaseErrorKind::Unknown, + Box::new(String::from( + "Found more than one primary key for an implicit reference", + )), + )); + } + }; + Ok(ForeignKeyConstraint { child_table: child_table.clone(), parent_table, foreign_key: row.foreign_key.clone(), foreign_key_rust_name: row.foreign_key, - primary_key: row.primary_key, - } + primary_key, + }) }) - .collect()) + .collect::>() }) .collect::>>>()?; Ok(rows.into_iter().flatten().collect()) @@ -163,7 +180,7 @@ struct ForeignKeyListRow { _seq: i32, parent_table: String, foreign_key: String, - primary_key: String, + primary_key: Option, _on_update: String, _on_delete: String, _match: String, @@ -368,14 +385,14 @@ fn load_foreign_key_constraints_loads_foreign_keys() { let table_3 = TableName::from_name("table_3"); let fk_one = ForeignKeyConstraint { child_table: table_2.clone(), - parent_table: table_1.clone(), + parent_table: table_1, foreign_key: "fk_one".into(), foreign_key_rust_name: "fk_one".into(), primary_key: "id".into(), }; let fk_two = ForeignKeyConstraint { - child_table: table_3.clone(), - parent_table: table_2.clone(), + child_table: table_3, + parent_table: table_2, foreign_key: "fk_two".into(), foreign_key_rust_name: "fk_two".into(), primary_key: "id".into(), diff --git a/diesel_cli/src/main.rs b/diesel_cli/src/main.rs index 0e2346ddea21..1d0155551c7c 100644 --- a/diesel_cli/src/main.rs +++ b/diesel_cli/src/main.rs @@ -11,7 +11,7 @@ clippy::used_underscore_binding, missing_copy_implementations )] -#![cfg_attr(test, allow(clippy::result_unwrap_used))] +#![cfg_attr(test, allow(clippy::unwrap_used))] mod config; @@ -23,7 +23,6 @@ mod infer_schema_internals; mod print_schema; #[cfg(any(feature = "postgres", feature = "mysql"))] mod query_helper; -mod validators; use chrono::*; use clap::ArgMatches; @@ -76,27 +75,24 @@ fn run_migration_command( let database_url = database::database_url(matches); let dir = migrations_dir(matches).unwrap_or_else(handle_error); let dir = FileBasedMigrations::from_path(dir).unwrap_or_else(handle_error); - if args.is_present("REVERT_ALL") { + if args.contains_id("REVERT_ALL") { call_with_conn!(database_url, revert_all_migrations_with_output(dir))?; } else { - let number = args.value_of("REVERT_NUMBER").unwrap(); - for _ in 0..number.parse::().expect("Unable to parse the value of the --number argument. A positive integer is expected.") { - match call_with_conn!( - database_url, - revert_migration_with_output(dir.clone()) - ) { - Ok(_) => {} - Err(e) if e.is::() => { - match e.downcast_ref::() { - // If n is larger then the actual number of migrations, - // just stop reverting them - Some(MigrationError::NoMigrationRun) => break, - _ => return Err(e), - } + let number = args.get_one::("REVERT_NUMBER").unwrap(); + for _ in 0..*number { + match call_with_conn!(database_url, revert_migration_with_output(dir.clone())) { + Ok(_) => {} + Err(e) if e.is::() => { + match e.downcast_ref::() { + // If n is larger then the actual number of migrations, + // just stop reverting them + Some(MigrationError::NoMigrationRun) => break, + _ => return Err(e), } - Err(e) => return Err(e), } + Err(e) => return Err(e), } + } } regenerate_schema_if_file_specified(matches)?; @@ -123,7 +119,7 @@ fn run_migration_command( println!("{:?}", result); } ("generate", args) => { - let migration_name = args.value_of("MIGRATION_NAME").unwrap(); + let migration_name = args.get_one::("MIGRATION_NAME").unwrap(); let version = migration_version(args); let versioned_name = format!("{}_{}", version, migration_name); let migration_dir = migrations_dir(matches) @@ -131,8 +127,14 @@ fn run_migration_command( .join(versioned_name); fs::create_dir(&migration_dir).unwrap(); - match args.value_of("MIGRATION_FORMAT") { - Some("sql") => generate_sql_migration(&migration_dir), + match args + .get_one::("MIGRATION_FORMAT") + .map(|s| s as &str) + { + Some("sql") => generate_sql_migration( + &migration_dir, + !args.contains_id("MIGRATION_NO_DOWN_FILE"), + ), Some(x) => return Err(format!("Unrecognized migration format `{}`", x).into()), None => unreachable!("MIGRATION_FORMAT has a default value"), } @@ -143,7 +145,7 @@ fn run_migration_command( Ok(()) } -fn generate_sql_migration(path: &Path) { +fn generate_sql_migration(path: &Path, with_down: bool) { use std::io::Write; let migration_dir_relative = @@ -157,32 +159,31 @@ fn generate_sql_migration(path: &Path) { let mut up = fs::File::create(up_path).unwrap(); up.write_all(b"-- Your SQL goes here").unwrap(); - let down_path = path.join("down.sql"); - println!( - "Creating {}", - migration_dir_relative.join("down.sql").display() - ); - let mut down = fs::File::create(down_path).unwrap(); - down.write_all(b"-- This file should undo anything in `up.sql`") - .unwrap(); + if with_down { + let down_path = path.join("down.sql"); + println!( + "Creating {}", + migration_dir_relative.join("down.sql").display() + ); + let mut down = fs::File::create(down_path).unwrap(); + down.write_all(b"-- This file should undo anything in `up.sql`") + .unwrap(); + } } fn migration_version<'a>(matches: &'a ArgMatches) -> Box { matches - .value_of("MIGRATION_VERSION") + .get_one::("MIGRATION_VERSION") .map(|s| Box::new(s) as Box) .unwrap_or_else(|| Box::new(Utc::now().format(TIMESTAMP_FORMAT))) } fn migrations_dir_from_cli(matches: &ArgMatches) -> Option { - matches - .value_of("MIGRATION_DIRECTORY") - .map(PathBuf::from) - .or_else(|| { - matches - .subcommand() - .and_then(|s| migrations_dir_from_cli(s.1)) - }) + matches.get_one("MIGRATION_DIRECTORY").cloned().or_else(|| { + matches + .subcommand() + .and_then(|s| migrations_dir_from_cli(s.1)) + }) } fn run_migrations_with_output( @@ -355,11 +356,10 @@ fn run_database_command( } fn generate_completions_command(matches: &ArgMatches) { - let shell: Shell = matches.value_of_t("SHELL").unwrap_or_else(|e| e.exit()); + let shell: &Shell = matches.get_one("SHELL").expect("Shell is set here?"); let mut app = cli::build_cli(); let name = app.get_name().to_string(); - generate(shell, &mut app, name, &mut stdout()); - // cli::build_cli().gen_completions_to("diesel", shell, &mut stdout()); + generate(*shell, &mut app, name, &mut stdout()); } /// Looks for a migrations directory in the current path and all parent paths, @@ -407,11 +407,11 @@ fn redo_migrations( Conn: MigrationHarness + Connection + 'static, { let migrations_inner = |harness: &mut HarnessWithOutput| -> Result<(), Box> { - let reverted_versions = if args.is_present("REDO_ALL") { + let reverted_versions = if args.contains_id("REDO_ALL") { harness.revert_all_migrations(migrations_dir.clone())? } else { - let number = args.value_of("REDO_NUMBER").unwrap(); - (0..number.parse::().expect("Unable to parse the value of the --number argument. A positive integer is expected.")) + let number = args.get_one::("REDO_NUMBER").unwrap(); + (0..*number) .filter_map(|_|{ match harness.revert_last_migration(migrations_dir.clone()) { Ok(v) => { @@ -438,7 +438,7 @@ fn redo_migrations( .map(|m| (m.name().version().as_owned(), m)) .collect::>(); - let migrations = reverted_versions + let mut migrations = reverted_versions .into_iter() .map(|v| { migrations @@ -446,6 +446,8 @@ fn redo_migrations( .ok_or_else(|| MigrationError::UnknownMigrationVersion(v.as_owned())) }) .collect::, _>>()?; + // Sort the migrations by version to apply them in order. + migrations.sort_by_key(|m| m.name().version().as_owned()); harness.run_migrations(&migrations)?; @@ -499,45 +501,45 @@ fn run_infer_schema(matches: &ArgMatches) -> Result<(), Box("schema") { + config.schema = Some(schema_name.clone()) } let filter = matches - .values_of("table-name") + .get_many::("table-name") .unwrap_or_default() .map(|table_name_regex| Regex::new(table_name_regex).map(Into::into)) .collect::>() .map_err(|e| format!("invalid argument for table filtering regex: {}", e)); - if matches.is_present("only-tables") { + if matches.contains_id("only-tables") { config.filter = Filtering::OnlyTables(filter?) - } else if matches.is_present("except-tables") { + } else if matches.contains_id("except-tables") { config.filter = Filtering::ExceptTables(filter?) } - if matches.is_present("with-docs") { + if matches.contains_id("with-docs") { config.with_docs = true; } - if let Some(sorting) = matches.value_of("column-sorting") { - match sorting { + if let Some(sorting) = matches.get_one::("column-sorting") { + match sorting as &str { "ordinal_position" => config.column_sorting = ColumnSorting::OrdinalPosition, "name" => config.column_sorting = ColumnSorting::Name, _ => return Err(format!("Invalid column sorting mode: {}", sorting).into()), } } - if let Some(path) = matches.value_of("patch-file") { - config.patch_file = Some(PathBuf::from(path)); + if let Some(path) = matches.get_one::("patch-file") { + config.patch_file = Some(path.clone()); } - if let Some(types) = matches.values_of("import-types") { - let types = types.map(String::from).collect(); + if let Some(types) = matches.get_many("import-types") { + let types = types.cloned().collect(); config.import_types = Some(types); } - if matches.is_present("generate-custom-type-definitions") { + if matches.contains_id("generate-custom-type-definitions") { config.generate_missing_sql_type_definitions = Some(false); } @@ -558,7 +560,7 @@ fn regenerate_schema_if_file_specified( let database_url = database::database_url(matches); - if matches.is_present("LOCKED_SCHEMA") { + if matches.contains_id("LOCKED_SCHEMA") { let mut buf = Vec::new(); print_schema::run_print_schema(&database_url, &config.print_schema, &mut buf)?; diff --git a/diesel_cli/src/print_schema.rs b/diesel_cli/src/print_schema.rs index a4b5a9694015..0e59f4846069 100644 --- a/diesel_cli/src/print_schema.rs +++ b/diesel_cli/src/print_schema.rs @@ -144,6 +144,8 @@ fn mysql_diesel_types() -> HashSet<&'static str> { types.insert("TinyInt"); types.insert("Tinyint"); + types.insert("Datetime"); + types.insert("Json"); types } diff --git a/diesel_cli/src/validators/mod.rs b/diesel_cli/src/validators/mod.rs deleted file mode 100644 index bed07382f7cc..000000000000 --- a/diesel_cli/src/validators/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub(crate) mod num; diff --git a/diesel_cli/src/validators/num.rs b/diesel_cli/src/validators/num.rs deleted file mode 100644 index 941571a2d88f..000000000000 --- a/diesel_cli/src/validators/num.rs +++ /dev/null @@ -1,41 +0,0 @@ -pub fn is_positive_int(val: &str) -> Result<(), String> { - match val.parse::() { - Ok(val) if val > 0 => Ok(()), - // If the the value is <= 0 or can't be parsed - _ => Err(format!("{} isn't a positive integer.", val)), - } -} - -#[cfg(test)] -mod tests { - use super::is_positive_int; - - #[test] - fn is_positive_int_should_parse_a_positive_integer_from_input_string() { - assert_eq!(is_positive_int("1"), Ok(())) - } - - #[test] - fn is_positive_int_should_throw_an_error_with_zero() { - assert_eq!( - is_positive_int("0"), - Err("0 isn't a positive integer.".to_string()) - ) - } - - #[test] - fn is_positive_int_should_throw_an_error_with_negative_integer() { - assert_eq!( - is_positive_int("-5"), - Err("-5 isn't a positive integer.".to_string()) - ) - } - - #[test] - fn is_positive_int_should_throw_an_error_with_float() { - assert_eq!( - is_positive_int("5.2"), - Err("5.2 isn't a positive integer.".to_string()) - ) - } -} diff --git a/diesel_cli/tests/database_reset.rs b/diesel_cli/tests/database_reset.rs index 83e4436324cb..23a5a8aa697d 100644 --- a/diesel_cli/tests/database_reset.rs +++ b/diesel_cli/tests/database_reset.rs @@ -31,7 +31,7 @@ fn reset_runs_database_setup() { p.create_migration( "12345_create_users_table", "CREATE TABLE users ( id INTEGER )", - "DROP TABLE users", + Some("DROP TABLE users"), ); assert!(db.table_exists("posts")); @@ -101,7 +101,7 @@ fn reset_works_with_migration_dir_by_arg() { "foo", "12345_create_users_table", "CREATE TABLE users ( id INTEGER )", - "DROP TABLE users", + Some("DROP TABLE users"), ); assert!(db.table_exists("posts")); @@ -132,7 +132,7 @@ fn reset_works_with_migration_dir_by_env() { "bar", "12345_create_users_table", "CREATE TABLE users ( id INTEGER )", - "DROP TABLE users", + Some("DROP TABLE users"), ); assert!(db.table_exists("posts")); @@ -214,7 +214,7 @@ fn reset_respects_migrations_dir_from_diesel_toml() { "custom_migrations", "12345_create_users_table", "CREATE TABLE users ( id INTEGER )", - "DROP TABLE users", + Some("DROP TABLE users"), ); assert!(db.table_exists("users")); diff --git a/diesel_cli/tests/database_setup.rs b/diesel_cli/tests/database_setup.rs index eefee8d47e1f..dfa6b4347fd7 100644 --- a/diesel_cli/tests/database_setup.rs +++ b/diesel_cli/tests/database_setup.rs @@ -49,7 +49,7 @@ fn database_setup_runs_migrations_if_no_schema_table() { p.create_migration( "12345_create_users_table", "CREATE TABLE users ( id INTEGER )", - "DROP TABLE users", + Some("DROP TABLE users"), ); // sanity check @@ -96,7 +96,7 @@ fn database_setup_respects_migration_dir_by_arg_to_database() { "foo", "12345_create_users_table", "CREATE TABLE users ( id INTEGER )", - "DROP TABLE users", + Some("DROP TABLE users"), ); // sanity check @@ -128,7 +128,7 @@ fn database_setup_respects_migration_dir_by_arg() { "foo", "12345_create_users_table", "CREATE TABLE users ( id INTEGER )", - "DROP TABLE users", + Some("DROP TABLE users"), ); // sanity check @@ -160,7 +160,7 @@ fn database_setup_respects_migration_dir_by_env() { "bar", "12345_create_users_table", "CREATE TABLE users ( id INTEGER )", - "DROP TABLE users", + Some("DROP TABLE users"), ); // sanity check @@ -199,7 +199,7 @@ fn database_setup_respects_migrations_dir_from_diesel_toml() { "custom_migrations", "12345_create_users_table", "CREATE TABLE users ( id INTEGER )", - "DROP TABLE users", + Some("DROP TABLE users"), ); // sanity check diff --git a/diesel_cli/tests/migration_generate.rs b/diesel_cli/tests/migration_generate.rs index a40fe25a60ae..72b10fbbe692 100644 --- a/diesel_cli/tests/migration_generate.rs +++ b/diesel_cli/tests/migration_generate.rs @@ -69,6 +69,24 @@ fn migration_generate_creates_a_migration_with_initial_contents() { } } +#[test] +fn migration_generate_with_no_down_file_has_no_down_file() { + let p = project("migration_name").folder("migrations").build(); + let result = p + .command("migration") + .arg("generate") + .arg("--no-down") + .arg("hello") + .run(); + assert!(result.is_success(), "Command failed: {:?}", result); + + let migrations = p.migrations(); + let migration = &migrations[0]; + + assert!(migration.path().join("up.sql").exists()); + assert!(!migration.path().join("down.sql").exists()); +} + #[test] fn migration_generate_doesnt_require_database_url_to_be_set() { let p = project("migration_name").folder("migrations").build(); diff --git a/diesel_cli/tests/migration_list.rs b/diesel_cli/tests/migration_list.rs index bc4c94e23f65..e94c0aa46ee3 100644 --- a/diesel_cli/tests/migration_list.rs +++ b/diesel_cli/tests/migration_list.rs @@ -1,3 +1,4 @@ +#![allow(clippy::expect_fun_call)] use chrono::Utc; use std::thread::sleep; use std::time::Duration; @@ -17,7 +18,7 @@ fn migration_list_lists_pending_applied_migrations() { p.create_migration( "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY)", - "DROP TABLE users", + Some("DROP TABLE users"), ); assert!(!db.table_exists("users")); @@ -66,7 +67,7 @@ fn migration_list_lists_migrations_ordered_by_timestamp() { p.command("setup").run(); let tag1 = format!("{}_initial", Utc::now().format(TIMESTAMP_FORMAT)); - p.create_migration(&tag1, "", ""); + p.create_migration(&tag1, "", Some("")); let result = p.command("migration").arg("list").run(); assert!(result.is_success(), "Result was unsuccessful {:?}", result); @@ -75,7 +76,7 @@ fn migration_list_lists_migrations_ordered_by_timestamp() { sleep(Duration::from_millis(1100)); let tag2 = format!("{}_alter", Utc::now().format(TIMESTAMP_FORMAT)); - p.create_migration(&tag2, "", ""); + p.create_migration(&tag2, "", Some("")); let result = p.command("migration").arg("list").run(); assert!(result.is_success(), "Result was unsuccessful {:?}", result); @@ -92,28 +93,28 @@ fn migration_list_orders_unknown_timestamps_last() { p.command("setup").run(); let tag1 = format!("{}_migration1", Utc::now().format(TIMESTAMP_FORMAT)); - p.create_migration(&tag1, "", ""); + p.create_migration(&tag1, "", Some("")); let tag4 = "abc_migration4"; - p.create_migration(&tag4, "", ""); + p.create_migration(tag4, "", Some("")); let tag5 = "zzz_migration5"; - p.create_migration(&tag5, "", ""); + p.create_migration(tag5, "", Some("")); sleep(Duration::from_millis(1100)); let tag2 = format!("{}_migration2", Utc::now().format(TIMESTAMP_FORMAT)); - p.create_migration(&tag2, "", ""); + p.create_migration(&tag2, "", Some("")); sleep(Duration::from_millis(1100)); let tag3 = format!("{}_migration3", Utc::now().format(TIMESTAMP_FORMAT)); - p.create_migration(&tag3, "", ""); + p.create_migration(&tag3, "", Some("")); let result = p.command("migration").arg("list").run(); assert!(result.is_success(), "Result was unsuccessful {:?}", result); let output = result.stdout(); - assert_tags_in_order(output, &[&tag1, &tag2, &tag3, &tag4, &tag5]); + assert_tags_in_order(output, &[&tag1, &tag2, &tag3, tag4, tag5]); } #[test] @@ -125,27 +126,27 @@ fn migration_list_orders_nontimestamp_versions_alphabetically() { p.command("setup").run(); let tag4 = "a_migration"; - p.create_migration(&tag4, "", ""); + p.create_migration(tag4, "", Some("")); let tag6 = "bc_migration"; - p.create_migration(&tag6, "", ""); + p.create_migration(tag6, "", Some("")); let tag5 = "aa_migration"; - p.create_migration(&tag5, "", ""); + p.create_migration(tag5, "", Some("")); let tag1 = "!wow_migration"; - p.create_migration(&tag1, "", ""); + p.create_migration(tag1, "", Some("")); let tag3 = "7letters"; - p.create_migration(&tag3, "", ""); + p.create_migration(tag3, "", Some("")); let tag2 = format!("{}_stamped_migration", Utc::now().format(TIMESTAMP_FORMAT)); - p.create_migration(&tag2, "", ""); + p.create_migration(&tag2, "", Some("")); let result = p.command("migration").arg("list").run(); assert!(result.is_success(), "Result was unsuccessful {:?}", result); let output = result.stdout(); - assert_tags_in_order(output, &[&tag1, &tag2, &tag3, &tag4, &tag5, &tag6]); + assert_tags_in_order(output, &[tag1, &tag2, tag3, tag4, tag5, tag6]); } #[test] @@ -157,15 +158,15 @@ fn migration_list_orders_old_and_new_timestamp_forms_mixed_correctly() { p.command("setup").run(); let tag1 = "20170505070309_migration"; - p.create_migration(&tag1, "", ""); + p.create_migration(tag1, "", Some("")); let tag2 = "2017-11-23-064836_migration"; - p.create_migration(&tag2, "", ""); + p.create_migration(tag2, "", Some("")); let result = p.command("migration").arg("list").run(); assert!(result.is_success(), "Result was unsuccessful {:?}", result); let output = result.stdout(); - assert_tags_in_order(output, &[&tag1, &tag2]); + assert_tags_in_order(output, &[tag1, tag2]); } #[test] @@ -188,7 +189,7 @@ fn migration_list_respects_migrations_dir_from_diesel_toml() { "custom_migrations", "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY)", - "DROP TABLE users", + Some("DROP TABLE users"), ); assert!(!db.table_exists("users")); diff --git a/diesel_cli/tests/migration_redo.rs b/diesel_cli/tests/migration_redo.rs index 1e2b54c36ddf..4c1c00aea97b 100644 --- a/diesel_cli/tests/migration_redo.rs +++ b/diesel_cli/tests/migration_redo.rs @@ -6,7 +6,7 @@ fn migration_redo_runs_the_last_migration_down_and_up() { p.create_migration( "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY);", - "DROP TABLE users;", + Some("DROP TABLE users;"), ); // Make sure the project is setup @@ -37,19 +37,19 @@ fn migration_redo_runs_the_last_two_migrations_down_and_up() { p.create_migration( "2017-08-31-210424_create_customers", "CREATE TABLE customers ( id INTEGER PRIMARY KEY )", - "DROP TABLE customers", + Some("DROP TABLE customers"), ); p.create_migration( "2017-09-03-210424_create_contracts", "CREATE TABLE contracts ( id INTEGER PRIMARY KEY )", - "DROP TABLE contracts", + Some("DROP TABLE contracts"), ); p.create_migration( "2017-09-12-210424_create_bills", "CREATE TABLE bills ( id INTEGER PRIMARY KEY )", - "DROP TABLE bills", + Some("DROP TABLE bills"), ); // Make sure the project is setup @@ -69,8 +69,8 @@ fn migration_redo_runs_the_last_two_migrations_down_and_up() { result.stdout() == "Rolling back migration 2017-09-12-210424_create_bills\n\ Rolling back migration 2017-09-03-210424_create_contracts\n\ - Running migration 2017-09-12-210424_create_bills\n\ - Running migration 2017-09-03-210424_create_contracts\n", + Running migration 2017-09-03-210424_create_contracts\n\ + Running migration 2017-09-12-210424_create_bills\n", "Unexpected stdout : {}", result.stdout() ); @@ -88,7 +88,7 @@ fn migration_redo_respects_migration_dir_var() { "foo", "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY);", - "DROP TABLE users;", + Some("DROP TABLE users;"), ); // Make sure the project is setup @@ -121,7 +121,7 @@ fn migration_redo_respects_migration_dir_env() { "bar", "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY);", - "DROP TABLE users;", + Some("DROP TABLE users;"), ); // Make sure the project is setup @@ -154,7 +154,7 @@ fn error_migrations_fails() { p.create_migration( "redo_error_migrations_fails", "CREATE TABLE users (id INTEGER PRIMARY KEY);", - "DROP TABLE users};", + Some("DROP TABLE users};"), ); // Make sure the project is setup @@ -185,7 +185,7 @@ fn migration_redo_respects_migrations_dir_from_diesel_toml() { "custom_migrations", "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY);", - "DROP TABLE users;", + Some("DROP TABLE users;"), ); // Make sure the project is setup @@ -216,19 +216,19 @@ fn migration_redo_all_runs_all_migrations_down_and_up() { p.create_migration( "2017-08-31-210424_create_customers", "CREATE TABLE customers ( id INTEGER PRIMARY KEY )", - "DROP TABLE customers", + Some("DROP TABLE customers"), ); p.create_migration( "2017-09-03-210424_create_contracts", "CREATE TABLE contracts ( id INTEGER PRIMARY KEY )", - "DROP TABLE contracts", + Some("DROP TABLE contracts"), ); p.create_migration( "2017-09-12-210424_create_bills", "CREATE TABLE bills ( id INTEGER PRIMARY KEY )", - "DROP TABLE bills", + Some("DROP TABLE bills"), ); // Make sure the project is setup @@ -247,9 +247,9 @@ fn migration_redo_all_runs_all_migrations_down_and_up() { == "Rolling back migration 2017-09-12-210424_create_bills\n\ Rolling back migration 2017-09-03-210424_create_contracts\n\ Rolling back migration 2017-08-31-210424_create_customers\n\ - Running migration 2017-09-12-210424_create_bills\n\ + Running migration 2017-08-31-210424_create_customers\n\ Running migration 2017-09-03-210424_create_contracts\n\ - Running migration 2017-08-31-210424_create_customers\n", + Running migration 2017-09-12-210424_create_bills\n", "Unexpected stdout : {}", result.stdout() ); @@ -269,19 +269,19 @@ fn migration_redo_with_more_than_max_should_redo_all() { p.create_migration( "2017-08-31-210424_create_customers", "CREATE TABLE customers ( id INTEGER PRIMARY KEY )", - "DROP TABLE customers", + Some("DROP TABLE customers"), ); p.create_migration( "2017-09-03-210424_create_contracts", "CREATE TABLE contracts ( id INTEGER PRIMARY KEY )", - "DROP TABLE contracts", + Some("DROP TABLE contracts"), ); p.create_migration( "2017-09-12-210424_create_bills", "CREATE TABLE bills ( id INTEGER PRIMARY KEY )", - "DROP TABLE bills", + Some("DROP TABLE bills"), ); // Make sure the project is setup @@ -305,9 +305,9 @@ fn migration_redo_with_more_than_max_should_redo_all() { == "Rolling back migration 2017-09-12-210424_create_bills\n\ Rolling back migration 2017-09-03-210424_create_contracts\n\ Rolling back migration 2017-08-31-210424_create_customers\n\ - Running migration 2017-09-12-210424_create_bills\n\ + Running migration 2017-08-31-210424_create_customers\n\ Running migration 2017-09-03-210424_create_contracts\n\ - Running migration 2017-08-31-210424_create_customers\n", + Running migration 2017-09-12-210424_create_bills\n", "Unexpected stdout : {}", result.stdout() ); @@ -339,8 +339,9 @@ fn migration_redo_n_with_a_string_should_throw_an_error() { assert!( result.stderr() == "error: Invalid value \"infinite\" for '--number ': \ - infinite isn't a positive integer.\n\nFor more information try --help\n", - "Unexpected stderr : {}", + invalid digit found in string\n\n\ + For more information try --help\n", + "Unexpected stderr : '{}'", result.stderr() ); } @@ -355,7 +356,7 @@ fn migration_redo_with_zero_should_not_revert_any_migration() { p.create_migration( "2017-08-31-210424_create_customers", "CREATE TABLE customers ( id INTEGER PRIMARY KEY )", - "DROP TABLE customers", + Some("DROP TABLE customers"), ); // Make sure the project is setup @@ -366,6 +367,6 @@ fn migration_redo_with_zero_should_not_revert_any_migration() { // Should not revert any migration. let result = p.command("migration").arg("redo").arg("-n").arg("0").run(); - assert!(!result.is_success(), "Result was unsuccessful {:?}", result); + assert!(result.is_success(), "Result was unsuccessful {:?}", result); assert!(result.stdout() == ""); } diff --git a/diesel_cli/tests/migration_revert.rs b/diesel_cli/tests/migration_revert.rs index e49bbb54e4cc..9fe846fded80 100644 --- a/diesel_cli/tests/migration_revert.rs +++ b/diesel_cli/tests/migration_revert.rs @@ -10,7 +10,7 @@ fn migration_revert_runs_the_last_migration_down() { p.create_migration( "12345_create_users_table", "CREATE TABLE users ( id INTEGER )", - "DROP TABLE users", + Some("DROP TABLE users"), ); // Make sure the project is setup @@ -38,7 +38,7 @@ fn migration_revert_respects_migration_dir_var() { "foo", "12345_create_users_table", "CREATE TABLE users ( id INTEGER )", - "DROP TABLE users", + Some("DROP TABLE users"), ); // Make sure the project is setup. @@ -70,7 +70,7 @@ fn migration_revert_respects_migration_dir_env() { "bar", "12345_create_users_table", "CREATE TABLE users ( id INTEGER )", - "DROP TABLE users", + Some("DROP TABLE users"), ); // Make sure the project is setup. @@ -112,7 +112,7 @@ fn migration_revert_respects_migration_dir_from_diesel_toml() { "custom_migrations", "12345_create_users_table", "CREATE TABLE users ( id INTEGER )", - "DROP TABLE users", + Some("DROP TABLE users"), ); // Make sure the project is setup. @@ -141,19 +141,19 @@ fn migration_revert_runs_the_last_two_migration_down() { p.create_migration( "2017-08-31-210424_create_customers", "CREATE TABLE customers ( id INTEGER PRIMARY KEY )", - "DROP TABLE customers", + Some("DROP TABLE customers"), ); p.create_migration( "2017-09-03-210424_create_contracts", "CREATE TABLE contracts ( id INTEGER PRIMARY KEY )", - "DROP TABLE contracts", + Some("DROP TABLE contracts"), ); p.create_migration( "2017-09-12-210424_create_bills", "CREATE TABLE bills ( id INTEGER PRIMARY KEY )", - "DROP TABLE bills", + Some("DROP TABLE bills"), ); // Make sure the project is setup @@ -194,19 +194,19 @@ fn migration_revert_all_runs_the_migrations_down() { p.create_migration( "2017-08-31-210424_create_customers", "CREATE TABLE customers ( id INTEGER PRIMARY KEY )", - "DROP TABLE customers", + Some("DROP TABLE customers"), ); p.create_migration( "2017-09-03-210424_create_contracts", "CREATE TABLE contracts ( id INTEGER PRIMARY KEY )", - "DROP TABLE contracts", + Some("DROP TABLE contracts"), ); p.create_migration( "2017-09-12-210424_create_bills", "CREATE TABLE bills ( id INTEGER PRIMARY KEY )", - "DROP TABLE bills", + Some("DROP TABLE bills"), ); // Make sure the project is setup @@ -244,7 +244,7 @@ fn migration_revert_with_zero_should_not_revert_any_migration() { p.create_migration( "2017-08-31-210424_create_customers", "CREATE TABLE customers ( id INTEGER PRIMARY KEY )", - "DROP TABLE customers", + Some("DROP TABLE customers"), ); // Make sure the project is setup @@ -259,8 +259,11 @@ fn migration_revert_with_zero_should_not_revert_any_migration() { .arg("-n") .arg("0") .run(); - - assert!(!result.is_success(), "Result was unsuccessful {:?}", result); + assert!( + result.is_success(), + "Result was unsuccessful '{:?}'", + result + ); assert!(result.stdout() == ""); } @@ -281,12 +284,13 @@ fn migration_revert_n_with_a_string_should_throw_an_error() { .arg("infinite") .run(); - assert!(!result.is_success(), "Result was unsuccessful {:?}", result); + assert!(!result.is_success(), "Result was successful {:?}", result); assert!( result.stderr() == "error: Invalid value \"infinite\" for '--number ': \ - infinite isn't a positive integer.\n\nFor more information try --help\n", + invalid digit found in string\n\n\ + For more information try --help\n", "Unexpected stderr : {}", result.stderr() ); @@ -302,19 +306,19 @@ fn migration_revert_with_more_than_max_should_revert_all() { p.create_migration( "2017-08-31-210424_create_customers", "CREATE TABLE customers ( id INTEGER PRIMARY KEY )", - "DROP TABLE customers", + Some("DROP TABLE customers"), ); p.create_migration( "2017-09-03-210424_create_contracts", "CREATE TABLE contracts ( id INTEGER PRIMARY KEY )", - "DROP TABLE contracts", + Some("DROP TABLE contracts"), ); p.create_migration( "2017-09-12-210424_create_bills", "CREATE TABLE bills ( id INTEGER PRIMARY KEY )", - "DROP TABLE bills", + Some("DROP TABLE bills"), ); // Make sure the project is setup @@ -346,3 +350,43 @@ fn migration_revert_with_more_than_max_should_revert_all() { assert!(!db.table_exists("contracts")); assert!(!db.table_exists("bills")); } + +#[test] +fn migration_revert_gives_reasonable_error_message_on_missing_down() { + let p = project("migration_revert_error_message_on_missing_down") + .folder("migrations") + .build(); + let db = database(&p.database_url()); + + p.create_migration( + "12345_create_users_table", + "CREATE TABLE users ( id INTEGER )", + None, + ); + + // Make sure the project is setup + p.command("setup").run(); + + assert!(db.table_exists("users")); + + let result = p.command("migration").arg("revert").run(); + + assert!( + !result.is_success(), + "Result was successful when it shouldn't be {:?}", + result + ); + assert!( + result.stdout().contains("Rolling back migration 12345"), + "Unexpected stdout {}", + result.stdout() + ); + assert!( + result + .stderr() + .contains("Missing `down.sql` file to revert migration"), + "Unexpected stderr {}", + result.stderr() + ); + assert!(db.table_exists("users")); +} diff --git a/diesel_cli/tests/migration_run.rs b/diesel_cli/tests/migration_run.rs index 0a81831c2c71..a2f9dfebe8fb 100644 --- a/diesel_cli/tests/migration_run.rs +++ b/diesel_cli/tests/migration_run.rs @@ -15,7 +15,7 @@ fn migration_run_runs_pending_migrations() { p.create_migration( "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY)", - "DROP TABLE users", + Some("DROP TABLE users"), ); assert!(!db.table_exists("users")); @@ -44,7 +44,7 @@ fn migration_run_inserts_run_on_timestamps() { p.create_migration( "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY)", - "DROP TABLE users", + Some("DROP TABLE users"), ); let migrations_done: bool = select(sql::( @@ -104,7 +104,7 @@ fn empty_migrations_are_not_valid() { p.command("setup").run(); - p.create_migration("12345_empty_migration", "", ""); + p.create_migration("12345_empty_migration", "", None); let result = p.command("migration").arg("run").run(); @@ -125,7 +125,7 @@ fn error_migrations_fails() { p.create_migration( "run_error_migrations_fails", "CREATE TABLE users (id INTEGER PRIMARY KEY}", - "DROP TABLE users", + Some("DROP TABLE users"), ); let result = p.command("migration").arg("run").run(); @@ -149,7 +149,7 @@ fn error_migrations_when_use_invalid_database_url() { p.create_migration( "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY)", - "DROP TABLE users", + Some("DROP TABLE users"), ); let result = p @@ -176,7 +176,7 @@ fn any_pending_migrations_works() { p.create_migration( "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY)", - "DROP TABLE users", + Some("DROP TABLE users"), ); let result = p.command("migration").arg("pending").run(); @@ -195,7 +195,7 @@ fn any_pending_migrations_after_running() { p.create_migration( "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY)", - "DROP TABLE users", + Some("DROP TABLE users"), ); p.command("migration").arg("run").run(); @@ -216,7 +216,7 @@ fn any_pending_migrations_after_running_and_creating() { p.create_migration( "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY)", - "DROP TABLE users", + Some("DROP TABLE users"), ); p.command("migration").arg("run").run(); @@ -224,7 +224,7 @@ fn any_pending_migrations_after_running_and_creating() { p.create_migration( "123456_create_posts_table", "CREATE TABLE posts (id INTEGER PRIMARY KEY)", - "DROP TABLE posts", + Some("DROP TABLE posts"), ); let result = p.command("migration").arg("pending").run(); @@ -246,7 +246,7 @@ fn migration_run_runs_pending_migrations_custom_database_url_1() { p.create_migration( "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY)", - "DROP TABLE users", + Some("DROP TABLE users"), ); assert!(!db.table_exists("users")); @@ -281,7 +281,7 @@ fn migration_run_runs_pending_migrations_custom_database_url_2() { p.create_migration( "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY)", - "DROP TABLE users", + Some("DROP TABLE users"), ); assert!(!db.table_exists("users")); @@ -317,7 +317,7 @@ fn migration_run_runs_pending_migrations_custom_migration_dir_1() { "custom_migrations", "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY)", - "DROP TABLE users", + Some("DROP TABLE users"), ); assert!(!db.table_exists("users")); @@ -353,7 +353,7 @@ fn migration_run_runs_pending_migrations_custom_migration_dir_2() { "custom_migrations", "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY)", - "DROP TABLE users", + Some("DROP TABLE users"), ); assert!(!db.table_exists("users")); @@ -393,7 +393,7 @@ fn migration_run_updates_schema_if_config_present() { p.create_migration( "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY)", - "DROP TABLE users", + Some("DROP TABLE users"), ); assert!(!p.has_file("src/my_schema.rs")); @@ -421,7 +421,7 @@ fn migrations_can_be_run_with_no_config_file() { p.create_migration( "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY)", - "DROP TABLE users", + Some("DROP TABLE users"), ); assert!(!db.table_exists("users")); @@ -452,7 +452,38 @@ fn migrations_can_be_run_with_no_cargo_toml() { p.create_migration( "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY)", - "DROP TABLE users", + Some("DROP TABLE users"), + ); + + assert!(!db.table_exists("users")); + + let result = p.command("migration").arg("run").run(); + + assert!(result.is_success(), "Result was unsuccessful {:?}", result); + assert!( + result.stdout().contains("Running migration 12345"), + "Unexpected stdout {}", + result.stdout() + ); + assert!(db.table_exists("users")); +} + +#[test] +fn migrations_can_be_run_with_no_down() { + let p = project("migrations_can_be_run_with_no_down") + .folder("migrations") + .build(); + let db = database(&p.database_url()); + + let cargo_toml_path = Path::new("Cargo.toml"); + p.delete_single_file(&cargo_toml_path); + + p.command("database").arg("setup").run(); + + p.create_migration( + "12345_create_users_table", + "CREATE TABLE users (id INTEGER PRIMARY KEY)", + None, ); assert!(!db.table_exists("users")); @@ -487,7 +518,7 @@ fn verify_schema_errors_if_schema_file_would_change() { p.create_migration( "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY)", - "DROP TABLE users", + Some("DROP TABLE users"), ); assert!(!p.has_file("src/my_schema.rs")); @@ -500,7 +531,7 @@ fn verify_schema_errors_if_schema_file_would_change() { p.create_migration( "12346_create_posts_table", "CREATE TABLE posts (id INTEGER PRIMARY KEY)", - "DROP TABLE posts", + Some("DROP TABLE posts"), ); let result = p @@ -548,7 +579,7 @@ fn migration_run_runs_pending_migrations_custom_migrations_dir_from_diesel_toml( "custom_migrations", "12345_create_users_table", "CREATE TABLE users (id INTEGER PRIMARY KEY)", - "DROP TABLE users", + Some("DROP TABLE users"), ); assert!(!db.table_exists("users")); diff --git a/diesel_cli/tests/print_schema.rs b/diesel_cli/tests/print_schema.rs index fdaa90f9b72b..0e26ef47574f 100644 --- a/diesel_cli/tests/print_schema.rs +++ b/diesel_cli/tests/print_schema.rs @@ -1,3 +1,4 @@ +#![allow(clippy::expect_fun_call)] use std::fs::File; use std::io::prelude::*; use std::path::{Path, PathBuf}; @@ -123,6 +124,12 @@ fn print_schema_unsigned() { test_print_schema("print_schema_unsigned", vec!["--with-docs"]); } +#[test] +#[cfg(feature = "mysql")] +fn print_schema_datetime_for_mysql() { + test_print_schema("print_schema_datetime_for_mysql", vec!["--with-docs"]); +} + #[test] #[cfg(not(windows))] fn print_schema_patch_file() { @@ -216,6 +223,12 @@ fn print_schema_array_type() { test_print_schema("print_schema_array_type", vec![]) } +#[test] +#[cfg(feature = "sqlite")] +fn print_schema_sqlite_implicit_foreign_key_reference() { + test_print_schema("print_schema_sqlite_implicit_foreign_key_reference", vec![]); +} + #[cfg(feature = "sqlite")] const BACKEND: &str = "sqlite"; #[cfg(feature = "postgres")] @@ -270,7 +283,7 @@ fn test_print_schema_config(test_name: &str, test_path: &Path, schema: String, e let p = p.build(); p.command("setup").run(); - p.create_migration("12345_create_schema", &schema, ""); + p.create_migration("12345_create_schema", &schema, None); let result = p.command("migration").arg("run").run(); assert!(result.is_success(), "Result was unsuccessful {:?}", result); diff --git a/diesel_cli/tests/print_schema/print_schema_datetime_for_mysql/diesel.toml b/diesel_cli/tests/print_schema/print_schema_datetime_for_mysql/diesel.toml new file mode 100644 index 000000000000..750e5ba85830 --- /dev/null +++ b/diesel_cli/tests/print_schema/print_schema_datetime_for_mysql/diesel.toml @@ -0,0 +1,3 @@ +[print_schema] +file = "src/schema.rs" +with_docs = true diff --git a/diesel_cli/tests/print_schema/print_schema_datetime_for_mysql/mysql/expected.rs b/diesel_cli/tests/print_schema/print_schema_datetime_for_mysql/mysql/expected.rs new file mode 100644 index 000000000000..3d8b74e74a4c --- /dev/null +++ b/diesel_cli/tests/print_schema/print_schema_datetime_for_mysql/mysql/expected.rs @@ -0,0 +1,27 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + /// Representation of the `users` table. + /// + /// (Automatically generated by Diesel.) + users (id) { + /// The `id` column of the `users` table. + /// + /// Its SQL type is `Integer`. + /// + /// (Automatically generated by Diesel.) + id -> Integer, + /// The `datetime` column of the `users` table. + /// + /// Its SQL type is `Datetime`. + /// + /// (Automatically generated by Diesel.) + datetime -> Datetime, + /// The `nullable_datetime` column of the `users` table. + /// + /// Its SQL type is `Nullable`. + /// + /// (Automatically generated by Diesel.) + nullable_datetime -> Nullable, + } +} diff --git a/diesel_cli/tests/print_schema/print_schema_datetime_for_mysql/mysql/schema.sql b/diesel_cli/tests/print_schema/print_schema_datetime_for_mysql/mysql/schema.sql new file mode 100644 index 000000000000..0b6f2d2e5e38 --- /dev/null +++ b/diesel_cli/tests/print_schema/print_schema_datetime_for_mysql/mysql/schema.sql @@ -0,0 +1,5 @@ +CREATE TABLE users ( + id INTEGER PRIMARY KEY, + datetime DATETIME NOT NULL, + nullable_datetime DATETIME +); diff --git a/diesel_cli/tests/print_schema/print_schema_sqlite_implicit_foreign_key_reference/diesel.toml b/diesel_cli/tests/print_schema/print_schema_sqlite_implicit_foreign_key_reference/diesel.toml new file mode 100644 index 000000000000..f57985adb185 --- /dev/null +++ b/diesel_cli/tests/print_schema/print_schema_sqlite_implicit_foreign_key_reference/diesel.toml @@ -0,0 +1,2 @@ +[print_schema] +file = "src/schema.rs" diff --git a/diesel_cli/tests/print_schema/print_schema_sqlite_implicit_foreign_key_reference/sqlite/expected.rs b/diesel_cli/tests/print_schema/print_schema_sqlite_implicit_foreign_key_reference/sqlite/expected.rs new file mode 100644 index 000000000000..f791e8ac91f7 --- /dev/null +++ b/diesel_cli/tests/print_schema/print_schema_sqlite_implicit_foreign_key_reference/sqlite/expected.rs @@ -0,0 +1,24 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + accounts (id) { + id -> Integer, + account -> Text, + data_center_id -> Integer, + auth_key -> Binary, + } +} + +diesel::table! { + data_centers (id) { + id -> Integer, + name -> Text, + } +} + +diesel::joinable!(accounts -> data_centers (data_center_id)); + +diesel::allow_tables_to_appear_in_same_query!( + accounts, + data_centers, +); diff --git a/diesel_cli/tests/print_schema/print_schema_sqlite_implicit_foreign_key_reference/sqlite/schema.sql b/diesel_cli/tests/print_schema/print_schema_sqlite_implicit_foreign_key_reference/sqlite/schema.sql new file mode 100644 index 000000000000..0680c88cbf80 --- /dev/null +++ b/diesel_cli/tests/print_schema/print_schema_sqlite_implicit_foreign_key_reference/sqlite/schema.sql @@ -0,0 +1,13 @@ +CREATE TABLE data_centers ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL +); + +CREATE TABLE accounts ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + account TEXT NOT NULL, + data_center_id INTEGER NOT NULL, + auth_key BLOB NOT NULL, + UNIQUE (account, data_center_id), + FOREIGN KEY (data_center_id) REFERENCES data_centers +); diff --git a/diesel_cli/tests/setup.rs b/diesel_cli/tests/setup.rs index 607487b8da7b..2d6ba09943d9 100644 --- a/diesel_cli/tests/setup.rs +++ b/diesel_cli/tests/setup.rs @@ -88,7 +88,7 @@ fn setup_runs_migrations_if_no_schema_table() { p.create_migration( "12345_create_users_table", "CREATE TABLE users ( id INTEGER )", - "DROP TABLE users", + Some("DROP TABLE users"), ); // sanity check @@ -116,7 +116,7 @@ fn setup_doesnt_run_migrations_if_schema_table_exists() { p.create_migration( "12345_create_users_table", "CREATE TABLE users ( id INTEGER )", - "DROP TABLE users", + Some("DROP TABLE users"), ); let result = p.command("setup").run(); diff --git a/diesel_cli/tests/support/command.rs b/diesel_cli/tests/support/command.rs index 08abac881ecd..e2aac56c5ba4 100644 --- a/diesel_cli/tests/support/command.rs +++ b/diesel_cli/tests/support/command.rs @@ -46,7 +46,7 @@ impl TestCommand { .build_command() .output() .expect("failed to execute process"); - CommandResult { output: output } + CommandResult { output } } fn build_command(&self) -> Command { diff --git a/diesel_cli/tests/support/mod.rs b/diesel_cli/tests/support/mod.rs index 6c6e5cd5b674..56f407bf9cce 100644 --- a/diesel_cli/tests/support/mod.rs +++ b/diesel_cli/tests/support/mod.rs @@ -4,9 +4,8 @@ macro_rules! try_drop { match $e { Ok(x) => x, Err(e) => { - use std::io::{stderr, Write}; if ::std::thread::panicking() { - write!(stderr(), "{}: {:?}", $msg, e).unwrap(); + eprintln!("{}: {:?}", $msg, e); return; } else { panic!("{}: {:?}", $msg, e); diff --git a/diesel_cli/tests/support/mysql_database.rs b/diesel_cli/tests/support/mysql_database.rs index 93ac35423d21..9618876f22c1 100644 --- a/diesel_cli/tests/support/mysql_database.rs +++ b/diesel_cli/tests/support/mysql_database.rs @@ -1,3 +1,4 @@ +#![allow(clippy::expect_fun_call)] use diesel::connection::SimpleConnection; use diesel::dsl::sql; use diesel::sql_types::Bool; @@ -50,7 +51,7 @@ impl Database { } fn split_url(&self) -> (String, String) { - let mut split: Vec<&str> = self.url.split("/").collect(); + let mut split: Vec<&str> = self.url.split('/').collect(); let database = split.pop().unwrap(); let mysql_url = format!("{}/{}", split.join("/"), "information_schema"); (database.into(), mysql_url) diff --git a/diesel_cli/tests/support/postgres_database.rs b/diesel_cli/tests/support/postgres_database.rs index e3d1b074dc2b..e2eede17b389 100644 --- a/diesel_cli/tests/support/postgres_database.rs +++ b/diesel_cli/tests/support/postgres_database.rs @@ -1,3 +1,4 @@ +#![allow(clippy::expect_fun_call)] use diesel::connection::SimpleConnection; use diesel::dsl::sql; use diesel::sql_types::Bool; @@ -49,7 +50,7 @@ impl Database { } fn split_url(&self) -> (String, String) { - let mut split: Vec<&str> = self.url.split("/").collect(); + let mut split: Vec<&str> = self.url.split('/').collect(); let database = split.pop().unwrap(); let postgres_url = format!("{}/{}", split.join("/"), "postgres"); (database.into(), postgres_url) diff --git a/diesel_cli/tests/support/project_builder.rs b/diesel_cli/tests/support/project_builder.rs index b502d482f5cb..21a6695f6708 100644 --- a/diesel_cli/tests/support/project_builder.rs +++ b/diesel_cli/tests/support/project_builder.rs @@ -84,7 +84,7 @@ impl Project { .read_dir() .expect("Error reading directory") .map(|e| Migration { - path: e.expect("error reading entry").path().into(), + path: e.expect("error reading entry").path(), }) .collect() } @@ -156,19 +156,27 @@ impl Project { migration_path.display().to_string() } - pub fn create_migration(&self, name: &str, up: &str, down: &str) { + pub fn create_migration(&self, name: &str, up: &str, down: Option<&str>) { self.create_migration_in_directory("migrations", name, up, down); } - pub fn create_migration_in_directory(&self, directory: &str, name: &str, up: &str, down: &str) { + pub fn create_migration_in_directory( + &self, + directory: &str, + name: &str, + up: &str, + down: Option<&str>, + ) { let migration_path = self.directory.path().join(directory).join(name); fs::create_dir(&migration_path) .expect("Migrations folder must exist to create a migration"); let mut up_file = fs::File::create(&migration_path.join("up.sql")).unwrap(); up_file.write_all(up.as_bytes()).unwrap(); - let mut down_file = fs::File::create(&migration_path.join("down.sql")).unwrap(); - down_file.write_all(down.as_bytes()).unwrap(); + if let Some(down) = down { + let mut down_file = fs::File::create(&migration_path.join("down.sql")).unwrap(); + down_file.write_all(down.as_bytes()).unwrap(); + } } } diff --git a/diesel_cli/tests/support/sqlite_database.rs b/diesel_cli/tests/support/sqlite_database.rs index fe18bf822e94..f039aa8242b5 100644 --- a/diesel_cli/tests/support/sqlite_database.rs +++ b/diesel_cli/tests/support/sqlite_database.rs @@ -1,3 +1,4 @@ +#![allow(clippy::expect_fun_call)] use diesel::connection::SimpleConnection; use diesel::dsl::sql; use diesel::sql_types::Bool; diff --git a/diesel_compile_tests/Cargo.lock b/diesel_compile_tests/Cargo.lock index 010398bff0b8..502c4cfe96a6 100644 --- a/diesel_compile_tests/Cargo.lock +++ b/diesel_compile_tests/Cargo.lock @@ -51,7 +51,7 @@ dependencies = [ [[package]] name = "diesel" -version = "2.0.0" +version = "2.0.0-rc.0" dependencies = [ "bigdecimal", "bitflags", @@ -84,7 +84,7 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.0.0" +version = "2.0.0-rc.0" dependencies = [ "proc-macro-error", "proc-macro2", diff --git a/diesel_compile_tests/Cargo.toml b/diesel_compile_tests/Cargo.toml index fdda3f37c9be..2faaaf407833 100644 --- a/diesel_compile_tests/Cargo.toml +++ b/diesel_compile_tests/Cargo.toml @@ -3,7 +3,7 @@ name = "diesel_compile_tests" version = "0.1.0" [dependencies] -diesel = { version = "2.0.0-rc.0", default-features = false, features = ["extras", "sqlite", "postgres", "mysql", "with-deprecated"], path = "../diesel" } +diesel = { version = "2.0.0-rc.1", default-features = false, features = ["extras", "sqlite", "postgres", "mysql", "with-deprecated"], path = "../diesel" } trybuild = "1.0.41" [workspace] diff --git a/diesel_compile_tests/tests/fail/aggregate_expression_requires_column_from_same_table.stderr b/diesel_compile_tests/tests/fail/aggregate_expression_requires_column_from_same_table.stderr index 73872513c64d..dfa8bdcd30b3 100644 --- a/diesel_compile_tests/tests/fail/aggregate_expression_requires_column_from_same_table.stderr +++ b/diesel_compile_tests/tests/fail/aggregate_expression_requires_column_from_same_table.stderr @@ -19,11 +19,20 @@ error[E0271]: type mismatch resolving `` for `posts::columns::id` +note: required because of the requirements on the impl of `AppearsOnTable` for `posts::columns::id` + --> tests/fail/aggregate_expression_requires_column_from_same_table.rs:11:1 + | +11 | / table! { +12 | | posts { +13 | | id -> Integer, +14 | | } +15 | | } + | |_^ = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `AppearsOnTable` for `diesel::expression::functions::aggregate_folding::sum::sum` = note: required because of the requirements on the impl of `SelectableExpression` for `diesel::expression::functions::aggregate_folding::sum::sum` = note: required because of the requirements on the impl of `SelectDsl>` for `SelectStatement>` + = note: this error originates in the macro `$crate::__diesel_column` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `users::table: TableNotEqual` is not satisfied --> tests/fail/aggregate_expression_requires_column_from_same_table.rs:19:31 @@ -32,11 +41,20 @@ error[E0277]: the trait bound `users::table: TableNotEqual` is not | ^^^^^^ the trait `TableNotEqual` is not implemented for `users::table` | = note: required because of the requirements on the impl of `AppearsInFromClause` for `users::table` - = note: required because of the requirements on the impl of `AppearsOnTable` for `posts::columns::id` +note: required because of the requirements on the impl of `AppearsOnTable` for `posts::columns::id` + --> tests/fail/aggregate_expression_requires_column_from_same_table.rs:11:1 + | +11 | / table! { +12 | | posts { +13 | | id -> Integer, +14 | | } +15 | | } + | |_^ = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `AppearsOnTable` for `diesel::expression::functions::aggregate_folding::sum::sum` = note: required because of the requirements on the impl of `SelectableExpression` for `diesel::expression::functions::aggregate_folding::sum::sum` = note: required because of the requirements on the impl of `SelectDsl>` for `SelectStatement>` + = note: this error originates in the macro `$crate::__diesel_column` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `posts::columns::id: SelectableExpression` is not satisfied --> tests/fail/aggregate_expression_requires_column_from_same_table.rs:20:38 @@ -59,11 +77,20 @@ error[E0271]: type mismatch resolving `` for `posts::columns::id` +note: required because of the requirements on the impl of `AppearsOnTable` for `posts::columns::id` + --> tests/fail/aggregate_expression_requires_column_from_same_table.rs:11:1 + | +11 | / table! { +12 | | posts { +13 | | id -> Integer, +14 | | } +15 | | } + | |_^ = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `AppearsOnTable` for `diesel::expression::functions::aggregate_folding::avg::avg` = note: required because of the requirements on the impl of `SelectableExpression` for `diesel::expression::functions::aggregate_folding::avg::avg` = note: required because of the requirements on the impl of `SelectDsl>` for `SelectStatement>` + = note: this error originates in the macro `$crate::__diesel_column` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `users::table: TableNotEqual` is not satisfied --> tests/fail/aggregate_expression_requires_column_from_same_table.rs:20:31 @@ -72,11 +99,20 @@ error[E0277]: the trait bound `users::table: TableNotEqual` is not | ^^^^^^ the trait `TableNotEqual` is not implemented for `users::table` | = note: required because of the requirements on the impl of `AppearsInFromClause` for `users::table` - = note: required because of the requirements on the impl of `AppearsOnTable` for `posts::columns::id` +note: required because of the requirements on the impl of `AppearsOnTable` for `posts::columns::id` + --> tests/fail/aggregate_expression_requires_column_from_same_table.rs:11:1 + | +11 | / table! { +12 | | posts { +13 | | id -> Integer, +14 | | } +15 | | } + | |_^ = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `AppearsOnTable` for `diesel::expression::functions::aggregate_folding::avg::avg` = note: required because of the requirements on the impl of `SelectableExpression` for `diesel::expression::functions::aggregate_folding::avg::avg` = note: required because of the requirements on the impl of `SelectDsl>` for `SelectStatement>` + = note: this error originates in the macro `$crate::__diesel_column` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `posts::columns::id: SelectableExpression` is not satisfied --> tests/fail/aggregate_expression_requires_column_from_same_table.rs:21:38 @@ -99,11 +135,20 @@ error[E0271]: type mismatch resolving `` for `posts::columns::id` +note: required because of the requirements on the impl of `AppearsOnTable` for `posts::columns::id` + --> tests/fail/aggregate_expression_requires_column_from_same_table.rs:11:1 + | +11 | / table! { +12 | | posts { +13 | | id -> Integer, +14 | | } +15 | | } + | |_^ = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `AppearsOnTable` for `diesel::expression::functions::aggregate_ordering::max::max` = note: required because of the requirements on the impl of `SelectableExpression` for `diesel::expression::functions::aggregate_ordering::max::max` = note: required because of the requirements on the impl of `SelectDsl>` for `SelectStatement>` + = note: this error originates in the macro `$crate::__diesel_column` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `users::table: TableNotEqual` is not satisfied --> tests/fail/aggregate_expression_requires_column_from_same_table.rs:21:31 @@ -112,11 +157,20 @@ error[E0277]: the trait bound `users::table: TableNotEqual` is not | ^^^^^^ the trait `TableNotEqual` is not implemented for `users::table` | = note: required because of the requirements on the impl of `AppearsInFromClause` for `users::table` - = note: required because of the requirements on the impl of `AppearsOnTable` for `posts::columns::id` +note: required because of the requirements on the impl of `AppearsOnTable` for `posts::columns::id` + --> tests/fail/aggregate_expression_requires_column_from_same_table.rs:11:1 + | +11 | / table! { +12 | | posts { +13 | | id -> Integer, +14 | | } +15 | | } + | |_^ = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `AppearsOnTable` for `diesel::expression::functions::aggregate_ordering::max::max` = note: required because of the requirements on the impl of `SelectableExpression` for `diesel::expression::functions::aggregate_ordering::max::max` = note: required because of the requirements on the impl of `SelectDsl>` for `SelectStatement>` + = note: this error originates in the macro `$crate::__diesel_column` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `posts::columns::id: SelectableExpression` is not satisfied --> tests/fail/aggregate_expression_requires_column_from_same_table.rs:22:38 @@ -139,11 +193,20 @@ error[E0271]: type mismatch resolving `` for `posts::columns::id` +note: required because of the requirements on the impl of `AppearsOnTable` for `posts::columns::id` + --> tests/fail/aggregate_expression_requires_column_from_same_table.rs:11:1 + | +11 | / table! { +12 | | posts { +13 | | id -> Integer, +14 | | } +15 | | } + | |_^ = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `AppearsOnTable` for `diesel::expression::functions::aggregate_ordering::min::min` = note: required because of the requirements on the impl of `SelectableExpression` for `diesel::expression::functions::aggregate_ordering::min::min` = note: required because of the requirements on the impl of `SelectDsl>` for `SelectStatement>` + = note: this error originates in the macro `$crate::__diesel_column` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `users::table: TableNotEqual` is not satisfied --> tests/fail/aggregate_expression_requires_column_from_same_table.rs:22:31 @@ -152,8 +215,17 @@ error[E0277]: the trait bound `users::table: TableNotEqual` is not | ^^^^^^ the trait `TableNotEqual` is not implemented for `users::table` | = note: required because of the requirements on the impl of `AppearsInFromClause` for `users::table` - = note: required because of the requirements on the impl of `AppearsOnTable` for `posts::columns::id` +note: required because of the requirements on the impl of `AppearsOnTable` for `posts::columns::id` + --> tests/fail/aggregate_expression_requires_column_from_same_table.rs:11:1 + | +11 | / table! { +12 | | posts { +13 | | id -> Integer, +14 | | } +15 | | } + | |_^ = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `AppearsOnTable` for `diesel::expression::functions::aggregate_ordering::min::min` = note: required because of the requirements on the impl of `SelectableExpression` for `diesel::expression::functions::aggregate_ordering::min::min` = note: required because of the requirements on the impl of `SelectDsl>` for `SelectStatement>` + = note: this error originates in the macro `$crate::__diesel_column` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/diesel_compile_tests/tests/fail/any_is_only_selectable_if_inner_expr_is_selectable.stderr b/diesel_compile_tests/tests/fail/any_is_only_selectable_if_inner_expr_is_selectable.stderr index c20de9bd9205..19f3aa1e64fe 100644 --- a/diesel_compile_tests/tests/fail/any_is_only_selectable_if_inner_expr_is_selectable.stderr +++ b/diesel_compile_tests/tests/fail/any_is_only_selectable_if_inner_expr_is_selectable.stderr @@ -12,12 +12,21 @@ error[E0271]: type mismatch resolving `` for `more_stuff::columns::names` +note: required because of the requirements on the impl of `AppearsOnTable` for `more_stuff::columns::names` + --> tests/fail/any_is_only_selectable_if_inner_expr_is_selectable.rs:13:1 + | +13 | / table! { +14 | | more_stuff (names) { +15 | | names -> Array, +16 | | } +17 | | } + | |_^ = note: 3 redundant requirements hidden = note: required because of the requirements on the impl of `AppearsOnTable` for `diesel::expression::grouped::Grouped>>` = note: required because of the requirements on the impl of `diesel::query_builder::where_clause::ValidWhereClause>` for `diesel::query_builder::where_clause::WhereClause>>>` = note: required because of the requirements on the impl of `Query` for `SelectStatement, diesel::query_builder::select_clause::DefaultSelectClause>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause>>>>` = note: required because of the requirements on the impl of `LoadQuery<'_, _, _>` for `SelectStatement, diesel::query_builder::select_clause::DefaultSelectClause>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause>>>>` + = note: this error originates in the macro `$crate::__diesel_column` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `stuff::table: TableNotEqual` is not satisfied --> tests/fail/any_is_only_selectable_if_inner_expr_is_selectable.rs:31:10 @@ -26,9 +35,18 @@ error[E0277]: the trait bound `stuff::table: TableNotEqual` i | ^^^^ the trait `TableNotEqual` is not implemented for `stuff::table` | = note: required because of the requirements on the impl of `AppearsInFromClause` for `stuff::table` - = note: required because of the requirements on the impl of `AppearsOnTable` for `more_stuff::columns::names` +note: required because of the requirements on the impl of `AppearsOnTable` for `more_stuff::columns::names` + --> tests/fail/any_is_only_selectable_if_inner_expr_is_selectable.rs:13:1 + | +13 | / table! { +14 | | more_stuff (names) { +15 | | names -> Array, +16 | | } +17 | | } + | |_^ = note: 3 redundant requirements hidden = note: required because of the requirements on the impl of `AppearsOnTable` for `diesel::expression::grouped::Grouped>>` = note: required because of the requirements on the impl of `diesel::query_builder::where_clause::ValidWhereClause>` for `diesel::query_builder::where_clause::WhereClause>>>` = note: required because of the requirements on the impl of `Query` for `SelectStatement, diesel::query_builder::select_clause::DefaultSelectClause>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause>>>>` = note: required because of the requirements on the impl of `LoadQuery<'_, _, _>` for `SelectStatement, diesel::query_builder::select_clause::DefaultSelectClause>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause>>>>` + = note: this error originates in the macro `$crate::__diesel_column` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/diesel_compile_tests/tests/fail/array_expressions_must_be_correct_type.stderr b/diesel_compile_tests/tests/fail/array_expressions_must_be_correct_type.stderr index a236f053739f..07be69b27394 100644 --- a/diesel_compile_tests/tests/fail/array_expressions_must_be_correct_type.stderr +++ b/diesel_compile_tests/tests/fail/array_expressions_must_be_correct_type.stderr @@ -4,17 +4,17 @@ error[E0277]: the trait bound `f64: SelectableExpression` is not s 9 | select(array((1f64, 3f64))).get_result::>(&mut connection); | ^^^^^^ the trait `SelectableExpression` is not implemented for `f64` | - ::: $DIESEL/src/query_builder/functions.rs - | - | crate::dsl::BareSelect: AsQuery, - | ------- required by this bound in `diesel::select` - | = note: required because of the requirements on the impl of `SelectableExpression` for `(f64, f64)` = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `SelectableExpression` for `diesel::pg::expression::array::ArrayLiteral<(f64, f64), diesel::sql_types::Integer>` = note: required because of the requirements on the impl of `diesel::query_builder::select_clause::SelectClauseExpression` for `diesel::query_builder::select_clause::SelectClause>` = note: required because of the requirements on the impl of `Query` for `SelectStatement>>` = note: required because of the requirements on the impl of `AsQuery` for `SelectStatement>>` +note: required by a bound in `diesel::select` + --> $DIESEL/src/query_builder/functions.rs + | + | crate::dsl::BareSelect: AsQuery, + | ^^^^^^^ required by this bound in `diesel::select` error[E0277]: the trait bound `f64: ValidGrouping<()>` is not satisfied --> tests/fail/array_expressions_must_be_correct_type.rs:9:5 @@ -22,16 +22,16 @@ error[E0277]: the trait bound `f64: ValidGrouping<()>` is not satisfied 9 | select(array((1f64, 3f64))).get_result::>(&mut connection); | ^^^^^^ the trait `ValidGrouping<()>` is not implemented for `f64` | - ::: $DIESEL/src/query_builder/functions.rs - | - | crate::dsl::BareSelect: AsQuery, - | ------- required by this bound in `diesel::select` - | = note: required because of the requirements on the impl of `ValidGrouping<()>` for `(f64, f64)` = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `ValidGrouping<()>` for `diesel::pg::expression::array::ArrayLiteral<(f64, f64), diesel::sql_types::Integer>` = note: required because of the requirements on the impl of `Query` for `SelectStatement>>` = note: required because of the requirements on the impl of `AsQuery` for `SelectStatement>>` +note: required by a bound in `diesel::select` + --> $DIESEL/src/query_builder/functions.rs + | + | crate::dsl::BareSelect: AsQuery, + | ^^^^^^^ required by this bound in `diesel::select` error[E0277]: the trait bound `f64: SelectableExpression` is not satisfied --> tests/fail/array_expressions_must_be_correct_type.rs:9:33 @@ -86,10 +86,18 @@ error[E0277]: the trait bound `f64: diesel::Expression` is not satisfied 9 | select(array((1f64, 3f64))).get_result::>(&mut connection); | ^^^^^ the trait `diesel::Expression` is not implemented for `f64` | - ::: $DIESEL/src/pg/expression/array.rs - | - | T: AsExpressionList, - | -------------------- required by this bound in `diesel::dsl::array` - | = note: required because of the requirements on the impl of `AsExpression` for `f64` = note: required because of the requirements on the impl of `AsExpressionList` for `(f64, f64)` +note: required by a bound in `diesel::dsl::array` + --> $DIESEL/src/pg/expression/array.rs + | + | T: AsExpressionList, + | ^^^^^^^^^^^^^^^^^^^^ required by this bound in `diesel::dsl::array` + +error[E0277]: the trait bound `f64: diesel::Expression` is not satisfied + --> tests/fail/array_expressions_must_be_correct_type.rs:9:12 + | +9 | select(array((1f64, 3f64))).get_result::>(&mut connection); + | ^^^^^^^^^^^^^^^^^^^ the trait `diesel::Expression` is not implemented for `f64` + | + = note: required because of the requirements on the impl of `AsExpression` for `f64` diff --git a/diesel_compile_tests/tests/fail/array_expressions_must_be_same_type.stderr b/diesel_compile_tests/tests/fail/array_expressions_must_be_same_type.stderr index 19531cbf9017..bb87fbccf054 100644 --- a/diesel_compile_tests/tests/fail/array_expressions_must_be_same_type.stderr +++ b/diesel_compile_tests/tests/fail/array_expressions_must_be_same_type.stderr @@ -4,17 +4,17 @@ error[E0277]: the trait bound `f64: SelectableExpression` is not s 11 | select(array((1, 3f64))).get_result::>(&mut connection).unwrap(); | ^^^^^^ the trait `SelectableExpression` is not implemented for `f64` | - ::: $DIESEL/src/query_builder/functions.rs - | - | crate::dsl::BareSelect: AsQuery, - | ------- required by this bound in `diesel::select` - | = note: required because of the requirements on the impl of `SelectableExpression` for `(diesel::internal::derives::as_expression::Bound, f64)` = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `SelectableExpression` for `diesel::pg::expression::array::ArrayLiteral<(diesel::internal::derives::as_expression::Bound, f64), diesel::sql_types::Integer>` = note: required because of the requirements on the impl of `diesel::query_builder::select_clause::SelectClauseExpression` for `diesel::query_builder::select_clause::SelectClause, f64), diesel::sql_types::Integer>>` = note: required because of the requirements on the impl of `Query` for `SelectStatement, f64), diesel::sql_types::Integer>>>` = note: required because of the requirements on the impl of `AsQuery` for `SelectStatement, f64), diesel::sql_types::Integer>>>` +note: required by a bound in `diesel::select` + --> $DIESEL/src/query_builder/functions.rs + | + | crate::dsl::BareSelect: AsQuery, + | ^^^^^^^ required by this bound in `diesel::select` error[E0277]: the trait bound `f64: ValidGrouping<()>` is not satisfied --> tests/fail/array_expressions_must_be_same_type.rs:11:5 @@ -22,16 +22,16 @@ error[E0277]: the trait bound `f64: ValidGrouping<()>` is not satisfied 11 | select(array((1, 3f64))).get_result::>(&mut connection).unwrap(); | ^^^^^^ the trait `ValidGrouping<()>` is not implemented for `f64` | - ::: $DIESEL/src/query_builder/functions.rs - | - | crate::dsl::BareSelect: AsQuery, - | ------- required by this bound in `diesel::select` - | - = note: required because of the requirements on the impl of `ValidGrouping<()>` for `(diesel::internal::derives::as_expression::Bound, f64)` - = note: 1 redundant requirements hidden + = note: required because of the requirements on the impl of `ValidGrouping<()>` for `(f64,)` + = note: 2 redundant requirements hidden = note: required because of the requirements on the impl of `ValidGrouping<()>` for `diesel::pg::expression::array::ArrayLiteral<(diesel::internal::derives::as_expression::Bound, f64), diesel::sql_types::Integer>` = note: required because of the requirements on the impl of `Query` for `SelectStatement, f64), diesel::sql_types::Integer>>>` = note: required because of the requirements on the impl of `AsQuery` for `SelectStatement, f64), diesel::sql_types::Integer>>>` +note: required by a bound in `diesel::select` + --> $DIESEL/src/query_builder/functions.rs + | + | crate::dsl::BareSelect: AsQuery, + | ^^^^^^^ required by this bound in `diesel::select` error[E0277]: the trait bound `f64: SelectableExpression` is not satisfied --> tests/fail/array_expressions_must_be_same_type.rs:11:30 @@ -52,8 +52,8 @@ error[E0277]: the trait bound `f64: ValidGrouping<()>` is not satisfied 11 | select(array((1, 3f64))).get_result::>(&mut connection).unwrap(); | ^^^^^^^^^^ the trait `ValidGrouping<()>` is not implemented for `f64` | - = note: required because of the requirements on the impl of `ValidGrouping<()>` for `(diesel::internal::derives::as_expression::Bound, f64)` - = note: 1 redundant requirements hidden + = note: required because of the requirements on the impl of `ValidGrouping<()>` for `(f64,)` + = note: 2 redundant requirements hidden = note: required because of the requirements on the impl of `ValidGrouping<()>` for `diesel::pg::expression::array::ArrayLiteral<(diesel::internal::derives::as_expression::Bound, f64), diesel::sql_types::Integer>` = note: required because of the requirements on the impl of `Query` for `SelectStatement, f64), diesel::sql_types::Integer>>>` = note: required because of the requirements on the impl of `LoadQuery<'_, _, Vec>` for `SelectStatement, f64), diesel::sql_types::Integer>>>` @@ -86,13 +86,13 @@ error[E0277]: the trait bound `f64: diesel::Expression` is not satisfied 11 | select(array((1, 3f64))).get_result::>(&mut connection).unwrap(); | ^^^^^ the trait `diesel::Expression` is not implemented for `f64` | - ::: $DIESEL/src/pg/expression/array.rs - | - | T: AsExpressionList, - | -------------------- required by this bound in `diesel::dsl::array` - | = note: required because of the requirements on the impl of `AsExpression` for `f64` = note: required because of the requirements on the impl of `AsExpressionList` for `(i32, f64)` +note: required by a bound in `diesel::dsl::array` + --> $DIESEL/src/pg/expression/array.rs + | + | T: AsExpressionList, + | ^^^^^^^^^^^^^^^^^^^^ required by this bound in `diesel::dsl::array` error[E0277]: the trait bound `{integer}: SelectableExpression` is not satisfied --> tests/fail/array_expressions_must_be_same_type.rs:12:5 @@ -100,23 +100,23 @@ error[E0277]: the trait bound `{integer}: SelectableExpression` is 12 | select(array((1, 3f64))).get_result::>(&mut connection).unwrap(); | ^^^^^^ the trait `SelectableExpression` is not implemented for `{integer}` | - ::: $DIESEL/src/query_builder/functions.rs - | - | crate::dsl::BareSelect: AsQuery, - | ------- required by this bound in `diesel::select` - | = help: the following implementations were found: <&'a T as SelectableExpression> <(T0, T1) as SelectableExpression> <(T0, T1, T2) as SelectableExpression> <(T0, T1, T2, T3) as SelectableExpression> - and 155 others + and 158 others = note: required because of the requirements on the impl of `SelectableExpression` for `({integer}, diesel::internal::derives::as_expression::Bound)` = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `SelectableExpression` for `diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::internal::derives::as_expression::Bound), diesel::sql_types::Double>` = note: required because of the requirements on the impl of `diesel::query_builder::select_clause::SelectClauseExpression` for `diesel::query_builder::select_clause::SelectClause), diesel::sql_types::Double>>` = note: required because of the requirements on the impl of `Query` for `SelectStatement), diesel::sql_types::Double>>>` = note: required because of the requirements on the impl of `AsQuery` for `SelectStatement), diesel::sql_types::Double>>>` +note: required by a bound in `diesel::select` + --> $DIESEL/src/query_builder/functions.rs + | + | crate::dsl::BareSelect: AsQuery, + | ^^^^^^^ required by this bound in `diesel::select` error[E0277]: the trait bound `{integer}: ValidGrouping<()>` is not satisfied --> tests/fail/array_expressions_must_be_same_type.rs:12:5 @@ -124,22 +124,22 @@ error[E0277]: the trait bound `{integer}: ValidGrouping<()>` is not satisfied 12 | select(array((1, 3f64))).get_result::>(&mut connection).unwrap(); | ^^^^^^ the trait `ValidGrouping<()>` is not implemented for `{integer}` | - ::: $DIESEL/src/query_builder/functions.rs - | - | crate::dsl::BareSelect: AsQuery, - | ------- required by this bound in `diesel::select` - | = help: the following implementations were found: <&'a T as ValidGrouping> <(T0, T1) as ValidGrouping<__GroupByClause>> <(T0, T1, T2) as ValidGrouping<__GroupByClause>> <(T0, T1, T2, T3) as ValidGrouping<__GroupByClause>> - and 140 others + and 143 others = note: required because of the requirements on the impl of `ValidGrouping<()>` for `({integer}, diesel::internal::derives::as_expression::Bound)` = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `ValidGrouping<()>` for `diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::internal::derives::as_expression::Bound), diesel::sql_types::Double>` = note: required because of the requirements on the impl of `Query` for `SelectStatement), diesel::sql_types::Double>>>` = note: required because of the requirements on the impl of `AsQuery` for `SelectStatement), diesel::sql_types::Double>>>` +note: required by a bound in `diesel::select` + --> $DIESEL/src/query_builder/functions.rs + | + | crate::dsl::BareSelect: AsQuery, + | ^^^^^^^ required by this bound in `diesel::select` error[E0277]: the trait bound `{integer}: SelectableExpression` is not satisfied --> tests/fail/array_expressions_must_be_same_type.rs:12:30 @@ -152,7 +152,7 @@ error[E0277]: the trait bound `{integer}: SelectableExpression` is <(T0, T1) as SelectableExpression> <(T0, T1, T2) as SelectableExpression> <(T0, T1, T2, T3) as SelectableExpression> - and 155 others + and 158 others = note: required because of the requirements on the impl of `SelectableExpression` for `({integer}, diesel::internal::derives::as_expression::Bound)` = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `SelectableExpression` for `diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::internal::derives::as_expression::Bound), diesel::sql_types::Double>` @@ -171,7 +171,7 @@ error[E0277]: the trait bound `{integer}: ValidGrouping<()>` is not satisfied <(T0, T1) as ValidGrouping<__GroupByClause>> <(T0, T1, T2) as ValidGrouping<__GroupByClause>> <(T0, T1, T2, T3) as ValidGrouping<__GroupByClause>> - and 140 others + and 143 others = note: required because of the requirements on the impl of `ValidGrouping<()>` for `({integer}, diesel::internal::derives::as_expression::Bound)` = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `ValidGrouping<()>` for `diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::internal::derives::as_expression::Bound), diesel::sql_types::Double>` @@ -189,7 +189,7 @@ error[E0277]: the trait bound `{integer}: QueryFragment` is not satisfied <() as QueryFragment> <(T0, T1) as QueryFragment<__DB>> <(T0, T1, T2) as QueryFragment<__DB>> - and 269 others + and 273 others = note: required because of the requirements on the impl of `QueryFragment` for `({integer}, diesel::internal::derives::as_expression::Bound)` = note: 3 redundant requirements hidden = note: required because of the requirements on the impl of `QueryFragment` for `SelectStatement), diesel::sql_types::Double>>>` @@ -206,7 +206,7 @@ error[E0277]: the trait bound `{integer}: QueryId` is not satisfied <() as QueryId> <(T0, T1) as QueryId> <(T0, T1, T2) as QueryId> - and 231 others + and 234 others = note: required because of the requirements on the impl of `QueryId` for `({integer}, diesel::internal::derives::as_expression::Bound)` = note: 3 redundant requirements hidden = note: required because of the requirements on the impl of `QueryId` for `SelectStatement), diesel::sql_types::Double>>>` @@ -218,16 +218,38 @@ error[E0277]: the trait bound `{integer}: diesel::Expression` is not satisfied 12 | select(array((1, 3f64))).get_result::>(&mut connection).unwrap(); | ^^^^^ the trait `diesel::Expression` is not implemented for `{integer}` | - ::: $DIESEL/src/pg/expression/array.rs + = help: the following implementations were found: + <&'a T as diesel::Expression> + <(T0, T1) as diesel::Expression> + <(T0, T1, T2) as diesel::Expression> + <(T0, T1, T2, T3) as diesel::Expression> + and 123 others + = note: required because of the requirements on the impl of `AsExpression` for `{integer}` + = note: required because of the requirements on the impl of `AsExpressionList` for `({integer}, f64)` +note: required by a bound in `diesel::dsl::array` + --> $DIESEL/src/pg/expression/array.rs | | T: AsExpressionList, - | -------------------- required by this bound in `diesel::dsl::array` + | ^^^^^^^^^^^^^^^^^^^^ required by this bound in `diesel::dsl::array` + +error[E0277]: the trait bound `f64: diesel::Expression` is not satisfied + --> tests/fail/array_expressions_must_be_same_type.rs:11:12 + | +11 | select(array((1, 3f64))).get_result::>(&mut connection).unwrap(); + | ^^^^^^^^^^^^^^^^ the trait `diesel::Expression` is not implemented for `f64` + | + = note: required because of the requirements on the impl of `AsExpression` for `f64` + +error[E0277]: the trait bound `{integer}: diesel::Expression` is not satisfied + --> tests/fail/array_expressions_must_be_same_type.rs:12:12 + | +12 | select(array((1, 3f64))).get_result::>(&mut connection).unwrap(); + | ^^^^^^^^^^^^^^^^ the trait `diesel::Expression` is not implemented for `{integer}` | = help: the following implementations were found: <&'a T as diesel::Expression> <(T0, T1) as diesel::Expression> <(T0, T1, T2) as diesel::Expression> <(T0, T1, T2, T3) as diesel::Expression> - and 120 others + and 123 others = note: required because of the requirements on the impl of `AsExpression` for `{integer}` - = note: required because of the requirements on the impl of `AsExpressionList` for `({integer}, f64)` diff --git a/diesel_compile_tests/tests/fail/boxed_queries_and_group_by.stderr b/diesel_compile_tests/tests/fail/boxed_queries_and_group_by.stderr index d578f4302194..a4f9483ecb75 100644 --- a/diesel_compile_tests/tests/fail/boxed_queries_and_group_by.stderr +++ b/diesel_compile_tests/tests/fail/boxed_queries_and_group_by.stderr @@ -69,8 +69,8 @@ error[E0271]: type mismatch resolving `, _>` - found struct `SelectStatement, _>>>` + = note: expected struct `BoxedSelectStatement<'_, (diesel::sql_types::Integer, diesel::sql_types::Text), FromClause, _>` + found struct `SelectStatement, _>>>` = note: required because of the requirements on the impl of `GroupByDsl<_>` for `BoxedSelectStatement<'_, (diesel::sql_types::Integer, diesel::sql_types::Text), FromClause, _>` error[E0277]: the trait bound `BoxedSelectStatement<'_, (diesel::sql_types::Integer, diesel::sql_types::Text), FromClause, _>: Table` is not satisfied diff --git a/diesel_compile_tests/tests/fail/boxed_queries_require_selectable_expression_for_filter.stderr b/diesel_compile_tests/tests/fail/boxed_queries_require_selectable_expression_for_filter.stderr index 3b0a284fe965..2a3a608fe729 100644 --- a/diesel_compile_tests/tests/fail/boxed_queries_require_selectable_expression_for_filter.stderr +++ b/diesel_compile_tests/tests/fail/boxed_queries_require_selectable_expression_for_filter.stderr @@ -4,10 +4,20 @@ error[E0271]: type mismatch resolving `().filter(posts::title.eq("Hello")); | ^^^^^^ expected struct `diesel::query_source::Never`, found struct `diesel::query_source::Once` | - = note: required because of the requirements on the impl of `AppearsOnTable` for `posts::columns::title` +note: required because of the requirements on the impl of `AppearsOnTable` for `posts::columns::title` + --> tests/fail/boxed_queries_require_selectable_expression_for_filter.rs:13:1 + | +13 | / table! { +14 | | posts { +15 | | id -> Integer, +16 | | title -> VarChar, +17 | | } +18 | | } + | |_^ = note: 2 redundant requirements hidden = note: required because of the requirements on the impl of `AppearsOnTable` for `diesel::expression::grouped::Grouped>>` = note: required because of the requirements on the impl of `FilterDsl>>>` for `BoxedSelectStatement<'_, (diesel::sql_types::Integer, diesel::sql_types::Text), FromClause, Pg>` + = note: this error originates in the macro `$crate::__diesel_column` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `users::table: TableNotEqual` is not satisfied --> tests/fail/boxed_queries_require_selectable_expression_for_filter.rs:21:37 @@ -16,7 +26,17 @@ error[E0277]: the trait bound `users::table: TableNotEqual` is not | ^^^^^^ the trait `TableNotEqual` is not implemented for `users::table` | = note: required because of the requirements on the impl of `AppearsInFromClause` for `users::table` - = note: required because of the requirements on the impl of `AppearsOnTable` for `posts::columns::title` +note: required because of the requirements on the impl of `AppearsOnTable` for `posts::columns::title` + --> tests/fail/boxed_queries_require_selectable_expression_for_filter.rs:13:1 + | +13 | / table! { +14 | | posts { +15 | | id -> Integer, +16 | | title -> VarChar, +17 | | } +18 | | } + | |_^ = note: 2 redundant requirements hidden = note: required because of the requirements on the impl of `AppearsOnTable` for `diesel::expression::grouped::Grouped>>` = note: required because of the requirements on the impl of `FilterDsl>>>` for `BoxedSelectStatement<'_, (diesel::sql_types::Integer, diesel::sql_types::Text), FromClause, Pg>` + = note: this error originates in the macro `$crate::__diesel_column` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/diesel_compile_tests/tests/fail/boxed_queries_require_selectable_expression_for_order.stderr b/diesel_compile_tests/tests/fail/boxed_queries_require_selectable_expression_for_order.stderr index 26186546641b..0a260f68dfb9 100644 --- a/diesel_compile_tests/tests/fail/boxed_queries_require_selectable_expression_for_order.stderr +++ b/diesel_compile_tests/tests/fail/boxed_queries_require_selectable_expression_for_order.stderr @@ -6,7 +6,17 @@ error[E0277]: the trait bound `FromClause: AppearsInFromClause as AppearsInFromClause> - = note: required because of the requirements on the impl of `AppearsOnTable>` for `posts::columns::title` +note: required because of the requirements on the impl of `AppearsOnTable>` for `posts::columns::title` + --> tests/fail/boxed_queries_require_selectable_expression_for_order.rs:13:1 + | +13 | / table! { +14 | | posts { +15 | | id -> Integer, +16 | | title -> VarChar, +17 | | } +18 | | } + | |_^ = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `AppearsOnTable>` for `diesel::expression::operators::Desc` = note: required because of the requirements on the impl of `OrderDsl>` for `BoxedSelectStatement<'_, (diesel::sql_types::Integer, diesel::sql_types::Text), FromClause, Pg>` + = note: this error originates in the macro `$crate::__diesel_column` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/diesel_compile_tests/tests/fail/cannot_mix_aggregate_and_non_aggregate_selects.stderr b/diesel_compile_tests/tests/fail/cannot_mix_aggregate_and_non_aggregate_selects.stderr index 1b0f8ad5b7e9..cccc72b55769 100644 --- a/diesel_compile_tests/tests/fail/cannot_mix_aggregate_and_non_aggregate_selects.stderr +++ b/diesel_compile_tests/tests/fail/cannot_mix_aggregate_and_non_aggregate_selects.stderr @@ -31,7 +31,12 @@ error[E0277]: the trait bound `diesel::expression::is_aggregate::No: MixedAggreg = help: the following implementations were found: > > - = note: required because of the requirements on the impl of `ValidGrouping<()>` for `__Derived, columns::nullable_int_col>>` +note: required because of the requirements on the impl of `ValidGrouping<()>` for `__Derived, columns::nullable_int_col>>` + --> tests/fail/cannot_mix_aggregate_and_non_aggregate_selects.rs:14:1 + | +14 | sql_function!(fn f(x: Nullable, y: Nullable) -> Nullable); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `ValidGrouping<()>` for `f::f, columns::nullable_int_col>>` = note: required because of the requirements on the impl of `SelectDsl, columns::nullable_int_col>>>` for `SelectStatement>` + = note: this error originates in the macro `sql_function` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/diesel_compile_tests/tests/fail/cannot_update_target_with_methods_other_than_filter_called.stderr b/diesel_compile_tests/tests/fail/cannot_update_target_with_methods_other_than_filter_called.stderr index 430c6f632f4d..bc887e6805d6 100644 --- a/diesel_compile_tests/tests/fail/cannot_update_target_with_methods_other_than_filter_called.stderr +++ b/diesel_compile_tests/tests/fail/cannot_update_target_with_methods_other_than_filter_called.stderr @@ -4,12 +4,12 @@ error[E0277]: the trait bound `SelectStatement, diesel: 15 | let command = update(users.select(id)).set(id.eq(1)); | ^^^^^^^^^^^^^^^^ the trait `Identifiable` is not implemented for `SelectStatement, diesel::query_builder::select_clause::SelectClause>` | - ::: $DIESEL/src/query_builder/functions.rs + = note: required because of the requirements on the impl of `IntoUpdateTarget` for `SelectStatement, diesel::query_builder::select_clause::SelectClause>` +note: required by a bound in `diesel::update` + --> $DIESEL/src/query_builder/functions.rs | | pub fn update(source: T) -> UpdateStatement { - | ---------------- required by this bound in `diesel::update` - | - = note: required because of the requirements on the impl of `IntoUpdateTarget` for `SelectStatement, diesel::query_builder::select_clause::SelectClause>` + | ^^^^^^^^^^^^^^^^ required by this bound in `diesel::update` error[E0277]: the trait bound `SelectStatement, diesel::query_builder::select_clause::DefaultSelectClause>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::NoWhereClause, diesel::query_builder::order_clause::OrderClause>: Identifiable` is not satisfied --> tests/fail/cannot_update_target_with_methods_other_than_filter_called.rs:16:26 @@ -17,9 +17,25 @@ error[E0277]: the trait bound `SelectStatement, diesel: 16 | let command = update(users.order(id)).set(id.eq(1)); | ^^^^^^^^^^^^^^^ the trait `Identifiable` is not implemented for `SelectStatement, diesel::query_builder::select_clause::DefaultSelectClause>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::NoWhereClause, diesel::query_builder::order_clause::OrderClause>` | - ::: $DIESEL/src/query_builder/functions.rs + = note: required because of the requirements on the impl of `IntoUpdateTarget` for `SelectStatement, diesel::query_builder::select_clause::DefaultSelectClause>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::NoWhereClause, diesel::query_builder::order_clause::OrderClause>` +note: required by a bound in `diesel::update` + --> $DIESEL/src/query_builder/functions.rs | | pub fn update(source: T) -> UpdateStatement { - | ---------------- required by this bound in `diesel::update` + | ^^^^^^^^^^^^^^^^ required by this bound in `diesel::update` + +error[E0277]: the trait bound `SelectStatement, diesel::query_builder::select_clause::SelectClause>: Identifiable` is not satisfied + --> tests/fail/cannot_update_target_with_methods_other_than_filter_called.rs:15:19 + | +15 | let command = update(users.select(id)).set(id.eq(1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Identifiable` is not implemented for `SelectStatement, diesel::query_builder::select_clause::SelectClause>` + | + = note: required because of the requirements on the impl of `IntoUpdateTarget` for `SelectStatement, diesel::query_builder::select_clause::SelectClause>` + +error[E0277]: the trait bound `SelectStatement, diesel::query_builder::select_clause::DefaultSelectClause>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::NoWhereClause, diesel::query_builder::order_clause::OrderClause>: Identifiable` is not satisfied + --> tests/fail/cannot_update_target_with_methods_other_than_filter_called.rs:16:19 + | +16 | let command = update(users.order(id)).set(id.eq(1)); + | ^^^^^^^^^^^^^^^^^^^^^^^ the trait `Identifiable` is not implemented for `SelectStatement, diesel::query_builder::select_clause::DefaultSelectClause>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::NoWhereClause, diesel::query_builder::order_clause::OrderClause>` | = note: required because of the requirements on the impl of `IntoUpdateTarget` for `SelectStatement, diesel::query_builder::select_clause::DefaultSelectClause>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::NoWhereClause, diesel::query_builder::order_clause::OrderClause>` diff --git a/diesel_compile_tests/tests/fail/columns_cannot_be_rhs_of_insert.stderr b/diesel_compile_tests/tests/fail/columns_cannot_be_rhs_of_insert.stderr index ffa9f34f2f80..81511790bd50 100644 --- a/diesel_compile_tests/tests/fail/columns_cannot_be_rhs_of_insert.stderr +++ b/diesel_compile_tests/tests/fail/columns_cannot_be_rhs_of_insert.stderr @@ -4,7 +4,17 @@ error[E0271]: type mismatch resolving `` for `columns::hair_color` +note: required because of the requirements on the impl of `AppearsOnTable` for `columns::hair_color` + --> tests/fail/columns_cannot_be_rhs_of_insert.rs:6:1 + | +6 | / table! { +7 | | users { +8 | | id -> Integer, +9 | | name -> Text, +10 | | hair_color -> Text, +11 | | } +12 | | } + | |_^ = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `AppearsOnTable` for `&columns::hair_color` = note: required because of the requirements on the impl of `InsertValues` for `ColumnInsertValue` @@ -12,3 +22,4 @@ error[E0271]: type mismatch resolving `