The `create_or_update` method has been deprecated and replaced by
`upsert` for clarity and consistency with common database terminology.
This commit also removes the file `src/entity.rs` which has been
forgotten in earlier commits and was no longer part of Georm.
This commit abstracts the database operations to use the generic
`sqlx::Executor` trait instead of a concrete `&sqlx::PgPool`.
This change allows all generated methods (find, create, update,
delete, and relationships) to be executed within a
`sqlx::Transaction`, in addition to a connection pool. This is a
crucial feature for ensuring atomic operations and data consistency.
The public-facing traits `Georm` and `Defaultable` have been updated
to require `sqlx::Executor`, and the documentation has been updated to
reflect this new capability.
This commit introduces support for PostgreSQL generated columns by
adding two new field attributes to the `Georm` derive macro:
`#[georm(generated)]` and `#[georm(generated_always)]`.
The `#[georm(generated_always)]` attribute is for fields that are
always generated by the database, such as `GENERATED ALWAYS AS
IDENTITY` columns or columns with a `GENERATED ALWAYS AS (expression)
STORED` clause. These fields are now excluded from `INSERT` and
`UPDATE` statements, preventing accidental writes and ensuring data
integrity at compile time.
The `#[georm(generated)]` attribute is for fields that have a default
value generated by the database but can also be manually overridden,
such as `GENERATED BY DEFAULT AS IDENTITY` columns. These fields
behave similarly to `#[georm(defaultable)]` fields, allowing them to
be omitted from `INSERT` statements to use the database-generated
value.
For now, the behaviour is the same between `#[georm(generated)]` and
`#[georm(defaultable)]`, but the addition of the former now will be
useful for future features.
Key changes:
- Added `generated` and `generated_always` attributes to
`GeormFieldAttributes`.
- Introduced `GeneratedType` enum in the IR to represent the different
generation strategies.
- Modified the `create` and `update` query generation to exclude
fields marked with `#[georm(generated_always)]`.
- Integrated `#[georm(generated)]` fields with the existing
defaultable struct logic.
- Added validation to prevent conflicting attribute usage, namely
`#[georm(generated)]` and `#[georm(generated_always)]` on the same
field.
Implements #3
Move trait implementation code from single monolithic file into separate
modules organised by operation type (create, delete, find, update,
upsert) for better code organisation and maintainability.
Add support for entities with composite primary keys using multiple
#[georm(id)] fields. Automatically generates {EntityName}Id structs for
type-safe composite key handling.
Features:
- Multi-field primary key detection and ID struct generation
- Full CRUD operations (find, create, update, delete, create_or_update)
- Proper SQL generation with AND clauses for composite keys
- Updated documNtation in README and lib.rs
Note: Relationships not yet supported for composite key entities
Adds an example demonstrating user, comment, and follower relationship
including:
- User management with profiles
- Comments (not really useful, just for showcasing)
- Follower/follozing relationships
- Ineractive CLI interface with CRUD operations
- Database migrations for the example schema
Replace the existing two-query create_or_update implementation with a
single atomic PostgreSQL upsert using ON CONFLICT clause to eliminate
race conditions and improve performance.
Race condition fix:
The previous implementation had a critical race condition where
multiple concurrent requests could:
1. Both call find() and get None (record doesn't exist)
2. Both call create() and the second one fails with duplicate key
error
3. Or between find() and create(), another transaction inserts the
record
This created unreliable behavior in high-concurrency scenarios.
Changes:
- Add generate_upsert_query function in trait_implementation.rs
- Generate SQL with INSERT ... ON CONFLICT ... DO UPDATE SET pattern
- Remove default trait implementation that used separate
find/create/update calls
- Update derive_trait to include upsert query generation
- Convert create_or_update from default implementation to required
trait method
The new implementation eliminates race conditions while reducing
database round trips from 2-3 queries down to 1, significantly
improving both reliability and performance.
Introduces support for `#[georm(defaultable)]` attribute on entity
fields. When fields are marked as defaultable, generates companion
`<Entity>Default` structs where defaultable fields become `Option<T>`,
enabling easier entity creation when some fields have database defaults
or are auto-generated.
Key features:
- Generates `<Entity>Default` structs with optional defaultable fields
- Implements `Defaultable<Id, Entity>` trait with async `create` method
- Validates that `Option<T>` fields cannot be marked as defaultable
- Preserves field visibility in generated companion structs
- Only generates companion struct when defaultable fields are present
Currently, all methods declared in the Georm trait are available.
If a struct has an ID pointing towards another entity, the user can
create a get method to get the entity pointed at from the database
too (local one-to-one relationship).
I still need to implement remote one-to-one relationships (one-to-one
relationships when the ID of the remote object is not available
locally).
I still need to also test and debug one-to-many relationships (ID of
the remote entiies not available locally) and many-to-many
relationships (declared in a dedicated table).
For now, IDs in all cases are simple types recognized by SQLx that are
not arrays. Options are only supported when explicitely specified for
one-to-one relationships.