0

I have a class that adopts both TableRecord and FetchableRecord. I am taging advantage of the automagic query generation provided by GRDB, and I only provide the table name because it doesn't match the class name. However now i want to add stored properties that are only used at run-time, and i don't want GRDB to try to fetch those autmatically. Can i exclude certain variables from the query?

Here is my code:

class Question: Identifiable, Codable, TableRecord, FetchableRecord {
    static var databaseTableName = "questions"
    
    var id: Int?
    var category: Int?
    var code: String?
    var ...
    var ...
    var ...
    var ...
    var selectedAnswer: Int? // This is only used at run-time and not present in the database.
}

I found this:

static var databaseSelection = [Column("id"), Column("category"), Column("code"), ...]

But that requires me to manually specify all the columns that i want it to fetch. Can i do the opposite, and just exclude one column (selectedAnswer)?

Basically what im looking for is something like this:

static var excludedVariables = ["selectedAnswer"]

I had a read through the documentation and couldn't find anything, but i don't know GRDB so i could have missed something.

Maciej Swic
  • 11,139
  • 8
  • 52
  • 68

2 Answers2

1

I think this is not a specific GRDB question, but more of a Codable question, which is "how to exclude one property from being decoded". So your solution could be to explicitly define the coding keys rather than let Swift synthesize them:

enum CodingKeys: String, CodingKey {
  case id, category, code, ...
}

It still requires you to define all of the columns/properties, but that is always the case with Codable when you want to exclude certain properties.

Rudedog
  • 4,323
  • 1
  • 23
  • 34
  • Thanks, but perhaps i wasn't specific enough in my question. I still need the Coding support, as this class gets loaded from SQLite but is later written to JSON and needs to retain the selectedAnswer in its JSON form. – Maciej Swic Jan 31 '22 at 18:09
  • 1
    Ah, ok. In that case, you would probably need to implement your own `init(from:)` method to override the synthesized one. – Rudedog Jan 31 '22 at 19:35
0

You do not need to do anything:

  • By default, GRDB selects all columns (SELECT * FROM ...). If there is no column for a non-persisted property, it will just not be fetched (and no invalid column will be inserted in the generated SQL).
  • Optional properties are decoded as nil from columns that do not exist in decoded rows.

For example:

import GRDB

var configuration = Configuration()
configuration.prepareDatabase { db in
    db.trace { print("SQL> \($0)") }
}
let dbQueue = DatabaseQueue(configuration: configuration)

struct Player: Identifiable, Codable, TableRecord, FetchableRecord {
    var id: Int64
    var name: String
    var score: Int
    var ignored: Int? // Has no matching column in the database
}

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()
    }

    try db.execute(sql: """
        INSERT INTO player (id, name, score) VALUES (NULL, 'Arthur', 100);
        INSERT INTO player (id, name, score) VALUES (NULL, 'Barbara', 1000);
        """)
    
    // SELECT * FROM player
    let players = try Player.fetchAll(db)

    // [Player(id: 1, name: "Arthur", score: 100, ignored: nil),
    //  Player(id: 2, name: "Barbara", score: 1000, ignored: nil)]
    print(players)
}
Gwendal Roué
  • 3,949
  • 15
  • 34
  • Then why does it start crashing as soon as I add a variable which doesn't exist as a column? I can check the exact message later but I think it was some SQL error about a nonexistent column. – Maciej Swic Feb 01 '22 at 19:58
  • The sample code provided above answers the question, and does not crash (you can paste the code and run it). If you have other questions, see https://stackoverflow.com/help/minimal-reproducible-example – Gwendal Roué Feb 01 '22 at 20:57