mirror of
https://github.com/Phundrak/georm.git
synced 2026-07-29 04:19:18 +02:00
feat: Add generated and generated_always attributes
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
This commit is contained in:
@@ -181,9 +181,13 @@ let user_role = UserRole::find(pool, &id).await?;
|
||||
|
||||
**Note**: Relationships are not yet supported for entities with composite primary keys.
|
||||
|
||||
### Defaultable Fields
|
||||
### Defaultable and Generated Fields
|
||||
|
||||
For fields with database defaults or auto-generated values, use the `defaultable` attribute:
|
||||
Georm provides three attributes for handling fields with database-managed values:
|
||||
|
||||
#### `#[georm(defaultable)]` - Optional Override Fields
|
||||
|
||||
For fields with database defaults that can be manually overridden:
|
||||
|
||||
```rust
|
||||
#[derive(Georm)]
|
||||
@@ -200,22 +204,74 @@ pub struct Post {
|
||||
}
|
||||
```
|
||||
|
||||
This generates a `PostDefault` struct for easier creation:
|
||||
#### `#[georm(generated)]` - Generated by Default
|
||||
|
||||
For PostgreSQL `GENERATED BY DEFAULT` columns that can be overridden but are typically auto-generated:
|
||||
|
||||
```rust
|
||||
#[derive(Georm)]
|
||||
#[georm(table = "products")]
|
||||
pub struct Product {
|
||||
#[georm(id, generated_always)]
|
||||
pub id: i32, // GENERATED ALWAYS AS IDENTITY
|
||||
#[georm(generated)]
|
||||
pub sku_number: i32, // GENERATED BY DEFAULT AS IDENTITY
|
||||
pub name: String,
|
||||
pub price: sqlx::types::BigDecimal,
|
||||
}
|
||||
```
|
||||
|
||||
#### `#[georm(generated_always)]` - Always Generated
|
||||
|
||||
For PostgreSQL `GENERATED ALWAYS` columns that are strictly managed by the database:
|
||||
|
||||
```rust
|
||||
#[derive(Georm)]
|
||||
#[georm(table = "products")]
|
||||
pub struct Product {
|
||||
#[georm(id, generated_always)]
|
||||
pub id: i32, // GENERATED ALWAYS AS IDENTITY
|
||||
pub name: String,
|
||||
pub price: sqlx::types::BigDecimal,
|
||||
pub discount_percent: i32,
|
||||
#[georm(generated_always)]
|
||||
pub final_price: Option<sqlx::types::BigDecimal>, // GENERATED ALWAYS AS (expression) STORED
|
||||
}
|
||||
```
|
||||
|
||||
#### Generated Structs and Behavior
|
||||
|
||||
Both `defaultable` and `generated` fields create a companion `<Entity>Default` struct:
|
||||
|
||||
```rust
|
||||
use georm::Defaultable;
|
||||
|
||||
let post_default = PostDefault {
|
||||
id: None, // Let database auto-generate
|
||||
title: "My Post".to_string(),
|
||||
published: None, // Use database default
|
||||
created_at: None, // Use database default (NOW())
|
||||
author_id: 42,
|
||||
let product_default = ProductDefault {
|
||||
name: "Laptop".to_string(),
|
||||
price: BigDecimal::from(999),
|
||||
discount_percent: 10,
|
||||
sku_number: None, // Let database auto-generate
|
||||
// Note: generated_always fields are excluded from this struct
|
||||
};
|
||||
|
||||
let created_post = post_default.create(pool).await?;
|
||||
let created_product = product_default.create(pool).await?;
|
||||
```
|
||||
|
||||
#### Key Differences
|
||||
|
||||
| Attribute | INSERT Behavior | UPDATE Behavior | Use Case |
|
||||
|--------------------|------------------------------------|-----------------|----------------------------------------------|
|
||||
| `defaultable` | Optional (can override defaults) | Included | Fields with database defaults |
|
||||
| `generated` | Optional (can override generation) | Included | `GENERATED BY DEFAULT` columns |
|
||||
| `generated_always` | **Excluded** (always generated) | **Excluded** | `GENERATED ALWAYS` columns, computed columns |
|
||||
|
||||
#### Important Notes
|
||||
|
||||
- **`generated_always` fields are completely excluded** from INSERT and UPDATE statements to prevent database errors
|
||||
- **`generated` and `generated_always` cannot be used together** on the same field
|
||||
- **`generated` fields behave like `defaultable` fields** but are semantically distinct for future enhancements
|
||||
- **Option types cannot be marked as `defaultable`** to prevent `Option<Option<T>>` situations
|
||||
|
||||
### Relationships
|
||||
|
||||
Georm supports comprehensive relationship modeling with two approaches: field-level relationships for foreign keys and struct-level relationships for reverse lookups.
|
||||
@@ -512,6 +568,8 @@ post_default.create(pool).await?;
|
||||
```rust
|
||||
#[georm(id)] // Mark as primary key
|
||||
#[georm(defaultable)] // Mark as defaultable field
|
||||
#[georm(generated)] // Mark as generated by default field
|
||||
#[georm(generated_always)] // Mark as always generated field
|
||||
#[georm(relation = { /* ... */ })] // Define relationship
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user