QSqlRecord forces null in composite primary key on QSqlTableModel::insertRecord
-
@JonB said in QSqlRecord forces null in composite primary key on QSqlTableModel::insertRecord:
I am surprised at this. If you have a plain QSqlTableModel and you remove the CONSTRAINT "FK_Food_ID" FOREIGN KEY in the definition could I then see exactly what the error message reads instead of the old
I honestly wish I got a different error message. That would at least indicate progress.
Version 1 (With Null constraints)CREATE TABLE "FoodDay" ( "food" INTEGER NOT NULL, "date" INTEGER NOT NULL, "amount" REAL NOT NULL, "caloriesCache" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY("food","date") )
Debug output:
QSqlRecord(4) 0: QSqlField("food", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "3" 1: QSqlField("date", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "2460949" 2: QSqlField("amount", double, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "150" 3: QSqlField("caloriesCache", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "4" QSqlRecord(4) 0: QSqlField("food", int, tableName: "FoodDay", generated: no, autoValue: false, readOnly: false) "0" 1: QSqlField("date", int, tableName: "FoodDay", generated: no, autoValue: false, readOnly: false) "0" 2: QSqlField("amount", double, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "150" 3: QSqlField("caloriesCache", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "4" Failed to flush to the database: NOT NULL constraint failed: FoodDay.food Unable to fetch row
Version 2: No Null constraints:
CREATE TABLE "FoodDay" ( "food" INTEGER, "date" INTEGER, "amount" REAL NOT NULL, "caloriesCache" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY("food","date") )
Debug output:
QSqlRecord(4) 0: QSqlField("food", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "3" 1: QSqlField("date", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "2460949" 2: QSqlField("amount", double, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "150" 3: QSqlField("caloriesCache", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "4" QSqlRecord(4) 0: QSqlField("food", int, tableName: "FoodDay", generated: no, autoValue: false, readOnly: false) "0" 1: QSqlField("date", int, tableName: "FoodDay", generated: no, autoValue: false, readOnly: false) "0" 2: QSqlField("amount", double, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "150" 3: QSqlField("caloriesCache", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "4"
No error, and data in the database
I did make sure to close my SQLite DB Browser before every interaction with my program in case the handle messed things up
Version 3: Only
food
column as the primary key with NULL allowed:CREATE TABLE "FoodDay" ( "food" INTEGER, "date" INTEGER, "amount" REAL NOT NULL, "caloriesCache" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY("food") )
QSqlRecord(4) 0: QSqlField("food", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "3" 1: QSqlField("date", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "2460949" 2: QSqlField("amount", double, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "150" 3: QSqlField("caloriesCache", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "4" QSqlRecord(4) 0: QSqlField("food", int, tableName: "FoodDay", generated: no, autoValue: false, readOnly: false) "0" 1: QSqlField("date", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "2460949" 2: QSqlField("amount", double, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "150" 3: QSqlField("caloriesCache", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "4"
DB Inserted record:
Version 4: With only
food
as PK, NOT NULL constraintsCREATE TABLE "FoodDay" ( "food" INTEGER NOT NULL, "date" INTEGER NOT NULL, "amount" REAL NOT NULL, "caloriesCache" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY("food") )
QSqlRecord(4) 0: QSqlField("food", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "3" 1: QSqlField("date", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "2460949" 2: QSqlField("amount", double, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "150" 3: QSqlField("caloriesCache", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "4" QSqlRecord(4) 0: QSqlField("food", int, tableName: "FoodDay", generated: no, autoValue: false, readOnly: false) "0" 1: QSqlField("date", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "2460949" 2: QSqlField("amount", double, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "150" 3: QSqlField("caloriesCache", int, tableName: "FoodDay", generated: yes, autoValue: false, readOnly: false) "4"
DB record inserted:
I can only conclude from this that Qt, if it finds an INT PK, that it automatically increments it, even though on the DB side it's not AUTOINCREMENT.
I want to be wrong on this@Stroopwafe1 said in QSqlRecord forces null in composite primary key on QSqlTableModel::insertRecord:
I can only conclude from this that Qt, if it finds an INT PK, that it automatically increments it, even though on the DB side it's not AUTOINCREMENT.
I want to be wrong on thisI really don't think so. And if it did I believe you would see
autoValue: true
for that column. (Possibly evenreadOnly: true
as well for this case, don't know.) If you want you could compare against what you see if you actually make the db columnAUTOINCREMENT
.If I were to have a play with this I would really want (a) a complete program, nothing I need to add to myself plus (b) simplified a lot, you have quite a bit of stuff I would have to write at present. Anything more than 100 lines (and preferably less) is too much. When I submit a problem I pride myself on reducing it so that every remaining line is essential to the issue! Up to you.
-
@Stroopwafe1 said in QSqlRecord forces null in composite primary key on QSqlTableModel::insertRecord:
I can only conclude from this that Qt, if it finds an INT PK, that it automatically increments it, even though on the DB side it's not AUTOINCREMENT.
I want to be wrong on thisI really don't think so. And if it did I believe you would see
autoValue: true
for that column. (Possibly evenreadOnly: true
as well for this case, don't know.) If you want you could compare against what you see if you actually make the db columnAUTOINCREMENT
.If I were to have a play with this I would really want (a) a complete program, nothing I need to add to myself plus (b) simplified a lot, you have quite a bit of stuff I would have to write at present. Anything more than 100 lines (and preferably less) is too much. When I submit a problem I pride myself on reducing it so that every remaining line is essential to the issue! Up to you.
I definitely can work on a minimum compilable example
@JonB said in QSqlRecord forces null in composite primary key on QSqlTableModel::insertRecord:
Anything more than 100 lines (and preferably less) is too much. When I submit a problem I pride myself on reducing it so that every remaining line is essential to the issue! Up to you.
I will do my best to keep it as small, yet still relevant as possible. I will leave out all the other tables as well since those are not part of the problem (only debugging whether composite int PK breaks things)
However, I'm not well-versed in how to share these mini projects on forums. Do you want it as code blocks in a reply? Or as a git repo? Or does QT Creator have some fancy feature to share a project?
-
For my part, I would usually only look at/copy 100 lines which are pasted here (line at top and at bottom must read
```
(3-backticks) to make it code block). However having got this far I would accept a simple github repo from you if it's easy, whatever gives me less work to do to get it compiling and running. What I would ask is for you to try to remove absolutely anything/every line which is not required to illustrate the issue. I do not care about your real-world requirements, I care only about whatever is minimal to show a problem. Please do include sending theCREATE TABLE
string to the db as I am not familiar with SQLite and don't have any tool which might be required to create a database/table outside of your code.Looking back at your examples, I think versions 3 or 4 are the most useful. (Actually looks like 4 is the best, 3 allows a PK to be NULL.) I agree I see just the
food
column alone seeming to change from3
to0
which looks odd.P.S. IMPORTANT
What is this I see about SQLite when I Googledoes sqlite make first column be autoincrement
:SQLite
https://www.sqlite.org › faq
26 Nov 2024 — Short answer: A column declared INTEGER PRIMARY KEY will autoincrement. Longer answer: If you declare a column of a table to be INTEGER ...
?!
I think you need to read https://www.sqlite.org/lang_createtable.html#rowid. You may need to look into
Except for WITHOUT ROWID tables, all rows within SQLite tables have a 64-bit signed integer key that uniquely identifies the row within its table. This integer is usually called the "rowid". The rowid value can be accessed using one of the special case-independent names "rowid", "oid", or "rowid" in place of a column name. If a table contains a user defined column named "rowid", "oid" or "rowid", then that name always refers the explicitly declared column and cannot be used to retrieve the integer rowid value.
The rowid (and "oid" and "rowid") is omitted in WITHOUT ROWID tables. WITHOUT ROWID tables are only available in SQLite version 3.8.2 (2013-12-06) and later. A table that lacks the WITHOUT ROWID clause is called a "rowid table".
If I read right, SQLite makes assumptions about you using an
INT
as aPK
which you do not want in your case....I think it's saying you need to add
WITHOUT ROWID
if you want to manage the PK INT value yourself? -
For my part, I would usually only look at/copy 100 lines which are pasted here (line at top and at bottom must read
```
(3-backticks) to make it code block). However having got this far I would accept a simple github repo from you if it's easy, whatever gives me less work to do to get it compiling and running. What I would ask is for you to try to remove absolutely anything/every line which is not required to illustrate the issue. I do not care about your real-world requirements, I care only about whatever is minimal to show a problem. Please do include sending theCREATE TABLE
string to the db as I am not familiar with SQLite and don't have any tool which might be required to create a database/table outside of your code.Looking back at your examples, I think versions 3 or 4 are the most useful. (Actually looks like 4 is the best, 3 allows a PK to be NULL.) I agree I see just the
food
column alone seeming to change from3
to0
which looks odd.P.S. IMPORTANT
What is this I see about SQLite when I Googledoes sqlite make first column be autoincrement
:SQLite
https://www.sqlite.org › faq
26 Nov 2024 — Short answer: A column declared INTEGER PRIMARY KEY will autoincrement. Longer answer: If you declare a column of a table to be INTEGER ...
?!
I think you need to read https://www.sqlite.org/lang_createtable.html#rowid. You may need to look into
Except for WITHOUT ROWID tables, all rows within SQLite tables have a 64-bit signed integer key that uniquely identifies the row within its table. This integer is usually called the "rowid". The rowid value can be accessed using one of the special case-independent names "rowid", "oid", or "rowid" in place of a column name. If a table contains a user defined column named "rowid", "oid" or "rowid", then that name always refers the explicitly declared column and cannot be used to retrieve the integer rowid value.
The rowid (and "oid" and "rowid") is omitted in WITHOUT ROWID tables. WITHOUT ROWID tables are only available in SQLite version 3.8.2 (2013-12-06) and later. A table that lacks the WITHOUT ROWID clause is called a "rowid table".
If I read right, SQLite makes assumptions about you using an
INT
as aPK
which you do not want in your case....I think it's saying you need to add
WITHOUT ROWID
if you want to manage the PK INT value yourself?Oh, so it's SQLite that's being dumb in this case? Well that's what I get for assuming all DB managers are basically the same.
I will try to look into the RowID, and if that doesn't solve it, I will still get back to this post then with the minimal example that I was working on right now.Thank you for your time already though, it might not be solved yet, but I do appreciate you taking a look
-
Oh, so it's SQLite that's being dumb in this case? Well that's what I get for assuming all DB managers are basically the same.
I will try to look into the RowID, and if that doesn't solve it, I will still get back to this post then with the minimal example that I was working on right now.Thank you for your time already though, it might not be solved yet, but I do appreciate you taking a look
@Stroopwafe1
I just added the final sentence:I think it's saying you need to add
WITHOUT ROWID
if you want to manage the PK INT value yourself?Not bad from me, given that I don't use SQLite :) I am familiar with MySQL/MariaDB and MS's SQL Server/T-SQL. They don't have this behaviour!
-
Oh, so it's SQLite that's being dumb in this case? Well that's what I get for assuming all DB managers are basically the same.
I will try to look into the RowID, and if that doesn't solve it, I will still get back to this post then with the minimal example that I was working on right now.Thank you for your time already though, it might not be solved yet, but I do appreciate you taking a look
@Stroopwafe1
P.S.
...Or if you prefer you might allow the SQLite table to have an autoincrement single-field INT PK for its "rowid" (maybe being SQLite it works better like that since it seems to want it?) and make your compositePRIMARY KEY("food","date")
be something like a "UNIQUE [INDEX]" instead.... -
@Stroopwafe1
P.S.
...Or if you prefer you might allow the SQLite table to have an autoincrement single-field INT PK for its "rowid" (maybe being SQLite it works better like that since it seems to want it?) and make your compositePRIMARY KEY("food","date")
be something like a "UNIQUE [INDEX]" instead....@JonB
Modifying the table to beWITHOUT ROWID
I get a non-null constraint error again with version 4.
HOWEVER, doing it in my small mini example, it seems that it doesn't do thegeneration: no
and value to NULL. So I have something wrong in my main project. I guess the mini example is also just a good way to test where the problem actually is...@JonB said in QSqlRecord forces null in composite primary key on QSqlTableModel::insertRecord:
...Or if you prefer you might allow the SQLite table to have an autoincrement single-field INT PK for its "rowid" (maybe being SQLite it works better like that since it seems to want it?) and make your composite PRIMARY KEY("food","date") be something like a "UNIQUE [INDEX]" instead....
Yeah seems that might be a lot more manageable than trying to force this.
-
@JonB
Modifying the table to beWITHOUT ROWID
I get a non-null constraint error again with version 4.
HOWEVER, doing it in my small mini example, it seems that it doesn't do thegeneration: no
and value to NULL. So I have something wrong in my main project. I guess the mini example is also just a good way to test where the problem actually is...@JonB said in QSqlRecord forces null in composite primary key on QSqlTableModel::insertRecord:
...Or if you prefer you might allow the SQLite table to have an autoincrement single-field INT PK for its "rowid" (maybe being SQLite it works better like that since it seems to want it?) and make your composite PRIMARY KEY("food","date") be something like a "UNIQUE [INDEX]" instead....
Yeah seems that might be a lot more manageable than trying to force this.
@Stroopwafe1
Googlingsqlite composite primary key
there is a discussion about your situation at https://stackoverflow.com/questions/734689/sqlite-primary-key-on-multiple-columns
(there may be others). Read through it all. One post there says:In another way, you can also make the two column primary key unique and the auto-increment key primary. Just like this: https://stackoverflow.com/a/6157337
Also https://www.sqlite.org/lang_createtable.html, at least the
3.5. The PRIMARY KEY
section. -
-
Yes, thank you so much for your time and insight.
For anyone else that comes across this in a future search result: The solution is to just set a UNIQUE constraint, and add a column for autoincrement id.The final SQL Table:
CREATE TABLE "FoodDay" ( "id" INTEGER NOT NULL, "food" INTEGER NOT NULL, "date" INTEGER NOT NULL, "amount" REAL NOT NULL, "caloriesCache" INTEGER NOT NULL DEFAULT 0, UNIQUE("food","date"), PRIMARY KEY("id" AUTOINCREMENT), CONSTRAINT "FK_FoodData_ID" FOREIGN KEY("food") REFERENCES "FoodData"("id") )
-
@Stroopwafe1 said in QSqlRecord forces null in composite primary key on QSqlTableModel::insertRecord:
I can see that the values have their generation reverted to no, which means (according to the docs) that they get generated by the database. Even though they shouldn't be.
You may know more than I about this, or we may be talking at cross purposes. I have not tried any of this (!), but my understanding is: "generated" refers to whether an
INSERT
orUPDATE
statement sent by Qt from the model will include that field and its value in theVALUES
orSET
clause. Prior to/at the time ofinsertRecord()
you may set these fields and they will appear in theINSERT
statement. But once/immediately after being inserted, futureUPDATE
s should not be allowed to alter the PK value, so those columns should not be generated asSET
s in anUPDATE
statement (while the other columns may appear).Now to why you get error
Failed to flush to the database: NOT NULL constraint failed: FoodDay.food Unable to fetch row
Isn't that actually a consequence of you using a
QSqlRelationalTableModel
? Isn't it that the Qt side expects to see in the relatedFoodData
table/model an entry for the value of yourfood
column inFoodDay
table/model equal to it? You have "broken the contract" you establish withQSqlRelationalTableModel
that Qt will see in-memory models maintaining the relationship? It is not good enough if you say the row does exist at the backend database side, if you want to useQSqlRelationalTableModel
(you don't have to, it's only for value lookup) you must have the FK model/table up-to-date in memory.You might temporarily replace the
QSqlRelationalTableModel
by aQSqlTableModel
(and perhaps comment out theCONSTRAINT "FK_Food_ID"
if necessary) and compare the behaviour --- I'm thinking you will get the same "generated" behaviour but no problem adding the record?To add on to this, I discovered why it was trying to insert NULL for relation fields.
I did the select before setting my relations. And it wasn't enough to have a separate model load the related table. It has to be loaded in theQSqlRelationTableModel
for it to work. So the very original comment here (sorry if this pings you) turned out to be 100% correct.
I'm just also posting this here for future reference@JonB said in QSqlRecord forces null in composite primary key on QSqlTableModel::insertRecord:
Isn't that actually a consequence of you using a QSqlRelationalTableModel? Isn't it that the Qt side expects to see in the related FoodData table/model an entry for the value of your food column in FoodDay table/model equal to it? You have "broken the contract" you establish with QSqlRelationalTableModel that Qt will see in-memory models maintaining the relationship? It is not good enough if you say the row does exist at the backend database side, if you want to use QSqlRelationalTableModel (you don't have to, it's only for value lookup) you must have the FK model/table up-to-date in memory.