prepare for list members

Fri, 13 Dec 2024 11:24:55 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Fri, 13 Dec 2024 11:24:55 +0100
changeset 7
c98ff52cd806
parent 6
d6981b56ab30
child 8
bd08116b8af4

prepare for list members

dbutils/class.c file | annotate | diff | comparison | revisions
dbutils/dbutils/dbutils.h file | annotate | diff | comparison | revisions
dbutils/field.c file | annotate | diff | comparison | revisions
dbutils/object.c file | annotate | diff | comparison | revisions
dbutils/object.h file | annotate | diff | comparison | revisions
test/main.c file | annotate | diff | comparison | revisions
--- a/dbutils/class.c	Wed Dec 11 21:53:31 2024 +0100
+++ b/dbutils/class.c	Fri Dec 13 11:24:55 2024 +0100
@@ -50,6 +50,7 @@
     cls->fields->collection.simple_destructor = (cx_destructor_func)field_destructor;
     cls->obj_fields = cxHashMapCreateSimple(CX_STORE_POINTERS);
     cls->obj_fields->collection.simple_destructor = (cx_destructor_func)field_destructor;
+    cls->foreign_keys = cxHashMapCreateSimple(sizeof(DBUForeignKeyField));
     
     return cls;
 }
@@ -67,9 +68,17 @@
     cxMapPut(cls->fields, name, field);
 }
 
+void dbuClassAddFKField(DBUClass *cls, const char *name, DBUField *field, DBUClass *fkcls) {
+    DBUForeignKeyField val;
+    val.field = field;
+    val.cls = fkcls;
+    field->foreignKeyClass = fkcls;
+    cxMapPut(cls->foreign_keys, name, &val);
+}
+
 void dbuClassAddObjField(DBUClass *cls, const char *name, DBUField *field, DBUClass *foreign_cls) {
     free(field->name.ptr);
-    field->name = cx_strdup(cx_str(name));
+    field->name = name ? cx_strdup(cx_str(name)) : (cxmutstr){NULL,0};
     cxMapPut(cls->obj_fields, foreign_cls->name, field);
 }
 
--- a/dbutils/dbutils/dbutils.h	Wed Dec 11 21:53:31 2024 +0100
+++ b/dbutils/dbutils/dbutils.h	Fri Dec 13 11:24:55 2024 +0100
@@ -48,6 +48,7 @@
 typedef struct DBUField   DBUField;
 
 typedef struct DBUObjectBuilder DBUObjectBuilder;
+typedef struct DBUForeignKeyField DBUForeignKeyField;
 
 typedef char* DBUObject;
 
@@ -100,6 +101,14 @@
     CxMap *obj_fields;
     
     /*
+     * foreign keys
+     * 
+     * key: field name
+     * value: DBUForeignKeyField
+     */
+    CxMap *foreign_keys;
+    
+    /*
      * object size used for allocation, typically sizeof(some_struct)
      */
     size_t obj_size;
@@ -111,6 +120,22 @@
     int (*init)(void *obj, const CxAllocator *a);
 };
 
+typedef struct DBUObjectResult DBUObjectResult;
+struct DBUObjectResult {
+    void *userdata1;
+    void *userdata2;
+    int int1;
+    int int2;
+    DBUObject (*create)(DBUObjectResult *result, DBUClass *type, const CxAllocator *a);
+    int (*add)(DBUObjectResult *result, DBUObject parent, void *obj, CxList *fk, const CxAllocator *a);
+    void (*free)(DBUObjectResult *result);
+};
+
+struct DBUForeignKeyField {
+    DBUField *field;
+    DBUClass *cls;
+};
+
 /*
  * abstract field
  */
@@ -163,10 +188,20 @@
     void (*destructor)(DBUField *f);
     
     /*
+     * object result builder callback struct (optional for list/array types)
+     * 
+     * DBUObjectResult contains callback for adding objects to
+     * list/array-fields
+     */
+    DBUObjectResult builder;
+    
+    DBUClass *foreignKeyClass;
+    
+    /*
      * if true, null values are not accepted and result in an error
      */
     bool nonnull;
-    bool query_length;
+    bool query_length; // TODO: remove
 };
 
 DBUContext* dbuContextCreate(void);
@@ -239,6 +274,7 @@
 void dbuClassSetPrimaryKeyCxMutStr(DBUClass *cls, const char *column_name, off_t offset);
 
 void dbuClassAddField(DBUClass *cls, const char *name, DBUField *field);
+void dbuClassAddFKField(DBUClass *cls, const char *name, DBUField *field, DBUClass *fkcls);
 void dbuClassAddObjField(DBUClass *cls, const char *name, DBUField *field, DBUClass *foreign_cls);
 
 #define dbuClassAdd(cls, type, member) \
@@ -260,6 +296,16 @@
     cxmutstr:     dbuClassAddCXMutStr) \
     (cls, member_name, offsetof(type, member), 0)
 
+#define dbuClassAddForeignKey(cls, type, member, foreigncls) \
+    _Generic(((type*)0)->member, \
+    int32_t:      dbuClassAddInt32FK,    \
+    uint32_t:     dbuClassAddUInt32FK,   \
+    int64_t:      dbuClassAddInt64FK,    \
+    uint64_t:     dbuClassAddUInt64FK,   \
+    char*:        dbuClassAddStringFK,   \
+    cxmutstr:     dbuClassAddCXMutStrFK) \
+    (cls, #member, offsetof(type, member), foreigncls, 0)
+
 void dbuClassAddInt(DBUClass *cls, const char *name, off_t offset, bool nonnull);
 void dbuClassAddUInt(DBUClass *cls, const char *name, off_t offset, bool nonnull);
 void dbuClassAddInt8(DBUClass *cls, const char *name, off_t offset, bool nonnull);
@@ -282,6 +328,13 @@
 void dbuClassAddBuf(DBUClass *cls, const char *name, off_t offset, off_t size_offset, bool nonnull);
 void dbuClassAddBufIntLen(DBUClass *cls, const char *name, off_t offset, off_t int_offset, bool nonnull);
 
+void dbuClassAddInt32FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull);
+void dbuClassAddUInt32FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull);
+void dbuClassAddInt64FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull);
+void dbuClassAddUInt64FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull);
+void dbuClassAddStringFK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull);
+void dbuClassAddCXMutStrFK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull);
+
 void dbuClassAddIntDef(DBUClass *cls, const char *name, off_t offset, int def);
 void dbuClassAddUIntDef(DBUClass *cls, const char *name, off_t offset, unsigned int def);
 void dbuClassAddInt16Def(DBUClass *cls, const char *name, off_t offset, int16_t def);
@@ -296,10 +349,10 @@
 void dbuClassAddDoubleDef(DBUClass *cls, const char *name, off_t offset, double def);
 
 void dbuClassAddObj(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls);
-void dbuClassAddLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls);
-void dbuClassAddValueLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls);
-void dbuClassAddCxArrayList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls);
-void dbuClassAddValueArrayList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls);
+void dbuClassAddCxLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls);
+void dbuClassAddValueCxLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls);
+void dbuClassAddCxArrayList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_clst);
+void dbuClassAddValueCxArrayList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls);
 void dbuClassAddArray(DBUClass *cls, const char *name, off_t array_offset, off_t size_offset, DBUClass *foreign_cls);
 void dbuClassAddValueArray(DBUClass *cls, const char *name, off_t array_offset, off_t size_offset, DBUClass *foreign_cls);
 
--- a/dbutils/field.c	Wed Dec 11 21:53:31 2024 +0100
+++ b/dbutils/field.c	Fri Dec 13 11:24:55 2024 +0100
@@ -31,6 +31,9 @@
 #include <string.h>
 #include <limits.h>
 
+#include <cx/linked_list.h>
+#include <cx/array_list.h>
+
 #include "field.h"
 
 /* -------------------- Default Initializer Functions -------------------- */
@@ -503,6 +506,17 @@
     dbuClassAddField(cls, name, create_offset_def_field(offset, def_init, init, nonnull, def));
 }
 
+static void add_offset_fk_field(
+        DBUClass *cls, 
+        const char *name, 
+        off_t offset,
+        DBUClass *fkcls,
+        DBUFieldInitFunc init,
+        bool nonnull)
+{
+    dbuClassAddFKField(cls, name, create_offset_def_field(offset, NULL, init, nonnull, (union DBUDefValue){ 0 }), fkcls);
+}
+
 static void add_bool(
         DBUClass *cls, 
         const char *name, 
@@ -821,6 +835,67 @@
 }
 
 
+void dbuClassAddInt32FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull) {
+    add_offset_fk_field(
+            cls,
+            name,
+            offset,
+            foreign_cls,
+            (DBUFieldInitFunc)field_init_int32,
+            nonnull);
+}
+
+void dbuClassAddUInt32FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull){
+    add_offset_fk_field(
+            cls,
+            name,
+            offset,
+            foreign_cls,
+            (DBUFieldInitFunc)field_init_uint32,
+            nonnull);
+}
+
+void dbuClassAddInt64FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull){
+    add_offset_fk_field(
+            cls,
+            name,
+            offset,
+            foreign_cls,
+            (DBUFieldInitFunc)field_init_int64,
+            nonnull);
+}
+
+void dbuClassAddUInt64FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull){
+    add_offset_fk_field(
+            cls,
+            name,
+            offset,
+            foreign_cls,
+            (DBUFieldInitFunc)field_init_uint64,
+            nonnull);
+}
+
+void dbuClassAddStringFK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull){
+    add_offset_fk_field(
+            cls, 
+            name,
+            offset,
+            foreign_cls,
+            (DBUFieldInitFunc)field_init_str,
+            nonnull);
+}
+
+void dbuClassAddCXMutStrFK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull){
+    add_offset_fk_field(
+            cls, 
+            name,
+            offset,
+            foreign_cls,
+            (DBUFieldInitFunc)field_init_cxmutstr,
+            nonnull);
+}
+
+
 void dbuClassAddIntDef(DBUClass *cls, const char *name, off_t offset, int def) {
     union DBUDefValue defvalue;
     defvalue.def = def;
@@ -995,8 +1070,50 @@
     dbuClassAddObjField(cls, name, (DBUField*)field, foreign_cls);
 }
 
-void dbuClassAddLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls) {
-    
+static int linked_list_add(DBUField *f, const CxAllocator *a, DBUObject obj, void *child) {
+    DBUOffsetField *field = (DBUOffsetField*)f;
+    CxList **list = (CxList**)(obj+field->offset);
+    if(*list) {
+        *list = cxLinkedListCreate(a, NULL, CX_STORE_POINTERS);
+        if(!(*list)) {
+            return 1;
+        }
+    }
+    return cxListAdd(*list, child);;
+}
+
+static DBUObject linkedlist_create_obj(DBUObjectResult *result, DBUClass *type, const CxAllocator *a) {
+    void *obj = cxMalloc(a, type->obj_size);
+    if(obj) {
+        memset(obj, 0, type->obj_size);
+    }
+    return obj;
+}
+static int linkedlist_add(DBUObjectResult *result, DBUObject parent, void *obj, CxList *fk, const CxAllocator *a) {
+    DBUOffsetField *field  = result->userdata1;
+    DBUClass *cls = result->userdata2;
+    CxList **list = (CxList**)(parent+field->offset);
+    if(*list) {
+        *list = cxLinkedListCreate(a, NULL, CX_STORE_POINTERS);
+        if(!(*list)) {
+            return 1;
+        }
+    }
+    cxListAdd(*list, obj);;
+    return 0;
+}
+
+void dbuClassAddCxLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls) {
+    DBUOffsetField *field = malloc(sizeof(DBUOffsetField));
+    memset(field, 0, sizeof(DBUOffsetField));
+    field->offset = offset;
+    field->def.def = 0;
+    field->field.builder.userdata1 = field;
+    field->field.builder.userdata2 = foreign_cls;
+    field->field.builder.create = linkedlist_create_obj;
+    field->field.builder.add = linkedlist_add;
+    dbuClassAddObjField(cls, NULL, (DBUField*)field, foreign_cls);
+    //dbuClassAddParentObjField(foreign_cls, name, cls, (DBUField*)field);
 }
 
 void dbuClassAddValueLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls) {
--- a/dbutils/object.c	Wed Dec 11 21:53:31 2024 +0100
+++ b/dbutils/object.c	Fri Dec 13 11:24:55 2024 +0100
@@ -64,7 +64,6 @@
     builder->ctx = type->context;
     builder->mainQuery = query;
     builder->resultType = type;
-    builder->subQueries = subQueries;
     builder->additionalQueries = additionalQueries;
     
     return builder;
@@ -84,16 +83,20 @@
 
 // builder result constructors
 
-static DBUObject list_result_create(DBUObjectBuilderResult *result, DBUClass *type, const CxAllocator *a) {
-    if(result->int1 == 0) {
-        return cxMalloc(a, type->obj_size);
-    } else {
-        memset(result->userdata2, 0, result->int1);
-        return result->userdata2;
+static DBUObject result_create_obj(DBUObjectResult *result, DBUClass *type, const CxAllocator *a) {
+    void *obj = cxMalloc(a, type->obj_size);
+    if(obj) {
+        memset(obj, 0, type->obj_size);
     }
+    return obj;
 }
 
-static int list_result_add(DBUObjectBuilderResult *result, void *obj) {
+static DBUObject valuelist_result_create(DBUObjectResult *result, DBUClass *type, const CxAllocator *a) {
+    memset(result->userdata2, 0, result->int1);
+    return result->userdata2;
+}
+
+static int list_result_add(DBUObjectResult *result, DBUObject parent, void *obj, CxList *fk, const CxAllocator *a) {
     return cxListAdd(result->userdata1, obj);
 }
 
@@ -104,11 +107,11 @@
     }
     // TODO: the class needs a obj destructor func, because we need to be able
     //       to destroy partialy created lists
-    DBUObjectBuilderResult result = { 
+    DBUObjectResult result = { 
         .userdata1 = result_list,
         .userdata2 = NULL,
         .int1 = CX_STORE_POINTERS,
-        .create = list_result_create,
+        .create = result_create_obj,
         .add = list_result_add
     };
     if(dbuObjectExec(builder, &result)) {
@@ -126,11 +129,11 @@
     }
     // TODO: the class needs a obj destructor func, because we need to be able
     //       to destroy partialy created lists
-    DBUObjectBuilderResult result = { 
+    DBUObjectResult result = { 
         .userdata1 = result_list,
         .userdata2 = malloc(builder->resultType->obj_size),
         .int1 = builder->resultType->obj_size,
-        .create = list_result_create,
+        .create = valuelist_result_create,
         .add = list_result_add
     };
     if(!result.userdata2) {
@@ -156,11 +159,14 @@
 
 /* ------------------------- Object Builder -----------------------------*/
 
-static int result_add_noop(DBUObjectBuilderResult *result, void *obj) {
+static int add_to_parent(DBUObjectResult *result, DBUObject parent, void *obj, CxList *fk, const CxAllocator *a) {
+    DBUBuilderQuery *query = result->userdata1;
+    
+    
     return 0;
 }
 
-int dbuObjectExec(DBUObjectBuilder *builder, DBUObjectBuilderResult *objresult) {
+int dbuObjectExec(DBUObjectBuilder *builder, DBUObjectResult *objresult) {
     if(cxListSize(builder->additionalQueries) > 0) {
         builder->cache = cxHashMapCreateSimple(sizeof(DBUBuilderObjCache));
         if(!builder->cache) {
@@ -171,16 +177,16 @@
     int ret = dbuObjectExecuteQuery(builder, builder->mainQuery, builder->resultType, objresult);
     if(!ret) {
         CxIterator i = cxListIterator(builder->additionalQueries);
-        cx_foreach(DBUBuilderQuery *, a, i) {
-            DBUObjectBuilderResult result = { 
-                .userdata1 = NULL,
+        cx_foreach(DBUBuilderQuery *, q, i) {
+            DBUObjectResult result = { 
+                .userdata1 = q,
                 .userdata2 = NULL,
                 .int1 = CX_STORE_POINTERS,
-                .create = list_result_create,
-                .add = result_add_noop
+                .create = result_create_obj,
+                .add = add_to_parent
             };
             
-            ret = dbuObjectExecuteQuery(builder, a->query, a->type, &result);
+            ret = dbuObjectExecuteQuery(builder, q->query, q->type, &result);
             if(ret) {
                 break;
             }
@@ -194,7 +200,7 @@
     return ret;
 }
 
-int dbuObjectExecuteQuery(DBUObjectBuilder *builder, DBUQuery *query, DBUClass *type, DBUObjectBuilderResult *objresult) {
+int dbuObjectExecuteQuery(DBUObjectBuilder *builder, DBUQuery *query, DBUClass *type, DBUObjectResult *objresult) {
     DBUClass *cls = type;
     
     // TODO: execute additional queries
@@ -264,6 +270,9 @@
     
     const CxAllocator *a = builder->allocator;
     
+    CxList *fklist = cxArrayListCreateSimple(CX_STORE_POINTERS, 4);
+    fklist->collection.simple_destructor = free;
+    
      // get result
     int err = 0;
     while(result->hasData(result)) {
@@ -318,18 +327,25 @@
                     // if obj caching is enabled and the current field contains
                     // the primary key, add this object to the cache
                     if(builder->cache && field.field == current_cls->primary_key) {
-                        cxmutstr cache_key = cx_asprintf("%.*s::%.*s",
-                                (int)current_cls->name.length, current_cls->name.ptr,
-                                (int)text.length, text.ptr);
-                        if(!cache_key.ptr) {
-                            err = 1;
-                            break;
-                        }
-                        int r = cxMapPut(builder->cache, cache_key, current_obj);
-                        free(cache_key.ptr);
-                        if(r) {
-                            err = 1;
-                            break;
+                        if(field.field == current_cls->primary_key) {
+                            cxmutstr cache_key = cx_asprintf("%.*s::%.*s",
+                                    (int)current_cls->name.length, current_cls->name.ptr,
+                                    (int)text.length, text.ptr);
+                            if(!cache_key.ptr) {
+                                err = 1;
+                                break;
+                            }
+                            int r = cxMapPut(builder->cache, cache_key, current_obj);
+                            free(cache_key.ptr);
+                            if(r) {
+                                err = 1;
+                                break;
+                            }
+                        } else if(field.field->foreignKeyClass) {
+                            cxmutstr fkey = cx_asprintf("%.*s::%.*s",
+                                    (int)field.field->foreignKeyClass->name.length, current_cls->name.ptr,
+                                    (int)text.length, text.ptr);
+                            cxListAdd(fklist, fkey.ptr);
                         }
                     }
                 }
@@ -340,12 +356,16 @@
             break;
         }
         
-        objresult->add(objresult, obj);
+        objresult->add(objresult, NULL, obj, fklist, a);
+        
+        cxListClear(fklist);
         
         // load next row
         result->nextRow(result);
     }
     
+    cxListDestroy(fklist);
+    
     result->free(result);
     
     
--- a/dbutils/object.h	Wed Dec 11 21:53:31 2024 +0100
+++ b/dbutils/object.h	Fri Dec 13 11:24:55 2024 +0100
@@ -49,17 +49,6 @@
     DBUQuery *query;
 };
 
-typedef struct DBUObjectBuilderResult DBUObjectBuilderResult;
-struct DBUObjectBuilderResult {
-    void *userdata1;
-    void *userdata2;
-    int int1;
-    int int2;
-    DBUObject (*create)(DBUObjectBuilderResult *result, DBUClass *type, const CxAllocator *a);
-    int (*add)(DBUObjectBuilderResult *result, void *obj);
-    void (*free)(DBUObjectBuilderResult *result);
-};
-
 typedef struct DBUBuilderObjCache {
     DBUClass *class;
     void *obj;
@@ -86,7 +75,7 @@
      * 
      * Subqueries, that should be executed for all main result rows.
      * Before a subquery is executed, the first parameter is set to the
-     * foreign key, that is stored in the main table.
+     * primary key of the main table.
      */
     CxMap *subQueries;
     
@@ -101,7 +90,7 @@
     /*
      * result builder
      */
-    DBUObjectBuilderResult *result;
+    DBUObjectResult *result;
     
     /*
      * object cache
@@ -113,9 +102,9 @@
     CxMap *cache;
 };
 
-int dbuObjectExec(DBUObjectBuilder *builder, DBUObjectBuilderResult *objresult);
+int dbuObjectExec(DBUObjectBuilder *builder, DBUObjectResult *objresult);
 
-int dbuObjectExecuteQuery(DBUObjectBuilder *builder, DBUQuery *query, DBUClass *type, DBUObjectBuilderResult *objresult);
+int dbuObjectExecuteQuery(DBUObjectBuilder *builder, DBUQuery *query, DBUClass *type, DBUObjectResult *objresult);
 
 #ifdef __cplusplus
 }
--- a/test/main.c	Wed Dec 11 21:53:31 2024 +0100
+++ b/test/main.c	Fri Dec 13 11:24:55 2024 +0100
@@ -50,12 +50,16 @@
 "zip text, "
 "city text);";
 
+const char *sql_create_table_role =
+"create table if not exists Role ("
+"role_id integer primary key autoincrement, "
+"person_id integer, "
+"name text);";
+
 
 const char *sql_check_table =
 "select person_id from Person limit 1;";
 
-
-
 const char *sql_create_test_data1 = 
 "insert into address (street, zip, city) "
 "values "
@@ -65,8 +69,16 @@
 const char *sql_create_test_data2 = 
 "insert into person (name, email, age, iscustomer, hash, address_id) "
 "values "
-"('alice', 'alice@example.com', 30, 1, 123456789, (select address_id from address where street = 'street 1')), "
-"('bob', 'bob@example.com', 25, 0, 987654321,  (select address_id from address where street = 'street 2'));";
+"('alice', 'alice@example.com', 30, 0, 123456789, (select address_id from address where street = 'street 1')), "
+"('bob', 'bob@example.com', 25, 1, 987654321,  (select address_id from address where street = 'street 2'));";
+
+const char *sql_create_test_data3 = 
+"insert into role (person_id, name) "
+"values "
+"(1, 'finance'), "
+"(1, 'dev'), "
+"(1, 'manager'), "
+"(2, 'extern');";
 
 typedef struct Address {
     int64_t address_id;
@@ -86,8 +98,15 @@
     uint64_t hash;
     
     Address *address;
+    
+    CxList *roles;
 } Person;
 
+typedef struct Role {
+    int64_t role_id;
+    int64_t person_id;
+    cxmutstr name;
+} Role;
 
 
 static int create_test_data(sqlite3 *db);
@@ -102,6 +121,8 @@
     dbuClassAdd(address, Address, zip);
     dbuClassAdd(address, Address, city);
     
+    DBUClass *role = dbuRegisterClass(ctx, "role", Role, role_id);
+    
     DBUClass *person = dbuRegisterClass(ctx, "person", Person, person_id);
     dbuClassAdd(person, Person, name);
     dbuClassAdd(person, Person, email);
@@ -109,7 +130,10 @@
     dbuClassAdd(person, Person, iscustomer);
     dbuClassAdd(person, Person, hash);
     dbuClassAddObj(person, "address_id", offsetof(Person, address), address);
+    dbuClassAddCxLinkedList(person, "person_id", offsetof(Person, roles), role);
     
+    dbuClassAddForeignKey(role, Role, person_id, person);
+    dbuClassAdd(role, Role, name);
     
     // Open or create the database
     sqlite3 *db;
@@ -128,7 +152,11 @@
     DBUQuery *query = conn->createQuery(conn, NULL);
     dbuQuerySetSQL(query, "select p.*, a.address_id as [__address__address_id], a.street, a.zip, a.city from Person p inner join Address a on p.address_id = a.address_id;");
     
+    DBUQuery *roleQuery = conn->createQuery(conn, NULL);
+    dbuQuerySetSQL(roleQuery, "select * from role;");
+    
     DBUObjectBuilder *builder = dbuObjectBuilder(person, query, cxDefaultAllocator);
+    dbuObjectBuilderAddAdditionalQuery(builder, role, roleQuery);
     CxList *persons = dbuObjectBuilderGetList(builder);
     if(persons) {
         CxIterator i = cxListIterator(persons);
@@ -147,7 +175,13 @@
 
 static int create_test_data(sqlite3 *db) {
     char *err_msg = NULL;
-    int rc = sqlite3_exec(db, sql_create_table_person, 0, 0, &err_msg);
+    sqlite3_stmt *stmt;
+    int rc = sqlite3_prepare_v2(db, sql_check_table, -1, &stmt, 0);
+    if(rc == SQLITE_OK) {
+        return 0;
+    }
+    
+    rc = sqlite3_exec(db, sql_create_table_person, 0, 0, &err_msg);
     if(rc != SQLITE_OK) {
         fprintf(stderr, "SQLite error: %s\n", err_msg);
         sqlite3_free(err_msg);
@@ -163,25 +197,14 @@
         return 1;
     }
     
-    
-    sqlite3_stmt *stmt;
-    rc = sqlite3_prepare_v2(db, sql_check_table, -1, &stmt, 0);
+    rc = sqlite3_exec(db, sql_create_table_role, 0, 0, &err_msg);
     if(rc != SQLITE_OK) {
-        fprintf(stderr, "SQLite error: %s\n", sqlite3_errmsg(db));
+        fprintf(stderr, "SQLite error: %s\n", err_msg);
+        sqlite3_free(err_msg);
         sqlite3_close(db);
         return 1;
     }
     
-    int exists = 0;
-    if(sqlite3_step(stmt) == SQLITE_ROW) {
-        exists = 1;
-    }
-    sqlite3_finalize(stmt);
-    
-    if(exists) {
-        return 0;
-    }
-    
     rc = sqlite3_exec(db, sql_create_test_data1, 0, 0, &err_msg);
     if(rc != SQLITE_OK) {
         fprintf(stderr, "SQLite error: %s\n", err_msg);
@@ -198,5 +221,13 @@
         return 1;
     }
     
+    rc = sqlite3_exec(db, sql_create_test_data3, 0, 0, &err_msg);
+    if(rc != SQLITE_OK) {
+        fprintf(stderr, "SQLite error: %s\n", err_msg);
+        sqlite3_free(err_msg);
+        sqlite3_close(db);
+        return 1;
+    }
+    
     return 0;
 }

mercurial