--- a/test/database.c Wed Jan 21 18:19:31 2026 +0100 +++ b/test/database.c Wed Feb 04 20:00:54 2026 +0100 @@ -94,6 +94,8 @@ static DBUClass *address; static DBUClass *person; static DBUClass *role; +static DBUClass *resource; +static DBUClass *note; typedef struct Country { int64_t country_id; @@ -130,6 +132,23 @@ cxmutstr name; } Role; +typedef struct Resource { + int64_t resource_id; + int64_t parent_id; + char *nodename; + char *content; + bool iscollection; +} Resource; + +typedef struct Note { + int64_t note_id; + int64_t resource_id; + char *tags; + int type; + + Resource *resource; +} Note; + int init_db_tests(void) { ctx = dbuContextCreate(); @@ -140,7 +159,7 @@ dbuClassAdd(address, Address, street); dbuClassAdd(address, Address, zip); dbuClassAdd(address, Address, city); - dbuClassAddObj(address, "country_id", offsetof(Address, country), country); + dbuClassAddObj(address, "country", offsetof(Address, country), country); role = dbuRegisterClass(ctx, "role", Role, role_id); dbuClassAdd(role, Role, person_id); @@ -152,12 +171,25 @@ dbuClassAdd(person, Person, age); dbuClassAdd(person, Person, iscustomer); dbuClassAdd(person, Person, hash); - dbuClassAddObj(person, "address_id", offsetof(Person, address), address); + dbuClassAddObj(person, "address", offsetof(Person, address), address); dbuClassAddCxLinkedList(person, "person_id", offsetof(Person, roles), role); dbuClassAddForeignKey(role, Role, person_id, person); dbuClassAdd(role, Role, name); + resource = dbuRegisterClass(ctx, "resource", Resource, resource_id); + dbuClassAdd(resource, Resource, parent_id); + dbuClassAdd(resource, Resource, nodename); + dbuClassAdd(resource, Resource, content); + dbuClassAdd(resource, Resource, iscollection); + + note = dbuRegisterClass(ctx, "note", Note, note_id); + dbuClassAdd(note, Note, resource_id); + dbuClassAdd(note, Note, tags); + dbuClassAdd(note, Note, type); + dbuClassAddObj(note, "resource", offsetof(Note, resource), resource); + + return 0; } @@ -409,6 +441,55 @@ cxMempoolFree(mp); } +CX_TEST(testMultiTableQuery3) { + CxMempool *mp = cxMempoolCreateSimple(64); + + CX_TEST_DO { + const char *sql1 = + "select n.*, r.resource_id as [__resource__resource_id], r.parent_id, " \ + "r.nodename, r.content, r.iscollection " \ + "from resource r inner join note n on r.resource_id = n.resource_id " \ + "where parent_id = (select resource_id from resource where nodename = 'Collection1') ;"; + DBUQuery *q = dbuQueryCreate(conn, mp->allocator, sql1); + DBUObjectBuilder *builder = dbuObjectBuilder(note, q, mp->allocator); + CxList *notes = dbuObjectBuilderGetList(builder); + dbuObjectBuilderDestroy(builder); + + CX_TEST_ASSERT(notes); + CX_TEST_ASSERT(cxListSize(notes) == 2); + + Note *n0 = cxListAt(notes, 0); + Note *n1 = cxListAt(notes, 1); + + CX_TEST_ASSERT(n0); + CX_TEST_ASSERT(n1); + + CX_TEST_ASSERT(n0->note_id > 0); + CX_TEST_ASSERT(n0->resource_id > 0); + CX_TEST_ASSERT(!cx_strcmp(n0->tags, "todo, test")); + CX_TEST_ASSERT(n0->type == 1); + CX_TEST_ASSERT(n0->resource); + CX_TEST_ASSERT(n0->resource_id == n0->resource->resource_id); + CX_TEST_ASSERT(!cx_strcmp(n0->resource->nodename, "note1")); + CX_TEST_ASSERT(!n0->resource->iscollection); + CX_TEST_ASSERT(n0->resource->parent_id > 0); + CX_TEST_ASSERT(!cx_strcmp(n0->resource->content, "Hello World!")); + + CX_TEST_ASSERT(n1->note_id > 0); + CX_TEST_ASSERT(n1->resource_id > 0); + CX_TEST_ASSERT(!cx_strcmp(n1->tags, "work, project2501, ai")); + CX_TEST_ASSERT(n1->type == 2); + CX_TEST_ASSERT(n1->resource); + CX_TEST_ASSERT(n1->resource_id == n1->resource->resource_id); + CX_TEST_ASSERT(!cx_strcmp(n1->resource->nodename, "note2")); + CX_TEST_ASSERT(!n1->resource->iscollection); + CX_TEST_ASSERT(n1->resource->parent_id > 0); + CX_TEST_ASSERT(!cx_strcmp(n1->resource->content, "Test String")); + } + + cxMempoolFree(mp); +} + CX_TEST(testMultiTableQueryUnknownResult1) { CxMempool *mp = cxMempoolCreateSimple(64);