Bug? collection.find().where().orWhere() returns session query when collection is empty
Added by Christian Meyer about 1 month ago
Hey
I am running into this weird thing:
I have a collection Object in a ManyToOne Relation.
I want a specific subset of that collection, so I do collection.find().where(<cond1>).orWhere(<cond2>).resultList()
somehow when the collection is empty, the resultList is as if the query was run on the session itself.
std::chrono::seconds List::calculateTimeElapsed() const
{
std::chrono::seconds retSecs(0);
if(!col_Items.size()) // addition to mitigate bug
return retSecs;
auto query = col_Items.find().where("time_done NOT NULL")
.orWhere("time_pause_start NOT NULL").resultList();
for(const auto& item : query)
{
// expected 0 Items when collection empty, got several
retSecs += item->timeElapsed();
}
return retSecs;
}
I have multiple empty List
Widgets that all show the same elapsedTime()
string.
Intuition says its something to do with the orWhere()
that is not correctly setup in collections.
Replies (3)
RE: Bug? collection.find().where().orWhere() returns session query when collection is empty - Added by Mark Petryk 23 days ago
Did you ever figure this out?
RE: Bug? collection.find().where().orWhere() returns session query when collection is empty - Added by Christian Meyer 20 days ago
Not Really,
But it somehow makes sense from the documentation.
Multiple conditions may be provided by successive calls to orWhere(), and are concatenated together using 'or'. Previous conditions will be surrounded by brackets and the new condition will be concatenated using 'or'. For example:
query.where("column_a = ?").bind("A") .where("column_b = ?").bind("B") .orWhere("column_c = ?").bind("C");
results in: "where ((column_a = 'A') and (column_b = 'B')) or column_c = 'C'"
Why this only happens when the collection is empty, I did not research.
I just do a precheck to see if the collection is empty for a quick return for now
RE: Bug? collection.find().where().orWhere() returns session query when collection is empty - Added by Matthias Van Ceulebroeck 19 days ago
Hello Christian,
I believe this is indeed expected behavior. A collection is not fully like a result set, but rather a "wrapper" around a query. This means it is not stable, and its size, is dependent on when it's accessed (and thus on how the database has changed).
If you enable the logging of queries you will see this behavior.
A small example:
I have set up a List
class, which contains multiple Item
, mapped with a ManyToOne
(and belongsTo
respectively). I have greatly simplified here.
class List : public Wt::Dbo::Dbo<List>
{
public:
List() = default;
List(int id)
: listId_(id)
{
}
int count() const
{
auto query = listItems_.find()
.where("item_count != 0")
.orWhere("item_count != 0")
.resultList();
int total = 0;
for(const auto& item : query)
{
// expected 0 Items when collection empty, got several
total += item->count();
}
return total;
}
template<class Action>
void persist(Action& a)
{
Wt::Dbo::field(a, listId_, "listid");
Wt::Dbo::hasMany(a, listItems_, Wt::Dbo::ManyToOne, "list");
}
private:
int listId_ = 0;
Wt::Dbo::collection<Wt::Dbo::ptr<Item>> listItems_;
};
class Item : public Wt::Dbo::Dbo<Item>
{
public:
Item() = default;
Item(int itemCount, const Wt::Dbo::ptr<List>& list)
: itemCount_(itemCount),
list_(list)
{
}
int count() const
{
return itemCount_;
}
template<class Action>
void persist(Action& a)
{
Wt::Dbo::field(a, itemCount_, "item_count");
Wt::Dbo::belongsTo(a, list_, "list");
}
private:
int itemCount_ = 0;
Wt::Dbo::ptr<List> list_;
};
When calling my equivalent of the above function, this does NOT work correctly, not even for non-empty sets. Say that A, B, and C are all conditions. It will return (A && B) || C)
.
In the case of a relation between two classes, like with List
and Item
, the collection is retrieved as SELECT * FROM item WHERE list_id = ?
. So the relation itself is added to the query of the collection
.
Take that together with the above point, and it will (WRONGLY!) make the query SELECT * FROM item WHERE (list_id = ? AND item_count != 0) OR item_count != 0
. This essentially OMITS the foreign key constraint, and only focuses on the last clause (unless of course, both first clauses are satisfied at once).
So, the "solution" is to write a new complete query, or to retrieve the whole result set col_Items
, and only then perform filtering on it, in the code, not in the database (mind that can be "heavy, depending on the table's size).
I hope that helps, and let me know if there is anything unclear.
Best,
Matthias