Skip to content

Commit 1db7c51

Browse files
committed
Merge pull request diesel-rs#178 from robertmaloney/rm/timestamp-helpers
Add helpers for managing `updated_at TIMESTAMP` columns
2 parents e94a3cb + 720f057 commit 1db7c51

9 files changed

Lines changed: 98 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@ All user visible changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/), as described
44
for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md)
55

6+
## Unreleased
7+
8+
### Added
9+
10+
* Added helper function `diesel_manage_updated_at('TABLE_NAME')` to postgres
11+
upon database setup. This function sets up a trigger on the specified table
12+
that automatically updates the `updated_at` column to the `current_timestamp`
13+
for each affected row in `UPDATE` statements.
14+
615
## [0.5.1] 2016-02-11
716

817
* Diesel CLI no longer has a hard dependency on SQLite and PostgreSQL. It

diesel/src/connection/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,6 @@ pub trait Connection: SimpleConnection + Sized {
9393
#[doc(hidden)] fn rollback_transaction(&self) -> QueryResult<()>;
9494
#[doc(hidden)] fn commit_transaction(&self) -> QueryResult<()>;
9595
#[doc(hidden)] fn get_transaction_depth(&self) -> i32;
96+
97+
#[doc(hidden)] fn setup_helper_functions(&self);
9698
}

diesel/src/migrations/mod.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ pub fn run_pending_migrations_in_directory<Conn>(conn: &Conn, migrations_dir: &P
103103
-> Result<(), RunMigrationsError> where
104104
Conn: MigrationConnection,
105105
{
106-
try!(create_schema_migrations_table_if_needed(conn));
106+
try!(setup_database(conn));
107107
let already_run = try!(conn.previously_run_migration_versions());
108108
let all_migrations = try!(migrations_in_directory(migrations_dir));
109109
let pending_migrations = all_migrations.into_iter().filter(|m| {
@@ -120,7 +120,7 @@ pub fn run_pending_migrations_in_directory<Conn>(conn: &Conn, migrations_dir: &P
120120
pub fn revert_latest_migration<Conn>(conn: &Conn) -> Result<String, RunMigrationsError> where
121121
Conn: MigrationConnection,
122122
{
123-
try!(create_schema_migrations_table_if_needed(conn));
123+
try!(setup_database(conn));
124124
let latest_migration_version = try!(conn.latest_run_migration_version());
125125
revert_migration_with_version(conn, &latest_migration_version, &mut stdout())
126126
.map(|_| latest_migration_version)
@@ -158,7 +158,12 @@ fn migration_with_version(ver: &str) -> Result<Box<Migration>, MigrationError> {
158158
}
159159

160160
#[doc(hidden)]
161-
pub fn create_schema_migrations_table_if_needed<Conn: Connection>(conn: &Conn) -> QueryResult<usize> {
161+
pub fn setup_database<Conn: Connection>(conn: &Conn) -> QueryResult<usize> {
162+
conn.setup_helper_functions();
163+
create_schema_migrations_table_if_needed(conn)
164+
}
165+
166+
fn create_schema_migrations_table_if_needed<Conn: Connection>(conn: &Conn) -> QueryResult<usize> {
162167
conn.silence_notices(|| {
163168
conn.execute("CREATE TABLE IF NOT EXISTS __diesel_schema_migrations (
164169
version VARCHAR PRIMARY KEY NOT NULL,

diesel/src/pg/connection/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ impl Connection for PgConnection {
114114
fn get_transaction_depth(&self) -> i32 {
115115
self.transaction_depth.get()
116116
}
117+
118+
fn setup_helper_functions(&self) {
119+
self.batch_execute(
120+
include_str!("setup/timestamp_helpers.sql")
121+
).expect("Error creating timestamp helper functions for Pg");
122+
}
117123
}
118124

119125
impl PgConnection {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
2+
BEGIN
3+
IF (
4+
NEW IS DISTINCT FROM OLD AND
5+
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
6+
) THEN
7+
NEW.updated_at := current_timestamp;
8+
END IF;
9+
RETURN NEW;
10+
END;
11+
$$ LANGUAGE plpgsql;
12+
13+
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
14+
BEGIN
15+
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
16+
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
17+
END;
18+
$$ LANGUAGE plpgsql;

diesel/src/sqlite/connection/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ impl Connection for SqliteConnection {
107107
fn get_transaction_depth(&self) -> i32 {
108108
self.transaction_depth.get()
109109
}
110+
111+
fn setup_helper_functions(&self) {
112+
// this will be implemented at least when timestamps are supported in SQLite
113+
}
110114
}
111115

112116
impl SqliteConnection {

diesel_cli/src/database.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ fn create_schema_table_and_run_migrations_if_needed(database_url: &String)
128128
-> DatabaseResult<()>
129129
{
130130
if !schema_table_exists(database_url).map_err(handle_error).unwrap() {
131-
try!(call_with_conn!(database_url, migrations::create_schema_migrations_table_if_needed));
131+
try!(call_with_conn!(database_url, migrations::setup_database));
132132
call_with_conn!(database_url, migrations::run_pending_migrations).unwrap_or_else(handle_error);
133133
};
134134
Ok(())

diesel_tests/tests/connection.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use schema::connection_without_transaction;
2+
use diesel::*;
3+
use diesel::expression::dsl::sql;
4+
5+
table! {
6+
auto_time {
7+
id -> Integer,
8+
n -> Integer,
9+
updated_at -> Timestamp,
10+
}
11+
}
12+
13+
#[test]
14+
#[cfg(feature = "postgres")]
15+
fn managing_updated_at_for_table() {
16+
use self::auto_time::columns::*;
17+
use self::auto_time::table as auto_time;
18+
use diesel::pg::types::date_and_time::PgTimestamp;
19+
20+
// transactions have frozen time, so we can't use them
21+
let connection = connection_without_transaction();
22+
connection.execute("CREATE TABLE auto_time (
23+
id SERIAL PRIMARY KEY,
24+
n INTEGER,
25+
updated_at TIMESTAMP
26+
);").unwrap();
27+
connection.execute("SELECT diesel_manage_updated_at('auto_time');").unwrap();
28+
29+
connection.execute("INSERT INTO auto_time (n) VALUES (2), (1), (5);").unwrap();
30+
let result = connection.query_one::<_, i64>(
31+
select(sql("COUNT(*) FROM auto_time WHERE updated_at IS NULL"))
32+
);
33+
assert_eq!(Ok(3), result);
34+
35+
connection.execute("UPDATE auto_time SET n = n + 1 WHERE true;").unwrap();
36+
let result = connection.query_one::<_, i64>(
37+
select(sql("COUNT(*) FROM auto_time WHERE updated_at IS NULL"))
38+
);
39+
assert_eq!(Ok(0), result);
40+
41+
let query = auto_time.find(2).select(updated_at);
42+
let old_time: PgTimestamp = query.first(&connection).unwrap();
43+
update(auto_time.find(2)).set(n.eq(0)).execute(&connection).unwrap();
44+
let new_time: PgTimestamp = query.first(&connection).unwrap();
45+
assert!(old_time < new_time);
46+
47+
// clean up because we aren't in a transaction
48+
connection.execute("DROP TABLE auto_time;").unwrap();
49+
}

diesel_tests/tests/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ include!(concat!(env!("OUT_DIR"), "/lib.rs"));
1212

1313
mod associations;
1414
mod expressions;
15+
mod connection;
1516
mod filter;
1617
mod filter_operators;
1718
mod find;

0 commit comments

Comments
 (0)