Setting a default value for a date time column
ahartman opened this issue · comments
What did you do?
try db.create(table: "patient", ifNotExists: true) { t in
t.autoIncrementedPrimaryKey("id")
t.column("createdDate", .text)
.defaults(sql: "CURRENT_TIMESTAMP")
t.column("modifiedDate", .text)
.defaults(sql: "strftime('%Y-%m-%d %H:%M:%S:%s', 'now', 'localtime')")
t.column("patientName", .text)
.indexed()
.unique()
.notNull()
t.column("patientLatitude", .real)
.indexed()
t.column("patientLongitude", .real)
}
What did you expect to happen?
Setting default values for createdDate and modifiedDate
What happened instead?
No values were set
I used two methods, both do not work.
Environment
**GRDB flavor(s): GRDB
**GRDB version: master
**Installation method: package
**Xcode version: 14.2
**Swift version: 5
**Platform(s) running GRDB: macOS, Catalyst
**macOS version running Xcode: 12.7.1
Demo Project
How to set a date time default value?
Regards, André Hartman
Hello @ahartman,
I suppose you wrote something as below, and ended up with nil dates (both in the database and in the inserted model):
// INSERT INTO patient (..., createdDate, modifiedDate) VALUES (..., NULL, NULL)
var patient = Patient(..., createdDate: nil, modifiedDate: nil)
try patient.insert(db)
Let's jump to the main point: GRDB record types currently do not play well with default values defined in the SQL schema. Not only insert(db)
inserts explicit values for all columns, and explicit NULL dates prevent the default from being used. On top of that, the INSERT statement does not return the inserted values. Those are the two difficulties that prevent "magic" from happening.
There is a documentation article named Record Timestamps and Transaction Date that could help you making progress, though.
GRDB record types currently do not play well with default values.
There is a way to make them nicer citizens, which is to have record types avoid sending some values to the database, so that the default values defined in the schema can express themselves.
The values sent to the database are defined by the encode(to: inout PersistenceContainer)
method. So we need this method to avoid sending dates.
We also need to grab the inserted dates. This is done with insertAndFetch
.
Sample code
import GRDB
var configuration = Configuration()
configuration.prepareDatabase { db in
db.trace { print("SQL> \($0.expandedDescription)") }
}
let dbQueue = try DatabaseQueue(configuration: configuration)
struct Player: Codable, FetchableRecord, MutablePersistableRecord {
var id: Int64?
var name: String
var score: Int
var createdDate: Date?
var modifiedDate: Date?
mutating func didInsert(_ inserted: InsertionSuccess) {
id = inserted.rowID
}
func encode(to container: inout PersistenceContainer) throws {
container["id"] = id
container["name"] = name
container["score"] = score
// Don't send nil dates to the database, so that the default values
// defined in the schema can express themselves.
if createdDate != nil {
container["createdDate"] = createdDate
}
if modifiedDate != nil {
container["modifiedDate"] = modifiedDate
}
}
}
try dbQueue.write { db in
try db.create(table: "player") { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text).notNull()
t.column("score", .integer).notNull()
t.column("createdDate", .text)
.notNull()
.defaults(sql: "CURRENT_TIMESTAMP")
t.column("modifiedDate", .text)
.notNull()
.defaults(sql: "CURRENT_TIMESTAMP")
}
do {
// INSERT INTO "player" ("id", "name", "score")
// VALUES (NULL,'Arthur',100) RETURNING *
var player = Player(id: nil, name: "Arthur", score: 100)
let insertedPlayer = try player.insertAndFetch(db)!
print(player.createdDate) // nil
print(insertedPlayer.createdDate) // not nil
}
do {
// INSERT INTO "player" ("id", "name", "score", "createdDate")
// VALUES (NULL,'Arthur',100,'2023-12-21 14:33:33.360') RETURNING *
var player = Player(id: nil, name: "Arthur", score: 100, createdDate: Date())
let insertedPlayer = try player.insertAndFetch(db)!
print(player.createdDate) // not nil
print(insertedPlayer.createdDate) // not nil
}
}
There are nasty consequences, though. For example:
var player = ...
player.createdDate = nil
// 😬 No error, but column is NOT updated.
// We'd expect instead SQLite error 19: NOT NULL constraint failed
try player.update(db)
All in all, I'm not sure I'd recommend this technique 😅
I'm closing this issue. Please reopen if you have further questions.