Closed ahartman closed 1 year ago
Hello @ahartman,
Thank you for the sample code.
It contains one mistake that explains why books are not attached to their author:
try db.create(table: "author", ifNotExists: true) { t in
// 1️⃣ Author primary key is an integer
t.autoIncrementedPrimaryKey("id")
...
}
try db.create(table: "book", ifNotExists: true) { t in
...
// 2️⃣ The foreign key is text
t.column("authorID", .text)
.notNull()
.indexed()
.references("Author", onDelete: .cascade)
}
The mismatch between integers and text explains why books are not attached to their author: GRDB can't find the author with id "1"
(string) in a list of authors with ids 1
, 2
, etc (all integers).
This wasn't easy to spot 😅
The recommendation is to fix the database schema, and make sure the foreign key has the same type as its target primary key.
An easy way to do this is to create foreign keys with the belongsTo
method. It would have prevented the original mistake in the first place:
try db.create(table: "book", ifNotExists: true) { t in
...
+ t.belongsTo("author", onDelete: .cascade)
+ .notNull()
- t.column("authorID", .text)
- .notNull()
- .indexed()
- .references("Author", onDelete: .cascade)
}
Cher Gwendal,
Thank you for your quick response; that solved the issue. I took me 2 hours since Xcode kept looking at old code, even though I cleaned the Build folder repeatedly. Ultimately, I deleted all DerivedData and only then did I get the code with your fix working.
Thanks, ahartman, Belgium
Yes, I was pretty puzzled as well, and when Xcode doesn't do its job it does not help 😉
I'll think of better diagnostics that will help other users who happen to perform the same kind of column type mismatch.
Happy GRDB, ahartman!
I'll think of better diagnostics that will help other users who happen to perform the same kind of column type mismatch.
It is possible to detect that some prefetched books could not be assigned to any author, at the cost of an increased memory consumption, and a slight performance impact.
I'm not sure of how to provide a diagnostic, though.
Throwing an error or trapping with a precondition failure has a high risk of breaking existing applications. Sure, they have a sloppy database schema. But this is not a reason for shipping a GRDB release that breaks them even more 😄
I'll probably leave things as they are, unless someone has a good idea.
Dear Gwendal,
When trying to translate the Author/Books example to my own tables I took me another 2 hours to find out that in the AuthorInfo struct the name of the var for the parent Table can be anything but the name of the var for the child tabel MUST be the name the Book struct in lower case and plural.
That is not so much a naming convention as a naming obligation; you might explain a bit more in the documentation on the various Musts in naming vars.
Met vriendelijke groeten, Kind regards, Mes meilleures salutations, Freundliche Grüße, André Hartman Rodeweg 90 B-2920 Kalmthout België
Op 29 okt. 2023, om 18:46 heeft Gwendal Roué @.***> het volgende geschreven:
I'll think of better diagnostics that will help other users who happen to perform the same kind of column type mismatch.
It is possible to detect that some prefetched books could not be assigned to any author, at the cost of an increased memory consumption, and a slight performance impact.
I'm not sure of how to provide a diagnostic, though.
Throwing an error or trapping with a precondition failure has a high risk of breaking existing applications. Sure, they have a sloppy database schema. But this is not a reason for shipping a GRDB release that breaks them even more 😄
I'll probably leave things as they are, unless someone has a good idea.
— Reply to this email directly, view it on GitHub https://github.com/groue/GRDB.swift/issues/1450#issuecomment-1784180461, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAFIBPGUFGXVX7VPIV2WPKDYB2B6RAVCNFSM6AAAAAA6UX2ZECVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTOOBUGE4DANBWGE. You are receiving this because you were mentioned.
the AuthorInfo struct the name of the var for the parent Table can be anything but the name of the var for the child tabel MUST be the name the Book struct in lower case and plural.
The documentation for including(all:)
can surely be improved.
Yet, the naming convention works well in the English language. In English, how else would you name the author
and books
properties?
struct AuthorInfo: Decodable, FetchableRecord {
var author: Author // The base record
var books: [Book] // A collection of associated records
}
let authorInfos = try Author
.including(all: Author.books)
.asRequest(of: AuthorInfo.self)
.fetchAll(db)
It is correct that the name of author
can be freely chosen :-) Users are expected to provide author
in their first try - and succeed.
It is also correct that the name of books
can not be freely chosen. The developer must use the pluralized English name of the book table, or perform specific configuration. Again, the default is expected to work on the first try (in English).
Of course, I have seen the Dutch names in your database schema (patiëntNaam, datum, etc) 🇧🇪❤️
To be clear, if the database tables have Dutch names, this will make it more difficult to work with GRDB associations. You will have to provide a lot of custom keys to associations:
struct Schrijver: TableRecord {
static let boeken = hasMany(Boek.self).forKey("boeken")
// ~~~~~~~~~~~~~~~~~
}
struct SchrijverEnBoeken: Decodable, FetchableRecord {
var schrijver: Schrijver
var boeken: [Boek] // Property name matches the association key
}
let schrijversEnBoeken = try Schrijver
.including(all: Schrijver.boeken)
.asRequest(of: SchrijverEnBoeken.self)
.fetchAll(db)
You have another option, though: the pluralization rules can be modified, so that "boek" would be converted to "boeken" automatically, "schrijver" to "schrijvers", etc.
GRDB inflections are 100% based on the Ruby on Rails inflections, so maybe it is possible to find Dutch pluralization rules and translates them from Ruby to Swift.
The linked StackOverflow question mentions:
ActiveSupport::Inflector.inflections do |inflect|
inflect.plural 'boek', 'boeken'
end
The equivalent Swift code would be:
// Run this early in the lifetime of your app, before using associations.
// Here we fully replace the default English inflections.
Inflections.default = {
var inflections = Inflections()
inflections.plural("boek", "boeken")
return inflections
}()
You can test your customized inflections:
XCTAssertEqual(Inflections.default.pluralize("boek"), "boeken")
If you happen to know a little bit of regular expressions, and complete a full list of Dutch pluralization rules, other Dutch-speaking GRDB users may well be very happy :-)
Dear Gwendal,
Thank you for the hint om Dutch names in the databases and pluralization; I will at least rename the tables in English, that will solve most of that problem.
Met vriendelijke groeten, Kind regards, Mes meilleures salutations, Freundliche Grüße, André Hartman Rodeweg 90 B-2920 Kalmthout België
Op 29 okt. 2023, om 19:58 heeft Gwendal Roué @.***> het volgende geschreven:
the AuthorInfo struct the name of the var for the parent Table can be anything but the name of the var for the child tabel MUST be the name the Book struct in lower case and plural.
The documentation for including(all:) https://github.com/groue/GRDB.swift/blob/master/Documentation/AssociationsBasics.md#includingall can surely be improved.
Yet, the naming convention works well in the English language. In English, how else would you name the author and books properties?
struct AuthorInfo: Decodable, FetchableRecord { var author: Author // The base record var books: [Book] // A collection of associated records } let authorInfos = try Author .including(all: Author.books) .asRequest(of: AuthorInfo.self) .fetchAll(db) It is correct that the name of author can be freely chosen :-) Users are expected to provide author in their first try - and succeed.
It is also correct that the name of books can not be freely chosen. The developer must use the pluralized English name of the book table, or perform specific configuration. Again, the default is expected to work on the first try (in English).
Of course, I have seen the Dutch names in your database schema (patiëntNaam, datum, etc) 🙂
To be clear, if the database tables have Dutch names, this will make it more difficult to work with GRDB associations. You will have to provide a lot of custom keys to associations:
struct Schrijver: TableRecord { static let boeken = hasMany(Boek.self).forKey("boeken") //
~~~~~ }struct SchrijverEnBoeken: Decodable, FetchableRecord { var schrijver: Schrijver var boeken: [Boek] // Property name matches the association key } let schrijversEnBoeken = try Schrijver .including(all: Schrijver.boeken) .asRequest(of: SchrijverEnBoeken.self) .fetchAll(db) The pluralization rules https://github.com/groue/GRDB.swift/blob/master/GRDB/Utils/Inflections%2BEnglish.swift can be modified, so that "boek" would be converted to "boeken" automatically. They are based on the Ruby on Rails inflections, so maybe it is possible to find Dutch pluralization rules https://stackoverflow.com/questions/2998551/ruby-on-rails-pluralize-for-other-languages and translates them from Ruby to Swift.
— Reply to this email directly, view it on GitHub https://github.com/groue/GRDB.swift/issues/1450#issuecomment-1784197563, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAFIBPE5JRGUDYDI3FRJIGTYB2KNPAVCNFSM6AAAAAA6UX2ZECVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTOOBUGE4TONJWGM. You are receiving this because you were mentioned.
Dear Gwendal,
I am having some subtle issues, either with SAL or with GRDB. I am working on an app for a psychology practice; the parent-child data object is patient and their visits. When retrieving a data set I also want to use an aggregate, for the count of the number of visits per patient and I want to filter the lists for a data range.
Without filtering, the visitCount is correct With filtering for a data range, the visitCount is very wrong. The SQL looks OK for me.
Of curse, I could calculate the visitCount easily in Swift after fetching but I believe in calculating as early as possible, i.e. when fetching.
Can you tell me my error? I attach two screen images, one of visitCount and one of visitCount plus date range.
Thanks in advance, André Hartman
@ahartman, let's move to your dedicated (👍) issue #1454.
Gwendal,
Sorry, I made the error first of writing in the closed call, and then repeated my issue in a new call, indeed 1454.
Met vriendelijke groeten, Kind regards, Mes meilleures salutations, Freundliche Grüße, André Hartman Rodeweg 90 B-2920 Kalmthout België
Op 7 nov. 2023, om 09:30 heeft Gwendal Roué @.***> het volgende geschreven:
@ahartman https://github.com/ahartman, let's move to your dedicated issue #1454 https://github.com/groue/GRDB.swift/issues/1454.
— Reply to this email directly, view it on GitHub https://github.com/groue/GRDB.swift/issues/1450#issuecomment-1798030622, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAFIBPCFXNQ3AIWC3QCK6ALYDHWTNAVCNFSM6AAAAAA6UX2ZECVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTOOJYGAZTANRSGI. You are receiving this because you were mentioned.
What did you do?
I tried the example for including(all:) example from the documentation
What did you expect to happen?
I expected a list of authors with their books
What happened instead?
The list with authors had empty Book arrays for all autrors
Environment
GRDB flavor(s): master branch taken from github GRDB version: 6.20.2 Installation method: as package Xcode version: 14.2 Swift version: 5 Platform(s) running GRDB: macOS 12.7.2, Mac Catalyst macOS version running Xcode: macOS 12.7.2, Mac Catalyst
Demo Project
A demo project is not possible as my project reads events from my Calendar. I include one file DBModel.swift as TXT, the issues sits in the function getAuthorsBooks; that function returns authors but no books. I also have a function getBooksAuthors that does the reverse and fetches books with their associated author; that one works fine.
The db tracing shows a SQL statement, that is obviously wrong, as it does not pick up the primary key from the parent table.
SQL: COMMIT TRANSACTION SQL: PRAGMA query_only = 0 SQL: PRAGMA query_only = 1 SQL: BEGIN DEFERRED TRANSACTION DBModel.txt
SQL: SELECT FROM "author" SQL: SELECT , "authorID" AS "grdb_authorID" FROM "book" WHERE "authorID" IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
As far as I can see I copied everything correctly from the examples in the documentation. I an average in Swift and know my way in SQL; getting GRDB to urn is really dense form e, so I hope someone can point out the error that I am missing.
Thanks in advance, ahartman, Belgium DBModel.txt