Commit b59d09c6 authored by EXT Arnaud Clère's avatar EXT Arnaud Clère

Illustrated a simple mapping between nested Person tree and table

encoding parentship in an (id,parent_id,...). This kind of mapping makes
using QSqlTableModel trivial.
parent 533b0132
......@@ -1076,7 +1076,7 @@ struct QBind<QList<T>> {
ts.insert(it++, t);
}
else {
T t(ts[it]);
T t(ts[it]); // support partial binds by copying current value, before binding then setting the bound item when successful
if ((s = i.bind(t)))
ts[it++] = t;
}
......
......@@ -59,6 +59,9 @@ class QModelBind : public IBind
public:
QModelBind(QAbstractItemModel* m, bool rowFirst=true) : m(m), rowFirst(rowFirst) { Q_ASSERT(m); }
struct Error { QIdentifierLiteral error; QModelIndex index; template<class T> T bind(Val<T>&& value) { QByteArray u(error.utf8()); for (; index.isValid(); index=index.parent()) u.append('/').append(QByteArray::number(index.row())).append(',').append(QByteArray::number(index.column())); return value.bind(QUtf8Data(u)); } };
QVector<Error> errors;
Val<Cursor> value() { return Cursor(this).value(); }
protected:
enum : int {
......@@ -154,7 +157,7 @@ protected:
virtual bool _bind( QByteArray& r) { return _bind(QByteArray(r)); }
virtual bool _bind( QVariant& r) { return _bind( QVariant(r)); }
virtual void _reportError(QIdentifierLiteral n) { qDebug(n.utf8()); }
virtual void _reportError(QIdentifierLiteral n) { errors.append(Error{n, parent}); }
virtual IBind* itemBind() = 0;
......@@ -504,6 +507,9 @@ protected:
return true;
}
else if (is(R)) {
if (m->rowCount() <= row+1) {
return false;
}
row++; // mandatory after _sequence()
if (hidden()) {
return true;
......
......@@ -82,7 +82,7 @@
struct Person
{
QString firstName, lastName; double height; int age; QVector<Phone> phones; QString comments; QList<Person> children;
int id; QString firstName, lastName; double height; int age; QVector<Phone> phones; QString comments; QList<Person> children;
Cursor bind(Val<Cursor>&& value) { // works with value->mode()==Read as well as Write
auto r = value.record("Person");
......@@ -130,7 +130,7 @@ QDataStream &operator>>(QDataStream &in, Person &p)
return in >> p.firstName >> p.lastName >> p.height >> p.age >> p.phones >> p.comments >> p.children;
}
// Using View types to customize an existing bind using metadata
// Using a view type to customize an existing bind using metadata
struct PersonsView
{
......@@ -144,6 +144,105 @@ struct PersonsView
}
};
// View types can even bind nested structures into flattened tables using a foreign key to express parent 1-n children relationships
struct PersonsTable {
QList<Person>& persons;
PersonsTable(QList<Person>& ps) : persons(ps) {}
Cursor bind(Val<Cursor>&& v) {
if (v->mode()==Read) {
ids.clear();
orphans.clear();
persons.clear();
Person p;
auto s(v.sequence());
while ((s = bind(p, std::move(s)))) {
// merge orphans with persons at the end only
}
for (int parentId: orphans.keys()) {
if (parentId && ids.contains(parentId)) {
ids[parentId]->children = orphans[parentId];
}
else {
persons.append(orphans[parentId]);
}
}
return s;
}
else if (v->mode()==Write) {
parentIds.clear();
ids.clear();
return flatten(persons, v.sequence());
}
}
private:
QStack<int> parentIds;
QMap<int,Person*> ids;
QMap<int,QList<Person>> orphans;
Seq<Cursor> bind(Person& p, Seq<Cursor>&& s) {
if (s->mode()==Read) {
int parentId=0;
double height_ft=0.;
s = s.record()
.bind("id" , p.id )
.bind("parent_id" , parentId )
.bind("first_name", p.firstName)
.bind("last_name" , p.lastName )
.bind("height_ft" , height_ft )
.bind("age" , p.age )
.bind("comments" , p.comments ) // TODO Remove while keeping p.comments untouched
.bind("phones" , p.phones )
.out();
if (s) {
p.height = height_ft/3.28;
orphans[parentId].append(p);
if (p.id) {
Q_ASSERT(!ids.contains(p.id)); // children may be attributed to any of the duplicates
ids.insert(p.id, &orphans[parentId].last());
}
}
return std::move(s);
}
else if (s->mode()==Write) {
if (!p.id) {
int newId=(2019-p.age)*10;
while (ids.contains(newId)) {
newId++;
Q_ASSERT(newId<(2020-p.age)*10);
}
p.id = newId;
ids.insert(p.id,&p);
}
s = s.record()
.bind("id" , p.id)
.bind("parent_id" , parentIds.isEmpty() ? 0 : parentIds.back())
.bind("first_name", p.firstName )
.bind("last_name" , p.lastName )
.bind("height_ft" , p.height*3.28)
.bind("age" , p.age )
.bind("comments" , p.comments )
.bind("phones" , p.phones )
.out();
parentIds.push_back(p.id);
s = flatten(p.children, std::move(s));
parentIds.pop_back();
return std::move(s);
}
}
Seq<Cursor> flatten(QList<Person>& ps, Seq<Cursor>&& s) {
for (auto&& p : ps) {
s = bind(p, std::move(s));
}
return std::move(s);
}
};
// //////////////////////////////////////////////////////////////////////////
// QBind<T> examples using external bind methods
......@@ -240,7 +339,7 @@ static QVector<double> transform{1./3, 2./3, 1./3, 1.,
2./3, 1./3, 2./3, 1.,
1./3, 2./3, 1./3, 1.,
0. , 0. , 0. , 1.};
static Person person{"John","Doe",1.75,18,{},"unicode is likely U+01 \x01 + U+1F \x1F + U+A4 ¤ U+B0 ° U+D8 Ø U+FF ÿ",QList<Person>()};
static Person person{0,"John","Doe",1.75,18,{},"unicode is likely U+01 \x01 + U+1F \x1F + U+A4 ¤ U+B0 ° U+D8 Ø U+FF ÿ",QList<Person>()};
static const double PI=3.141592653589793;
static Phone phone {Phone::Home,"+44 1234567"};
......@@ -1055,12 +1154,15 @@ void doGuiExample() {
int currentTransformTab = -1;
QObject::connect(r0, &QTabWidget::currentChanged, [&](int newTab) {
if (0<=currentTransformTab)
transformTabs[currentTransformTab].bind(QModelReader<>(transformTabs[currentTransformTab].model, false).value());
QByteArray json;
QJsonWriter(&json).bind(transform);
r0Label->setText(QString::fromUtf8(json));
QByteArray text;
if (0<=currentTransformTab) {
QModelReader<> reader(transformTabs[currentTransformTab].model, false);
transformTabs[currentTransformTab].bind(reader.value());
TextWriter(&text).bind(reader.errors);
text.append('\n');
}
QJsonWriter(&text).bind(transform);
r0Label->setText(QString::fromUtf8(text));
transformTabs[newTab].model->clear();
transformTabs[newTab].bind(QModelWriter<>(transformTabs[newTab].model, false).value());
......@@ -1110,19 +1212,22 @@ void doGuiExample() {
});}},
// The same design allows flattening trees into a sequence
{ new QStandardItemModel(&dlg), new QTableView(&dlg), "sequence().with(persons,flatten)", [&](Val<Cursor>&& v) {
return v.sequence().with(persons,flatten); } },
{ new QStandardItemModel(&dlg), new QTableView(&dlg), "bind(PersonsTable{persons})", [&](Val<Cursor>&& v) {
return v.bind(PersonsTable(persons)); } },
};
for (auto&& t: personsTabs) { r1->addTab(t.widget, t.name); t.widget->setModel(t.model); }
int currentPersonsTab = -1;
QObject::connect(r1, &QTabWidget::currentChanged, [&](int newTab) {
if (0<=currentPersonsTab)
personsTabs[currentPersonsTab].bind(QModelReader<>(personsTabs[currentPersonsTab].model).value());
QByteArray json;
QJsonWriter(&json).bind(persons);
r1Label->setText(QString::fromUtf8(json));
QByteArray text;
if (0<=currentPersonsTab) {
QModelReader<> reader(personsTabs[currentPersonsTab].model);
personsTabs[currentPersonsTab].bind(reader.value());
TextWriter(&text).bind(reader.errors);
text.append('\n');
}
QJsonWriter(&text).bind(persons);
r1Label->setText(QString::fromUtf8(text));
personsTabs[newTab].model->clear();
personsTabs[newTab].bind(QModelWriter<>(personsTabs[newTab].model).value());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment