Skip to content

Commit

Permalink
feat: Auto-update timestamp columns (#287)
Browse files Browse the repository at this point in the history
Add utilities and migrations to enable auto-updating timestamp columns
of a row whenever the row is updated. Also, add the relevant migrations
to the `UserMigrator` to auto-enable the feature for the `user` table.

Closes #285
  • Loading branch information
spencewenski authored Jul 24, 2024
1 parent 999f362 commit 9e1ee56
Show file tree
Hide file tree
Showing 22 changed files with 691 additions and 31 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ and [Poem](https://github.com/poem-web/poem).
- Structured logs/traces using tokio's [tracing](https://docs.rs/tracing/latest/tracing/) crate. Export traces/metrics
using OpenTelemetry (requires the `otel` feature).
- Health checks to ensure the app's external dependencies are healthy
- Pre-built migrations for common DB tables, e.g. `user` (requires the `db-sql` feature)
- Support for auto-updating timestamp columns, e.g. `updated_at`, when updating DB rows (Postgres only currently) (
requires the `db-sql` feature)

# Getting started

Expand Down
1 change: 1 addition & 0 deletions src/migration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
pub mod check;
pub mod schema;
pub mod timestamp;
pub mod user;
24 changes: 17 additions & 7 deletions src/migration/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,39 @@ use sea_orm_migration::{prelude::*, schema::*};
/// Timestamp related fields.
#[derive(DeriveIden)]
#[non_exhaustive]
pub enum Timetamps {
pub enum Timestamps {
/// When the row was created. When used with the [timestamps] method, will default to
/// the current timestamp (with timezone).
CreatedAt,
/// When the row was updated. When used with the [timestamps] method, will be initially set to
/// the current timestamp (with timezone). Updates to the row will need to provide this field
/// as well in order for it to be updated.
// Todo: does seaorm automatically update this?
/// the current timestamp (with timezone).
///
/// To automatically update the value for a row whenever the row is updated, include the
/// [crate::migration::timestamp::m20240723_201404_add_update_timestamp_function::Migration]
/// in your [MigratorTrait] implementation, along with a [MigrationTrait] for your table
/// that add a trigger to update the column. Helper methods are provided for this in
/// the [crate::migration::timestamp] module. Specifically, see:
/// - [crate::migration::timestamp::exec_create_update_timestamp_trigger]
/// - [crate::migration::timestamp::exec_drop_update_timestamp_trigger]
///
/// Note that the auto-updates mentioned above are currently only supported on Postgres. If
/// an app is using a different DB, it will need to manually update the timestamp when updating
/// a row.
UpdatedAt,
}

/// Create a table if it does not exist yet and add some default columns
/// (e.g., create/update timestamps).
pub fn table<T: IntoIden + 'static>(name: T) -> TableCreateStatement {
pub fn table<T: IntoTableRef>(name: T) -> TableCreateStatement {
timestamps(Table::create().table(name).if_not_exists().to_owned())
}

/// Add "timestamp with time zone" columns (`CreatedAt` and `UpdatedAt`) to a table.
/// The default for each column is the current timestamp.
pub fn timestamps(mut table: TableCreateStatement) -> TableCreateStatement {
table
.col(timestamp_with_time_zone(Timetamps::CreatedAt).default(Expr::current_timestamp()))
.col(timestamp_with_time_zone(Timetamps::UpdatedAt).default(Expr::current_timestamp()))
.col(timestamp_with_time_zone(Timestamps::CreatedAt).default(Expr::current_timestamp()))
.col(timestamp_with_time_zone(Timestamps::UpdatedAt).default(Expr::current_timestamp()))
.to_owned()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//! Migration to create a SQL function to update the [Timestamps::UpdatedAt] column for a row
//! with the current timestamp.
//!
//! Note: Currently only supports Postgres. If another DB is used, will do nothing.
use crate::migration::schema::Timestamps;
use crate::migration::timestamp::{
exec_create_update_timestamp_function, exec_drop_update_timestamp_function,
};
use sea_orm_migration::prelude::*;

#[derive(DeriveMigrationName)]
pub struct Migration;

const COLUMN: Timestamps = Timestamps::UpdatedAt;

#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
exec_create_update_timestamp_function(manager, COLUMN).await
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
exec_drop_update_timestamp_function(manager, COLUMN).await
}
}
Loading

0 comments on commit 9e1ee56

Please sign in to comment.