Skip to content

Commit b416410

Browse files
committed
Release 0.8.5: transaction override fix & tests
Bump crate and macro versions to 0.8.5 and update docs/examples. Reworked Database::transaction to reinstall the transaction-scoped database override on every poll using a ThreadOverrideGuard and poll_with_thread_override so model/query helpers continue to use the active transaction even if the future is resumed on another thread. Moved inline unit tests out of implementation modules into dedicated files under src/testing/ and added a regression test that polls a transaction-scoped future on two different threads to verify per-poll override behavior. Also updated crate editions to 2021 and refreshed CHANGELOG entries.
1 parent d283af5 commit b416410

14 files changed

Lines changed: 269 additions & 135 deletions

CHANGELOG.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ All notable changes to TideORM will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.8.5] - 2026-03-20
9+
10+
### Fixed
11+
12+
- Reworked `Database::transaction()` to restore the transaction-scoped database override on every future poll, so model and query helpers keep using the active transaction even if the async runtime resumes the future on a different thread.
13+
14+
### Changed
15+
16+
- Moved the remaining inline unit tests out of implementation modules and into dedicated owner test files under `src/testing/`, keeping private-module coverage while making the source files easier to maintain.
17+
- Refreshed dependency examples and macro-crate docs to use the 0.8.5 release version.
18+
19+
### Internal
20+
21+
- Added a regression test that manually polls a transaction-scoped future on two different threads to verify the per-poll override behavior.
22+
- Verified the release prep with `cargo test --lib --features sqlite` and `cargo clippy --workspace --all-targets -- -D warnings`.
23+
824
## [0.8.4] - 2026-03-20
925

1026
### Fixed
@@ -571,7 +587,8 @@ This is the first public release of TideORM, a developer-friendly ORM for Rust w
571587
- **Repository:** [https://github.com/mohamadzoh/tideorm](https://github.com/mohamadzoh/tideorm)
572588
- **Documentation:** See README.md and examples/
573589

574-
[Unreleased]: https://github.com/mohamadzoh/tideorm/compare/v0.8.4...HEAD
590+
[Unreleased]: https://github.com/mohamadzoh/tideorm/compare/v0.8.5...HEAD
591+
[0.8.5]: https://github.com/mohamadzoh/tideorm/compare/v0.8.4...v0.8.5
575592
[0.8.4]: https://github.com/mohamadzoh/tideorm/compare/v0.8.1...v0.8.4
576593
[0.8.1]: https://github.com/mohamadzoh/tideorm/compare/v0.8.0...v0.8.1
577594
[0.8.0]: https://github.com/mohamadzoh/tideorm/compare/v0.7.3...v0.8.0

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "tideorm"
3-
version = "0.8.4"
4-
edition = "2024"
3+
version = "0.8.5"
4+
edition = "2021"
55
authors = ["Mohamad Al Zohbie <alzoubi528@gmail.com>"]
66
description = "A developer-friendly ORM for Rust with clean, expressive syntax"
77
license = "MIT"
@@ -48,7 +48,7 @@ rust_decimal = { version = "1.40.0", features = ["serde"] }
4848
thiserror = "2.0.18"
4949

5050
# Derive macros (our own crate)
51-
tideorm-macros = { version = "0.8.4", path = "tideorm-macros" }
51+
tideorm-macros = { version = "0.8.5", path = "tideorm-macros" }
5252

5353
# Utils
5454
parking_lot = "0.12.5"

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,22 +192,22 @@ let recent_posts = user.posts.load_with(|q| {
192192
```toml
193193
[dependencies]
194194
# PostgreSQL (default)
195-
tideorm = { version = "0.8.4", features = ["postgres"] }
195+
tideorm = { version = "0.8.5", features = ["postgres"] }
196196

197197
# MySQL
198-
tideorm = { version = "0.8.4", features = ["mysql"] }
198+
tideorm = { version = "0.8.5", features = ["mysql"] }
199199

200200
# SQLite
201-
tideorm = { version = "0.8.4", features = ["sqlite"] }
201+
tideorm = { version = "0.8.5", features = ["sqlite"] }
202202

203203
# Enable attachments support explicitly
204-
tideorm = { version = "0.8.4", features = ["postgres", "attachments"] }
204+
tideorm = { version = "0.8.5", features = ["postgres", "attachments"] }
205205

206206
# Enable translations support explicitly
207-
tideorm = { version = "0.8.4", features = ["postgres", "translations"] }
207+
tideorm = { version = "0.8.5", features = ["postgres", "translations"] }
208208

209209
# Enable full-text search support explicitly
210-
tideorm = { version = "0.8.4", features = ["postgres", "fulltext"] }
210+
tideorm = { version = "0.8.5", features = ["postgres", "fulltext"] }
211211
```
212212

213213
### Feature Flags

docs/queries.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ TideORM provides full-text search capabilities across PostgreSQL (tsvector/tsque
514514
Enable the feature explicitly when you need the full-text search API:
515515

516516
```toml
517-
tideorm = { version = "0.8.4", features = ["postgres", "fulltext"] }
517+
tideorm = { version = "0.8.5", features = ["postgres", "fulltext"] }
518518
```
519519

520520
### Search Basics

docs/relations.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ Enable the feature first:
210210

211211
```toml
212212
[dependencies]
213-
tideorm = { version = "0.8.4", features = ["postgres", "attachments"] }
213+
tideorm = { version = "0.8.5", features = ["postgres", "attachments"] }
214214
```
215215

216216
### Model Setup
@@ -545,7 +545,7 @@ Enable the feature first:
545545

546546
```toml
547547
[dependencies]
548-
tideorm = { version = "0.8.4", features = ["postgres", "translations"] }
548+
tideorm = { version = "0.8.5", features = ["postgres", "translations"] }
549549
```
550550

551551
### Model Setup

src/database.rs

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,36 @@ fn current_scope_handle() -> Result<DatabaseHandle> {
174174
global_db_handle().current_handle()
175175
}
176176

177+
struct ThreadOverrideGuard {
178+
previous: Option<DatabaseHandle>,
179+
}
180+
181+
impl ThreadOverrideGuard {
182+
fn install(handle: DatabaseHandle) -> Self {
183+
Self {
184+
previous: Database::replace_thread_override(Some(handle)),
185+
}
186+
}
187+
}
188+
189+
impl Drop for ThreadOverrideGuard {
190+
fn drop(&mut self) {
191+
Database::set_thread_override(self.previous.take());
192+
}
193+
}
194+
195+
fn poll_with_thread_override<F>(
196+
future: std::pin::Pin<&mut F>,
197+
cx: &mut std::task::Context<'_>,
198+
handle: &DatabaseHandle,
199+
) -> std::task::Poll<F::Output>
200+
where
201+
F: Future + ?Sized,
202+
{
203+
let _guard = ThreadOverrideGuard::install(handle.clone());
204+
future.poll(cx)
205+
}
206+
177207
#[doc(hidden)]
178208
pub fn __current_connection() -> Result<ConnectionRef> {
179209
Ok(match current_scope_handle()? {
@@ -423,15 +453,20 @@ impl Database {
423453
.map_err(|e| Error::transaction(e.to_string()))?,
424454
};
425455

426-
let txn = Arc::new(txn);
427-
let tx = Transaction { inner: txn.clone() };
428-
let previous_override =
429-
Self::replace_thread_override(Some(DatabaseHandle::Transaction(txn.clone())));
430-
431-
let outcome = f(&tx).await;
456+
let outcome = {
457+
let txn = Arc::new(txn);
458+
let tx = Transaction { inner: txn.clone() };
459+
let override_handle = DatabaseHandle::Transaction(txn.clone());
460+
let mut future = std::pin::pin!(f(&tx));
461+
let outcome = std::future::poll_fn(|cx| {
462+
poll_with_thread_override(future.as_mut(), cx, &override_handle)
463+
})
464+
.await;
465+
466+
(txn, outcome)
467+
};
432468

433-
Self::set_thread_override(previous_override);
434-
drop(tx);
469+
let (txn, outcome) = outcome;
435470

436471
match outcome {
437472
Ok(result) => {
@@ -1063,22 +1098,5 @@ impl Connection for Transaction {
10631098
}
10641099

10651100
#[cfg(test)]
1066-
mod tests {
1067-
use super::{Connection, Database};
1068-
1069-
#[test]
1070-
fn hidden_accessors_return_errors_for_disconnected_database() {
1071-
let db = Database::disconnected();
1072-
1073-
assert!(db.__internal_connection().is_err());
1074-
assert!(db.__internal_backend().is_err());
1075-
assert!(db.__get_connection().is_err());
1076-
}
1077-
1078-
#[test]
1079-
fn backend_defaults_safely_for_disconnected_database() {
1080-
let db = Database::disconnected();
1081-
1082-
assert_eq!(db.backend(), crate::config::DatabaseType::Postgres);
1083-
}
1084-
}
1101+
#[path = "testing/database_tests.rs"]
1102+
mod tests;

src/model/serialization.rs

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -503,25 +503,5 @@ where
503503
}
504504

505505
#[cfg(test)]
506-
mod tests {
507-
use super::hash_map_output_key;
508-
use serde_json::json;
509-
510-
#[test]
511-
fn hash_map_output_key_hides_structured_presenter_params() {
512-
assert_eq!(
513-
hash_map_output_key("params", &json!({"view": "minimal"})),
514-
None
515-
);
516-
assert_eq!(hash_map_output_key("params", &json!(["minimal"])), None);
517-
assert_eq!(hash_map_output_key("title", &json!("title")), Some("title"));
518-
}
519-
520-
#[test]
521-
fn hash_map_output_key_preserves_scalar_params_values() {
522-
assert_eq!(
523-
hash_map_output_key("params", &json!("keep me")),
524-
Some("params")
525-
);
526-
}
527-
}
506+
#[path = "../testing/model_serialization_tests.rs"]
507+
mod tests;

src/relations.rs

Lines changed: 2 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -602,74 +602,8 @@ impl<'de, E: Model> Deserialize<'de> for SelfRefMany<E> {
602602
}
603603

604604
#[cfg(test)]
605-
mod tests {
606-
use super::{Value, build_self_ref_tree_sql};
607-
use crate::config::DatabaseType;
608-
609-
#[derive(tideorm::Model)]
610-
#[tideorm(table = "relation_test_nodes")]
611-
struct RelationTestNode {
612-
#[tideorm(primary_key, auto_increment)]
613-
id: i64,
614-
slug: String,
615-
parent_slug: Option<String>,
616-
}
617-
618-
#[test]
619-
fn self_ref_tree_sql_uses_recursive_cte_and_local_key() {
620-
let (sql, params) = build_self_ref_tree_sql::<RelationTestNode>(
621-
"parent_slug",
622-
"slug",
623-
&serde_json::json!("root"),
624-
3,
625-
DatabaseType::Postgres,
626-
)
627-
.unwrap();
628-
629-
assert!(sql.starts_with("WITH RECURSIVE \"tide_tree\""));
630-
assert!(sql.contains("\"node\".\"slug\" AS \"tree_key\""));
631-
assert!(sql.contains("\"child\".\"parent_slug\" = \"tree\".\"tree_key\""));
632-
assert!(sql.contains("\"tree\".\"depth\" < $2"));
633-
assert!(sql.contains("SELECT \"result_node\".*"));
634-
assert!(matches!(params.first(), Some(Value::String(Some(root))) if root == "root"));
635-
assert!(matches!(params.get(1), Some(Value::BigInt(Some(depth))) if *depth == 3));
636-
}
637-
638-
#[test]
639-
fn self_ref_tree_sql_uses_backend_specific_placeholders() {
640-
let (sql, params) = build_self_ref_tree_sql::<RelationTestNode>(
641-
"parent_slug",
642-
"slug",
643-
&serde_json::json!("root"),
644-
2,
645-
DatabaseType::MySQL,
646-
)
647-
.unwrap();
648-
649-
assert!(sql.starts_with("WITH RECURSIVE `tide_tree`"));
650-
assert!(sql.contains("`node`.`parent_slug` = ?"));
651-
assert!(sql.contains("`tree`.`depth` < ?"));
652-
assert!(matches!(params.first(), Some(Value::String(Some(root))) if root == "root"));
653-
assert!(matches!(params.get(1), Some(Value::BigInt(Some(depth))) if *depth == 2));
654-
}
655-
656-
#[test]
657-
fn self_ref_tree_sql_rejects_unknown_columns() {
658-
let err = build_self_ref_tree_sql::<RelationTestNode>(
659-
"parent_slug",
660-
"missing_column",
661-
&serde_json::json!("root"),
662-
2,
663-
DatabaseType::MySQL,
664-
)
665-
.unwrap_err();
666-
667-
assert!(
668-
err.to_string()
669-
.contains("Unknown self-reference column 'missing_column'")
670-
);
671-
}
672-
}
605+
#[path = "testing/relations_tests.rs"]
606+
mod tests;
673607

674608
// =============================================================================
675609
// RELATION TYPE WRAPPERS

0 commit comments

Comments
 (0)