add dense result test default tip

Fri, 02 Jan 2026 15:36:13 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Fri, 02 Jan 2026 15:36:13 +0100
changeset 39
cefc18b7a9d1
parent 38
27fadadaae31

add dense result test

dbutils/object.c file | annotate | diff | comparison | revisions
test/database.c file | annotate | diff | comparison | revisions
test/database.h file | annotate | diff | comparison | revisions
test/main.c file | annotate | diff | comparison | revisions
--- a/dbutils/object.c	Thu Jan 01 17:59:32 2026 +0100
+++ b/dbutils/object.c	Fri Jan 02 15:36:13 2026 +0100
@@ -241,6 +241,10 @@
     DBUClass *cls = type;
     
     // execute sql
+    if(!query) {
+        return 1;
+    }
+    
     if(query->exec(query)) {
         query->free(query);
         return 1;
@@ -269,14 +273,13 @@
     // list of all classes returned by the result
     // used when dense == true, to check, if a new result row contains
     // the same objects (primary keys haven't changed)
-    size_t result_types_capacity = 16;
-    size_t result_types_size = 0;
     CX_ARRAY(DBUResultType, result_types);
     cx_array_init(result_types, 16);
     
     DBUResultType mainResult = { .cls = cls };
     cx_array_add(result_types, mainResult);
     
+    // analyse columns: create a column-field mapping
     for(int i=0;i<numcols;i++) {
         cxstring fieldname = cx_str(result->fieldName(result, i));
         DBUField *field = NULL;
@@ -342,7 +345,7 @@
     CxList *fklist = cxArrayListCreate(NULL, sizeof(DBUFK), 4);
     fklist->collection.simple_destructor = (cx_destructor_func)dbufkelm_free;
       
-     // get result
+    // get result
     int err = 0;
     while(result->hasData(result)) {       
         // create main result obj
@@ -351,12 +354,15 @@
         int skip_fields = 0;
         int cls_index = 0;
         if(dense) {
+            // if the primary key in this row is the same as in the previous
+            // row, we don't create a new obj for this row, but reuse the
+            // previous obj ptr.
             cxstring text = result->getText(result, main_pk_index);
             cxmutstr prev_pk = result_types.data[0].prev_key;
-            if(prev_pk.ptr && !cx_strcmp(text, cx_strcast(prev_pk))) {
+            if(prev_pk.ptr && !cx_strcmp(text, prev_pk)) {
                 obj = result_types.data[0].prev_obj;
                 addobj = false;
-                if(1 < result_types_size) {
+                if(1 < result_types.size) {
                     skip_fields = result_types.data[1].pk_col;
                 }
             }
@@ -391,12 +397,12 @@
                 
                 if(dense) {
                     // check if this object was already added
-                    if(cls_index < result_types_size) {
+                    if(cls_index < result_types.size) {
                         cxstring text = result->getText(result, result_types.data[cls_index].pk_col);
                         cxmutstr prev_pk = result_types.data[cls_index].prev_key;
                         if(prev_pk.ptr && !cx_strcmp(text, cx_strcast(prev_pk))) {
                             //printf("already added -> skip\n");
-                            if(cls_index+1 < result_types_size) {
+                            if(cls_index+1 < result_types.size) {
                                 i = result_types.data[cls_index+1].pk_col-1; // -1 because i++ by loop
                                 //printf("next col %d\n", i);
                                 continue;
@@ -438,7 +444,7 @@
                         //printf("list field\n");
                         
                         DBUField *parent_field = NULL;
-                        for(int c=0;c<result_types_size;c++) {
+                        for(int c=0;c<result_types.size;c++) {
                             DBUResultType parentType = result_types.data[c];
                             DBUField *f = cxMapGet(parentType.cls->obj_fields, field.cls->name);
                             if(f && f->builder.create) {
@@ -514,7 +520,7 @@
         cxListClear(fklist);
         
         if(dense) {
-            for(int i=0;i<result_types_size;i++) {
+            for(int i=0;i<result_types.size;i++) {
                 cxstring text = result->getText(result, result_types.data[i].pk_col);
                 free(result_types.data[i].prev_key.ptr);
                 result_types.data[i].prev_key = cx_strdup(text);
@@ -528,7 +534,7 @@
     
     cxListFree(fklist);
     
-    for(int i=0;i<result_types_size;i++) {
+    for(int i=0;i<result_types.size;i++) {
         free(result_types.data[i].prev_key.ptr);
     }
     free(result_types.data);
--- a/test/database.c	Thu Jan 01 17:59:32 2026 +0100
+++ b/test/database.c	Fri Jan 02 15:36:13 2026 +0100
@@ -143,6 +143,8 @@
     dbuClassAddObj(address, "country_id", offsetof(Address, country), country);
     
     role = dbuRegisterClass(ctx, "role", Role, role_id);
+    dbuClassAdd(role, Role, person_id);
+    dbuClassAdd(role, Role, name);
     
     person = dbuRegisterClass(ctx, "person", Person, person_id);
     dbuClassAdd(person, Person, name);
@@ -272,7 +274,7 @@
     cxMempoolFree(mp);
 }
 
-CX_TEST_SUBROUTINE(verifyMultiTableResult, CxList *persons) {
+CX_TEST_SUBROUTINE(verifyMultiTableResult, CxList *persons, bool verify_address) {
     CX_TEST_ASSERT(persons);
     CX_TEST_ASSERT(cxListSize(persons) == 2);
 
@@ -291,16 +293,18 @@
     CX_TEST_ASSERT(p1->iscustomer == 1);
     CX_TEST_ASSERT(p0->hash == 123456789);
     CX_TEST_ASSERT(p1->hash == 987654321);
-
-    CX_TEST_ASSERT(p0->address != NULL);
-    CX_TEST_ASSERT(p1->address != NULL);
+    
+    if(verify_address) {
+        CX_TEST_ASSERT(p0->address != NULL);
+        CX_TEST_ASSERT(p1->address != NULL);
 
-    CX_TEST_ASSERT(!cx_strcmp(p0->address->street, "street 1"));
-    CX_TEST_ASSERT(!cx_strcmp(p1->address->street, "street 2"));
-    CX_TEST_ASSERT(!cx_strcmp(p0->address->zip, "12343"));
-    CX_TEST_ASSERT(!cx_strcmp(p1->address->zip, "23456"));
-    CX_TEST_ASSERT(!cx_strcmp(p0->address->city, "city 17"));
-    CX_TEST_ASSERT(!cx_strcmp(p1->address->city, "city 18"));
+        CX_TEST_ASSERT(!cx_strcmp(p0->address->street, "street 1"));
+        CX_TEST_ASSERT(!cx_strcmp(p1->address->street, "street 2"));
+        CX_TEST_ASSERT(!cx_strcmp(p0->address->zip, "12343"));
+        CX_TEST_ASSERT(!cx_strcmp(p1->address->zip, "23456"));
+        CX_TEST_ASSERT(!cx_strcmp(p0->address->city, "city 17"));
+        CX_TEST_ASSERT(!cx_strcmp(p1->address->city, "city 18"));
+    }
 }
 
 CX_TEST(testMultiTableQuery) {
@@ -312,7 +316,7 @@
         DBUObjectBuilder *builder = dbuObjectBuilder(person, query, mp->allocator);
         CxList *persons = dbuObjectBuilderGetList(builder);
         
-        CX_TEST_CALL_SUBROUTINE(verifyMultiTableResult, persons);
+        CX_TEST_CALL_SUBROUTINE(verifyMultiTableResult, persons, true);
         
         dbuObjectBuilderDestroy(builder);
     }
@@ -329,7 +333,7 @@
         DBUObjectBuilder *builder = dbuObjectBuilder(person, query, mp->allocator);
         CxList *persons = dbuObjectBuilderGetList(builder);
         
-        CX_TEST_CALL_SUBROUTINE(verifyMultiTableResult, persons);
+        CX_TEST_CALL_SUBROUTINE(verifyMultiTableResult, persons, true);
         
         dbuObjectBuilderDestroy(builder);
     }
@@ -361,7 +365,7 @@
         DBUObjectBuilder *builder = dbuObjectBuilder(person, query, mp->allocator);
         CxList *persons = dbuObjectBuilderGetList(builder);
         
-        CX_TEST_CALL_SUBROUTINE(verifyMultiTableResult, persons);
+        CX_TEST_CALL_SUBROUTINE(verifyMultiTableResult, persons, true);
         CX_TEST_CALL_SUBROUTINE(verifyMultiTableResultCountry, persons);
         
         dbuObjectBuilderDestroy(builder);
@@ -384,11 +388,52 @@
         DBUObjectBuilder *builder = dbuObjectBuilder(person, query, mp->allocator);
         CxList *persons = dbuObjectBuilderGetList(builder);
         
-        CX_TEST_CALL_SUBROUTINE(verifyMultiTableResult, persons);
+        CX_TEST_CALL_SUBROUTINE(verifyMultiTableResult, persons, true);
         CX_TEST_CALL_SUBROUTINE(verifyMultiTableResultCountry, persons);
         
         dbuObjectBuilderDestroy(builder);
     }
     
     cxMempoolFree(mp);
-}
\ No newline at end of file
+}
+
+CX_TEST_SUBROUTINE(verifyPersonRoles, CxList *persons) {
+    Person *p0 = cxListAt(persons, 0);
+    Person *p1 = cxListAt(persons, 1);
+    CX_TEST_ASSERT(p0 && p1);
+    
+    CX_TEST_ASSERT(cxListSize(p0->roles) == 3);
+    CX_TEST_ASSERT(cxListSize(p1->roles) == 1);
+    
+    Role *r0 = cxListAt(p0->roles, 0);
+    Role *r1 = cxListAt(p0->roles, 1);
+    Role *r2 = cxListAt(p0->roles, 2);
+    
+    Role *p1_r0 = cxListAt(p1->roles, 0);
+    
+    CX_TEST_ASSERT(!cx_strcmp(r0->name, "finance"));
+    CX_TEST_ASSERT(!cx_strcmp(r1->name, "dev"));
+    CX_TEST_ASSERT(!cx_strcmp(r2->name, "manager"));
+    
+    CX_TEST_ASSERT(!cx_strcmp(p1_r0->name, "extern"));
+}
+
+CX_TEST(testQuerySubListDense1) {
+    CxMempool *mp = cxMempoolCreateSimple(64);
+    
+    CX_TEST_DO {
+        const char *sql1 = 
+            "select p.*, r.role_id as [__role__role_id], r.person_id, r.name from Person p inner join Role r on p.person_id = r.person_id order by p.person_id;";
+        DBUQuery *query = dbuQueryCreate(conn, mp->allocator, sql1);
+        DBUObjectBuilder *builder = dbuObjectBuilder(person, query, mp->allocator);
+        dbuObjectBuilderSetDenseResult(builder, true);
+        CxList *persons = dbuObjectBuilderGetList(builder);
+        
+        CX_TEST_CALL_SUBROUTINE(verifyMultiTableResult, persons, false);
+        CX_TEST_CALL_SUBROUTINE(verifyPersonRoles, persons);
+        
+        dbuObjectBuilderDestroy(builder);
+    }
+    
+    cxMempoolFree(mp);
+}
--- a/test/database.h	Thu Jan 01 17:59:32 2026 +0100
+++ b/test/database.h	Fri Jan 02 15:36:13 2026 +0100
@@ -54,6 +54,7 @@
 CX_TEST(testMultiTableQuery1);
 CX_TEST(testMultiTableQuery2);
 CX_TEST(testMultiTableQueryUnknownResult1);
+CX_TEST(testQuerySubListDense1);
 
 
 #ifdef __cplusplus
--- a/test/main.c	Thu Jan 01 17:59:32 2026 +0100
+++ b/test/main.c	Fri Jan 02 15:36:13 2026 +0100
@@ -137,6 +137,7 @@
     cx_test_register(suite, testMultiTableQuery1);
     cx_test_register(suite, testMultiTableQuery2);
     cx_test_register(suite, testMultiTableQueryUnknownResult1);
+    cx_test_register(suite, testQuerySubListDense1);
 #endif
     cx_test_register(suite, testObjectToJsonSimple);
     cx_test_register(suite, testObjectToJsonChildObj);

mercurial