QSqlQueryModel::data() return "null" vs "invalid" QVariant specification
-
Hence me asking you for an simple example so that we have exactly the same code to work on and find, if possible, a solution to your problem.
Roles and what is returned are two different things. There are roles that are expecting certain types, like for example a brush for
BackgroundRole. Then for user specific roles, you are free to return whatever you want. -
Hence me asking you for an simple example so that we have exactly the same code to work on and find, if possible, a solution to your problem.
Roles and what is returned are two different things. There are roles that are expecting certain types, like for example a brush for
BackgroundRole. Then for user specific roles, you are free to return whatever you want.@SGaist
This is a question about the C++ source code/behaviour. I do not presently have a case which goes wrong, but I am concerned there may be such a case. I am not able to check every call in every combination, hence the question about the source code.However, since you are kind enough to respond, here is outline Python code for the situation:
import sys import typing from PyQt5 import QtCore, QtWidgets, QtGui class MySqlQueryModel(QtGui.QSqlQueryModel): def data(self, index: QtCore.QModelIndex, role: QtCore.Qt.ItemDataRole = QtCore.Qt.DisplayRole) -> typing.Any: return super().data(index, role) def setData(self, index: QtCore.QModelIndex, value: typing.Any, role: QtCore.Qt.ItemDataRole = QtCore.Qt.EditRole) -> bool: return super().setData(index, value, role) class Main(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Main") self.setGeometry(100, 100, 500, 500) self.centralWidget = QtWidgets.QWidget(self) self.setCentralWidget(self.centralWidget) self.centralLayout = QtWidgets.QHBoxLayout(self.centralWidget) # create a QTableView self.table = QtWidgets.QTableView() self.centralLayout.addWidget(self.table) # create a QSqlModel, and set it as view's model self.model = MySqlQueryModel() # set up a (MySQL) database connection # `my_table` has `col1` which is/can be NULL and `col2` which is/can be non-NULL self.model.setQuery("SELECT col1, col2 FROM my_table WHERE col1 IS NULL AND col2 IS NOT NULL") # or perhaps just the following: # self.model.setQuery("SELECT NULL AS col1, 'something-non-NULL' AS col2") self.table.setModel(self.model) self.show() if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) main = Main() sys.exit(app.exec_())Note the following:
- Here I am overriding
QSqlQueryModel. If it makes a difference, I am doing exactly the same forQSqlTableModel&QSqlRelationalTableModel. - The overridden
data()&setData()methods are straight pass-throughs to the base C++ methods, but via Python/PyQt. If my code were C++ instead of Python/PyQt there would be no issue.
My question is about the behaviour of the C++
QVariantreturned by, say,QSqlQueryModel::data()and the C++QVariantpassed from the caller intoQSqlQueryModel::setData().My question is for all possible in-built roles (
enum Qt::ItemDataRole) which might be passed to these methods, fromQt::DisplayRolethroughQt::InitialSortOrderRole, inclusive, i.e. all the Qt definedQt::ItemDataRoles, and not any kind ofQt::UserRoleI might define. Thus it includes both true "data" roles (e.g.Qt::DisplayRole) and purely "appearance" roles (e.g.Qt::TextAlignmentRole).My question concerns 2 "slightly odd"
QVarianttypes/values:- The "invalid"
QVariant. This is whereQVariant::isValid() == false. I believe it can be created via plainQVariant(). - The "null"
QVariant. This is whereQVariant::isNull() == true. I am unsure how it is created in C++.
Now, unlike C++, PyQt "maps/unmaps" these two to/from the same Python
Nonetype/value. TheQVarianttype is simply hidden from me. The author of PyQt states that this relies on "[a particular call] not needing to treat these two as distinguishable", which he believes "is 'mostly' the case". But he states he does not claim to know exactly what individual C++ methods might do in the way of distinguishing them.So, my questions are:
-
In the case of the return value from any of the
data()overrides, for any givenItemDataRolepassed in are there any cases where it might return both an "invalid" and a "null"QVariantunder certain circumstances, to mean different things? We are only interested in one, single role being able to return either/both of these, not in different roles where some return one and some return the other. This is because PyQt returnsNoneto me for either/both cases, so I cannot distinguish. -
In the case of the value passed into any of the
setData()overrides, for any possibleItemDataRolepassed in are there any cases where it would behave differently depending on whether an "invalid" or a "null"QVariantis passed in? This might mean they treat those differently such that it matters, or it might mean that they only accept one of two and error on the other. This is because I can only passNonefrom PyQt for either/both cases [it maps that to one of "invalid"/"null"QVariant(always the same), and I don't even know which of the two], so I cannot distinguish.
I trust that the above is a very clear statement of the issue I face when writing my code. I cannot try out every combination of roles & values for each method in every circumstance, so I am asking someone with knowledge of the code/C++ behaviour to help.
Thank you very much.
- Here I am overriding
-
Hence me asking you for an simple example so that we have exactly the same code to work on and find, if possible, a solution to your problem.
Roles and what is returned are two different things. There are roles that are expecting certain types, like for example a brush for
BackgroundRole. Then for user specific roles, you are free to return whatever you want.@SGaist
Do you have any comment on my "loss-of-distinction" for "invalid" versus "null"QVariants causing any problems for eitherQAbstractItemModel::data()orQAbstractItemModel::setData(), in view of my clarification and small example code above?Or, do I just have to go with it and hope/see whether I get away with it OK in practice?
-
The only place it really matters is the editor delegate.
If your model
data()returns a valid but nullQVariant, theQItemEditorFactorywill know what appropriate editor to show (QSpinBoxfor numbers,QDateEditfor dates, etc.) if you lose that distinction it will just get the default (aQLineEdit).I'm not aware of any distinction mattering in
setDataonQSqlQueryModel -
The only place it really matters is the editor delegate.
If your model
data()returns a valid but nullQVariant, theQItemEditorFactorywill know what appropriate editor to show (QSpinBoxfor numbers,QDateEditfor dates, etc.) if you lose that distinction it will just get the default (aQLineEdit).I'm not aware of any distinction mattering in
setDataonQSqlQueryModel@VRonin
Thanks for comment.Obviously I'm most concerned about
I'm not aware of any distinction mattering in setData on QSqlQueryModel
because getting that wrong will potentially end up putting the wrong data value back to the database!
In order set the data to what I will want passed onto MySQL as database
NULL, I have to goQSqlQueryModel.setData(index, None)from PyQt. But I have no control over whether this ends up calling the C++ base method with theQVariant &valueparameter set to aQVariantwhere either:QVariant::isValid() == false; orQVariant::isValid() == true && QVariant::isNull() == true
In all cases that
Nonewill map to (the same, regardless of the role) one and only one of these two, but I don't even know which!If it does not matter which, or if it passes the "right" one (presumably the second case), it will be fine. Else I'll have problems... Let's hope for now that your "I'm not aware of any distinction mattering" is correct!
-
The MySql driver only checks
isNull(source) notisValidso sinceisValid==falseimpliesisNull==truethere is no distinction between them when usingsetDataonQSqlQueryModelusing that database@VRonin
That sounds good.so since isValid==true implies isNull==true
Umm, did you mean that? Any valid
QVariantis also "null", doesn't sound right. Do you perhaps meanisValid==false implies isNull==true??This is not helped for me because I'm at the Python side not the C++ side...
-
@VRonin
That sounds good.so since isValid==true implies isNull==true
Umm, did you mean that? Any valid
QVariantis also "null", doesn't sound right. Do you perhaps meanisValid==false implies isNull==true??This is not helped for me because I'm at the Python side not the C++ side...
-
@JonB said in QSqlQueryModel::data() return "null" vs "invalid" QVariant specification:
Do you perhaps mean isValid==false implies isNull==true??
Yep, exactly, fixed now
-
@VRonin
You did that deliberately just to confuse me, didn't you? ;-) I was scratching my head thinking that I really didn't understand what was going on!
