[odb-users] Composite pattern (not composite value) with extra pointer to a child

Alexandre Pretyman alexandre.pretyman at gmail.com
Sun Mar 15 10:47:20 EDT 2015


Hello list,

This is just a heads up from a current experience with persisting the
composite pattern with ODB, where the child has a pointer to the parent and
the parent has also a pointer to a "chosen child". I present the problem I
ran into and what I did to work around it at the end.

I would populate the object and when I tried to persist it, I was getting a
Foreign Key constraint violation.

Here are the class definitions for this scenario:

#pragma db object polymorphic
struct simple_object {
virtual ~simple_object() {}
#pragma db id auto
unsigned long id = {0};
simple_object * parent;
 std::string name;
};

#pragma db object
struct composite_object : simple_object {
simple_object * chosen_child;
#pragma db not_null inverse(parent)
std::vector<std::unique_ptr<simple_object>> children;
 simple_object * add_child() {
std::unique_ptr<simple_object> child(new simple_object);
child->parent = this;
simple_object * ptr_to_return = child.get();
children.push_back(std::move(child));
return ptr_to_return;
}
};

I would populate the objects and then try to persist them after being
populated with ODB:

{
odb::session s;
odb::transaction t (db->begin ());

composite_object comp_obj;
comp_obj.name = "I am the parent";
 auto child = comp_obj.add_child();
child->name = "I am the child";
comp_obj.chosen_child = child;
 db->persist(comp_obj);
db->persist(child);

t.commit ();
}

However this gives me a Foreign Key constraint on sqlite:

sqlite3_trace - INSERT INTO "simple_object" ("id", "typeid", "parent",
"name") VALUES (NULL, 'composite_object', NULL, 'I am the parent')
sqlite3_trace - INSERT INTO "composite_object" ("id", "chosen_child")
VALUES (1, 0)
sqlite3_trace - INSERT INTO "simple_object" ("id", "typeid", "parent",
"name") VALUES (NULL, 'simple_object', 1, 'I am the child')
sqlite3_trace - COMMIT
Exception: 19 (787): FOREIGN KEY constraint failed

Because as the parent object was being persisted, it tried to persist the
child which had an invalid id, with INSERT INTO "composite_object" ("id",
"chosen_child") VALUES (1, 0) where 0 should have been a valid foreign key.

Trying to persist the child before the parent also resulted also with a
foreign key violation, because it tried to refer to a parent which had an
invalid id, as the parent had not been persisted yet.

The solution was to persist the objects before they were wired together and
update the parent at the end to reflect the changes:

{
odb::session s;
odb::transaction t (db->begin ());

composite_object comp_obj;
db->persist(comp_obj);
comp_obj.name = "I am the parent";
 auto child = comp_obj.add_child();
child->name = "I am the child";
comp_obj.chosen_child = child;
 db->persist(child);
db->update(comp_obj);

t.commit ();
}

Where this resulted in a successful commit:

sqlite3_trace - INSERT INTO "simple_object" ("id", "typeid", "parent",
"name") VALUES (NULL, 'composite_object', NULL, '')
sqlite3_trace - INSERT INTO "composite_object" ("id", "chosen_child")
VALUES (1, NULL)
sqlite3_trace - INSERT INTO "simple_object" ("id", "typeid", "parent",
"name") VALUES (NULL, 'simple_object', 1, 'I am the child')
sqlite3_trace - UPDATE "simple_object" SET "parent"=NULL, "name"='I am the
parent' WHERE "id"=1
sqlite3_trace - UPDATE "composite_object" SET "chosen_child"=2 WHERE "id"=1
sqlite3_trace - COMMIT

So it seems that for this scenario odb must be part of wiring of the object
pointers.

Regards,

-- 
Alexandre Pretyman


More information about the odb-users mailing list