Replies: 2 comments 22 replies
-
Hello @abournier, Thanks for having checked the library possibilities before opening an issue 😊 You are totally right, the So you're correct when you modify the The rest of the answer explores the topic, but you won't find any better option there. First, you have another option which is an SQL trigger, but I really not recommend using it: Trigger (not advised)You can instruct SQLite to touch the // Do it only once!
try db.execute(sql: """
CREATE TRIGGER UpdateDrinkTimestamp UPDATE ON drink BEGIN
UPDATE drink SET updatedAt = CURRENT_TIMESTAMP WHERE id=old.id;
END;
""") What's cool with a trigger is that you can't forget to update the timestamp 👍 Even batch updates are covered. What's NOT COOL with the trigger is that you can't prevent the timestamp from being updated (not today, and not in the future when you eventually discover that you need this). 😅 With a trigger, you'd need to call func saveDrink(_ drink: inout Drink) throws {
try dbWriter.write { db in
drink = drink.saveAndFetch(db)
}
} Also, you can make
You didn't tell why, so I will guess. We're talking about this one single extra line What I can tell is that I'm much aware of the feature. I've used ActiveRecord the Ruby ORM for a long time. It ships with built-in support for timestamps. So... the feature was not ported to GRDB. The main reason is that users of this feature eventually want to perform updates without touching the timestamps. That's how people are. They think they always want to update timestamps, and probably they do, until they discover they don't, because things have changed. So ActiveRecord supports explicit disabling of timestamp updates: # Explicit
drink.save(touch: false) Then other users discover that they want to disable timestamp updates at a distance. So ActiveRecord supports implicit disabling of timestamp updates: # Implicit (with a 😳global😱)
Drink.record_timestamps = false
drink.a_method_that_calls_save_eventually
Drink.record_timestamps = true Needless to say, a global is not quite concurrency-safe in a multi-threaded application. Oh, and I did not mention developers who need to inject an altered clock in, say, tests. It's a never-ending game between convenience and exceptions. Meh. All in all, I did not spend the time to design the feature correctly, with the necessary amount of flexibility. And it would really not frequently be used on mobile/desktop dev. I admit servers are another story, but GRDB targets apps. Maybe someone will come with a workable design one day? |
Beta Was this translation helpful? Give feedback.
-
Having record instances responsible for their timestamps considered harmfulSeveral answers in this discussion make individual record instances responsible for their own timestamps. This idea has merits, but is it acceptable or meaningful that within a single write, not all records have the same timestamp? This is what happens when each individual record accesses "now" in a naive way ( Consider for example this piece of SQLite documentation:
This means that the following SQL query assigns the same date to all players, even if the update statement takes a lot of time to execute: -- Identical timestamps for all players
UPDATE player SET score = score + 1, updateDate = datetime('now') PostgreSQL is even stricter:
Now, would we want the equivalent Swift code to generate distinct timestamps? // Think about what you expect from timestamps here:
for player in players {
try player.incrementScore(db)
} To me, the clear answer is that we should strive for identical timestamps. I won't assume I'm smarter than SQLite + PostgresQL designers. A quick Google search for a detailed description of ActiveRecord behavior on this topic did not yield significant answer - but I'm sure Rails was doing the wrong thing a few years ago : https://groups.google.com/g/rubyonrails-talk/c/TtqeqnP9JGI/m/OnZcfJxxQjYJ. Let's not make the same mistake: record instances must not be responsible for the value of their timestamps. Possible solution: database access localsSo, the GRDB user can make sure that several related statements or a whole transaction use the same timestamp. I'm not sure yet how this could be done. I'm considering taking inspiration from Swift Like enum DatabaseTimestamp {
@DatabaseAccessLocal static var currentDate: Date?
} One can access the above database access local with extension Player {
mutating func incrementStore(_ db: Database) {
try updateChanges(db) {
$0.score += 1
// It is a programmer error to forget to set the current date,
// so shamelessly crash if not set.
$0.modificationDate = DatabaseTimestamp.currentDate!
}
}
} One can set the database access local from within a database access: try dbQueue.write { db in
DatabaseTimestamp.$currentDate.withValue(Date()) {
for var player in players {
try player.incrementScore(db)
}
}
} Database access hookThe above implementation of I know that the whole concept of "programmer error", and the force unwrap operator, give the shivers to many developers 🤷♂️. So we should also make it possible to prevent such programmer error, which means make sure that all database accesses set enum DatabaseTimestamp {
// We can use a non-optional Date now, because
// we remove the opportunity for programmer errors below
@DatabaseAccessLocal var currentDate = Date()
}
extension Player {
mutating func incrementStore(_ db: Database) {
try updateChanges(db) {
$0.score += 1
$0.modificationDate = DatabaseTimestamp.currentDate // 😎
}
}
}
// Make sure all database accesses set the current date
var config = Configuration()
config.prepareDatabaseAccess { dbAccess in
DatabaseTimestamp.$currentDate.withValue(Date(), execute: dbAccess)
}
let dbQueue = try DatabaseQueue("/path/to/db", configuration: config)
// Run!
try dbQueue.write { db in
for var player in players {
try player.incrementScore(db)
}
} I wonder how this Those are raw thoughts on the topic! What do you think? |
Beta Was this translation helpful? Give feedback.
-
Hello,
Supposing I have a model like this:
GRDB has now callbacks, so I can populate 1 field such id with its UUID in willInsert():
I checked GRDB code and willInsert() and didInsert() are the uniq callback to be "mutating".
Now, if I want to auto-update the field
updatedAt
, I can't do this in callbacks as other callback methods are non mutating.updatedAt
should be updated after any insert/update.I know I can create a custom method to update it before the save, and call it "manually", for example in:
I don't like it at all.
Is-there a way to update this kind of field in
aroundSave()
orwillSave()
? If I used these methods with "mutating" keyword, the compiler warns about it. I can remove the warning with the keyword "private". I'm quite sure it's not a good idea too...Thanks for any suggestion.
Beta Was this translation helpful? Give feedback.
All reactions