Toasty v0.9.0
#[document] fields. Store an embedded struct in one document column without giving up typed queries into its fields. PostgreSQL uses JSONB, MySQL uses JSON, SQLite and Turso use JSON text, and DynamoDB uses a map. The same generated path works across backends:
#[derive(Model)]
struct User {
#[document]
address: Address,
// ... other fields
}
let users = User::filter(
User::fields().address().city().eq("Seattle"),
)
.exec(&mut db)
.await?;
Atomic upserts. #[derive(Model)] now generates upsert_by_* builders for primary keys and unique constraints. An upsert can share assignments across both branches, set create- and update-specific values, apply mutations such as increment(), or use or_ignore():
let user = User::upsert_by_email("alice@example.com")
.on_create(|user| user.name("Alice").login_count(0))
.on_update(|user| {
user.login_count(toasty::stmt::increment())
})
.exec(&mut db)
.await?;
PostgreSQL, SQLite, and Turso support the complete API. DynamoDB supports primary-key forms; MySQL rejects upserts because its conflict behavior cannot preserve Toasty's targeted semantics. Read about upserts.
Dynamic and native JSON fields. Models can use serde_json::Value directly for payloads whose structure is not known to Toasty. Both serde_json::Value and toasty::Json<T> can select text, PostgreSQL JSONB, or native JSON storage:
#[column(type = jsonb)]
payload: serde_json::Value,
Use #[document] when Toasty should generate typed paths into a fixed structure; use JSON fields for opaque or dynamic values that are read and replaced as a whole. Read about JSON encoding.
Filtered and ordered includes. Association paths passed to .include() now accept .filter() and .order_by(), so applications can preload exactly the related records they need in the required order:
let users = User::all()
.include(
User::fields()
.todos()
.filter(Todo::fields().complete().eq(false))
.order_by(Todo::fields().priority().desc()),
)
.exec(&mut db)
.await?;
Turso Sync. The Turso driver can connect a local database to Turso Cloud or a local sync server. Applications configure authentication and bootstrap behavior on the driver, then explicitly call push() and pull() when they want to exchange changes. Read about Turso Sync.
Embedded enum improvements. Enum variants can declare explicitly shared fields with #[shared(name)] and define enum-level indexes or unique constraints over those columns. Embedded enums also support rename_all, explicit integer discriminants, and scalar storage for unit enums.
- support order_by in includes (#1109)
- relation link/unlink return a builder instead of executing eagerly (#1118)
- support serde_json::Value fields (#1116)
- support native JSON and JSONB column storage (#1114)
- [breaking] require explicit column types for JSON fields (#1106)
- support temporal Vec fields (#1105)
- filter associations in include (#1089)
- introduce Expr::Static for inline SQL literals, hook up LIMIT/OFFSET (#1001)
- support integer storage for enum discriminants (#1101)
- add upsert support (#1091)
- add #[shared] variant fields and enum-level #[index]/#[unique] (#1078)
- implement Scalar for unit enum embeds (#1082)
- add #[document] storage for embedded types with nested-path filtering (#1028)
- type indexed key discovery as primary keys on DynamoDB (#1113)
- serialize unconstrained numeric migration snapshots (#1115)
- roll back transactions when finalization fails (#1102)
- support any() on many-to-many relations (#1097)
- store Vec as a native enum array on Postgres (#1092)
- compare composite-FK include filter against target fields (#1086)
- return None for optional belongs_to with NULL foreign key (#1090)
- lower IN-list over an embedded-field projection (#1084)
Toasty v0.8.0
#[version] works on SQL. Optimistic concurrency was DynamoDB-only. It now runs on SQLite, PostgreSQL, MySQL, and Turso: creating a record initializes the field to 1, every update and delete increments it, and writing through a stale snapshot fails the condition check instead of clobbering the newer row.
#[version]
version: u64,
#[belongs_to] infers its keys. The key and references arguments are derived from the field name and the target's primary key, so the common case is a bare attribute:
#[belongs_to]
user: toasty::Deferred<User>,
Optional embeds and shared enum columns. An embedded struct can be Option, and enum variants can store a common field in one column by naming it:
metadata: Option<Metadata>,
enum Creature {
Human { #[column("name")] name: String, profession: String },
Animal { #[column("name")] name: String, species: String },
}
Both variants write creature_name; profession and species keep their own columns.
Composite unique indices. A model attribute declares uniqueness over several fields at once. SQL backends lower it to a unique index; DynamoDB rejects it with unsupported_feature.
#[unique(org_id, slug)]
struct Account { /* ... */ }
Query DSL additions. between(lo, hi) (inclusive, and usable as a DynamoDB sort-key condition), like_with_escape(pattern, escape_char) for patterns containing literal % or _, and indexes on unit enum fields.
Tracing. Each statement the driver executes emits one toasty::query event carrying duration_ms, rows, error, and the OpenTelemetry db.* fields, under the caller's span. Events past the slow-statement threshold are logged at WARN.
- Emit one
toasty::queryevent per statement and propagate caller spans (#1071) - Support
#[version]optimistic concurrency on SQL drivers (#1065) - Infer
keyandreferencesin#[belongs_to](#1063) - Share columns across enum variants via
#[column("name")](#1064) - Escape support for
LIKEexpressions (#1039) set_*replace-variants on the query builder (#1037)- Serde serialization and deserialization for
toasty::Json<T>(#1035) - Index unit enum fields (#1027)
betweenoperator in the query DSL (#1029)- Composite unique indices (#1018)
Option<EmbeddedType>model fields (#1021)- Scalar terminal fields in
has_manyrelations (#1012)
- Avoid a panic when updating a mixed enum to a unit variant (#1069)
- Fix enum decoding for OR'd variant filters (#1067)
- Make multi-key delete and update consistent (#1053)
- Truncate auto-generated index names that exceed the backend limit (#1023)
- Increment the
#[version]field on query-based updates (#1022)
- [breaking] Derive default table names at compile time (#1070)
- [breaking] Make
UpdateByKeyreturning columns explicit (#1024) - [breaking] Rename the
RelationManyField/RelationOneFieldassociated type toTarget(#1015) - [breaking] Align
stmt::Querywith the per-modelQuery(#1011) - [breaking] Unify per-model query structs into
Query<T>(#995) - [breaking] Remove the
Registertrait (#1006) - Remove compile-time field validation from the
create!macro (#997)
- Encode bool values to match the DynamoDB key attribute type (#945)
- [breaking]
Model::table_nameis now aStringrather than anOption<String>(#1070)
- Serialize
#[version]condition checks for optimistic concurrency (#1065) - Serialize
LIKE ... ESCAPE(#1039) - Serialize
BETWEEN(#1029)
- Propagate errors from
exec_sqlinstead of panicking (#1007)
- Make multi-key delete and update consistent (#1053)
- Encode bool values as
N("1"/"0")to match the key attribute type (#945)
- Include the path in Toasty config load errors (#1036)
toasty-macros, toasty-driver-postgresql, toasty-driver-mysql, toasty-driver-turso, and the integration-suite crates received the shared changes listed above (query events, #[version] on SQL, #[belongs_to] inference, unified Query<T>, compile-time table names) with no crate-specific entries.
Toasty v0.7.0
Relations are now plain fields, with eager loading by default. The HasMany, HasOne, and BelongsTo wrapper types are gone. A relation field is just its target type, and whether you wrap it in Deferred controls when it loads: a bare field loads eagerly with its parent, a Deferred<...> field loads on demand (via .include() or an accessor).
#[has_many]
posts: Vec<Post>, // eager — loaded with the User
#[has_one]
profile: toasty::Deferred<Option<Profile>>, // on demand
Multi-step via relations. Declare relations that traverse an intermediate relation, and load them with .include():
#[has_many(via = posts.comments)]
comments: toasty::Deferred<Vec<Comment>>,
update! macro. A new macro for updates, mirroring create!. Set multiple fields in one call, with field shorthand, embedded-struct patches, and has-many inserts:
toasty::update!(user {
name: "Carlos",
email: "carlos@example.com",
})
.exec(&mut db)
.await?;
Arithmetic updates. Numeric fields get increment, decrement, add, and subtract, compiled to in-place column updates and usable as method shorthand inside update!:
toasty::update!(comment { views.increment() }).exec(&mut db).await?;
Turso driver. libSQL now joins SQLite, PostgreSQL, MySQL, and DynamoDB. On SQLite and Turso, TransactionMode lets you pick the lock-acquisition strategy for concurrent writers.
Raw SQL. An escape hatch for queries the builder doesn't cover, with typed results.
New field wrappers. Deferred<T> (replaces #[deferred]) and toasty::Json<T> (replaces #[serialize(json)]) are ordinary types — they compose with Option, embeds, and derives.
- Derive
Cloneand addDeserializeforDeferred<T>(#994) - [breaking] Increment, decrement, add, and subtract update operators (#979)
update!macro for concise field updates (#980)- Reject
create()on multi-step relation scopes at compile time (#978) - Raw SQL execution API (#965)
- Remove the
#[deferred]attribute in favor ofDeferred<T>(#961) - Eager relation fields (#958)
.include()of multi-stepviarelations (#946)- Expose migration core from
toasty(#944) - Turso driver with TransactionMode-aware concurrent writes (#938)
- Dispatch has-many
stmt::applybatches per entry (#932) TransactionModefor SQLite lock-acquisition control (#931)- Allow
#[version]on tuple-newtype embeds ofu64(#930) - Serialize
SELECT DISTINCT(#934) - [breaking] Replace
#[serialize(json)]with thetoasty::Json<T>wrapper (#926) - Expose the primary-key type via
Model::PrimaryKey(#921) - Fold simple
Batchassignments in update lowering (#917) - Multi-step (
via)has_manyandhas_onerelations (#890) - Non-panicking
try_geton relation types (#918)
- Deserialize a present
Deferred<T>value as loaded (#999) - Lift relation-path
LIKEinto a foreign-key subquery (#992) - Lift relation-path
IN-subquery throughBelongsTochains (#990) - Make
starts_withcase-sensitive on SQLite and MySQL (#983) - Handle
ExprOrinevalverify_expr(#959) - [breaking] Scope
.ilike()to PostgreSQL and document operator pass-through (#937)
- [breaking] Merge one-relation field traits (#971)
- Simplify
Fieldbounds now thatLoad<Output = Self>is required (#976) - Move
UpdateTarget::Queryrewrite into lower (#975) - [breaking] Delete the
Relationtrait, tighten relation field shapes (#967) - Split
viarelations into a field variant (#966) - [breaking] Merge has-relation field variants (#964)
- [breaking] Require
Deferredrelation fields (#954) - Gate the SQLite connect doctest (#953)
- Split relation field traits from targets (#950)
- Unify lazy-slot relation encoding (#949)
- [breaking] Move schema diff types to
schema::diff(#929) - Consolidate migration types and reorganize the
db::diffAPI (#928)
- INNER join variant (#922)
- Cap SQLite auto-increment integer storage at 4 bytes (#969)
- [breaking] Remove singular has-many create-builder methods (#977)
- Respect the
pairattribute in the#[has_one]macro (#927)
- Rename types in
toasty_core::schema::diff(#942)
- Adopt
tokio-postgres-rustls0.14 (#952)
- Accept libpq query-param connection options (#985)
- Omit empty
ExpressionAttributeValuesonIS NULL/IS NOT NULLscans (#940)
toasty-driver-mysql, toasty-driver-sqlite, toasty-driver-turso, toasty-cli, and the integration-suite crates received the shared changes listed above (raw SQL API, Turso driver, required Deferred relation fields, schema-diff move) with no crate-specific entries.
Toasty v0.6.0
- Vec model fields with push, extend, clear, pop, remove_at, and remove operations on PostgreSQL, MySQL, SQLite, and DynamoDB (#866, #872, #880, #887)
- Connection pooling improvements: lifetime capping, idle time limits, and automatic broken connection detection and eviction (#879, #882, #874, #867)
- Query capabilities: .select() projection through BelongsTo relations, per-call column projection, ilike() filter method, filtering by associated model fields, all-condition filters for associations, and latest_by queries (#827, #820, #801, #781, #784, #707)
- Compile-time validation for column storage types and create! macro field sets (#832, #648)
- Auto proxying through tuple-newtype Embed types (#836)
- #[deferred] field attribute and Deferred wrapper with embedded type support (#793, #799)
- Backward pagination driver capability (#757)
- Full-table scan support on DynamoDB (#821)
- IN-list parameter optimization for PostgreSQL (#818)