add new object builder API

Mon, 09 Dec 2024 23:47:52 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 09 Dec 2024 23:47:52 +0100
changeset 4
1908c8b1599f
parent 3
69ea9040d896
child 5
f0c66b2139ea

add new object builder API

dbutils/class.c file | annotate | diff | comparison | revisions
dbutils/dbutils/db.h 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
dbutils/sqlite.c file | annotate | diff | comparison | revisions
dbutils/sqlite.h file | annotate | diff | comparison | revisions
test/main.c file | annotate | diff | comparison | revisions
--- a/dbutils/class.c	Sun Dec 08 15:46:03 2024 +0100
+++ b/dbutils/class.c	Mon Dec 09 23:47:52 2024 +0100
@@ -48,6 +48,8 @@
     cls->name = cx_strdup(cx_str(name));
     cls->fields = cxHashMapCreateSimple(CX_STORE_POINTERS);
     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;
     
     return cls;
 }
@@ -60,9 +62,16 @@
 }
 
 void dbuClassAddField(DBUClass *cls, const char *name, DBUField *field) {
+    free(field->name.ptr);
+    field->name = cx_strdup(cx_str(name));
     cxMapPut(cls->fields, name, field);
 }
 
+void dbuClassAddObjField(DBUClass *cls, const char *name, DBUField *field, DBUClass *foreign_cls) {
+    free(field->name.ptr);
+    field->name = cx_strdup(cx_str(name));
+    cxMapPut(cls->obj_fields, foreign_cls->name, field);
+}
 
 
 void dbuClassSetPrimaryKeyInt32(DBUClass *cls, const char *column_name, off_t offset) {
--- a/dbutils/dbutils/db.h	Sun Dec 08 15:46:03 2024 +0100
+++ b/dbutils/dbutils/db.h	Mon Dec 09 23:47:52 2024 +0100
@@ -71,9 +71,11 @@
     int (*setParamBytes)(DBUQuery *q, int index, void *bytes, int len);
     int (*exec)(DBUQuery *q);
     DBUResult* (*getResult)(DBUQuery *q);
+    int (*reset)(DBUQuery *q);
     void (*free)(DBUQuery *q);
     DBUConnection *connection;
     const CxAllocator *allocator;
+    int ref;
 };
 
 struct DBUResult {
@@ -107,6 +109,17 @@
 void dbuQueryFree(DBUQuery *q);
 
 
+
+DBUObjectBuilder* dbuObjectBuilder(DBUClass *type, DBUQuery *query, const CxAllocator *a);
+int dbuObjectBuilderAddSubquery(DBUObjectBuilder *builder, DBUClass *type, DBUQuery *subquery);
+int dbuObjectBuilderAddAdditionalQuery(DBUObjectBuilder *builder, DBUClass *type, DBUQuery *query);
+CxList* dbuObjectBuilderGetList(DBUObjectBuilder *builder);
+CxList* dbuObjectBuilderGetValueList(DBUObjectBuilder *builder);
+int dbuObjectBuilderGetArray(DBUObjectBuilder *builder, void **array, size_t *size);
+void dbuObjectBuilderDestroy(DBUObjectBuilder *builder);
+
+
+// TODO: remove
 CxList* dbuQuerySingleType(DBUContext *ctx, DBUQuery *query, const char *type);
 
 #ifdef __cplusplus
--- a/dbutils/dbutils/dbutils.h	Sun Dec 08 15:46:03 2024 +0100
+++ b/dbutils/dbutils/dbutils.h	Mon Dec 09 23:47:52 2024 +0100
@@ -47,7 +47,7 @@
 typedef struct DBUClass   DBUClass;
 typedef struct DBUField   DBUField;
 
-typedef struct DBUObjectQuery DBUObjectQuery;
+typedef struct DBUObjectBuilder DBUObjectBuilder;
 
 typedef char* DBUObject;
 
@@ -64,6 +64,11 @@
 
 struct DBUClass {
     /*
+     * context, that contains this class
+     */
+    DBUContext *context;
+    
+    /*
      * class/table name
      */
     cxmutstr name;
@@ -79,7 +84,7 @@
     DBUField *primary_key;
     
     /*
-     * class fields
+     * primitive fields
      * 
      * key: field name
      * value: DBUField*
@@ -87,6 +92,14 @@
     CxMap *fields;
     
     /*
+     * object fields
+     * 
+     * key: type name
+     * value: DBUField*
+     */
+    CxMap *obj_fields;
+    
+    /*
      * object size used for allocation, typically sizeof(some_struct)
      */
     size_t obj_size;
@@ -95,7 +108,7 @@
      * optional initializer function
      * 
      */
-    void* (*init)(const CxAllocator *a);
+    int (*init)(void *obj, const CxAllocator *a);
 };
 
 /*
@@ -103,6 +116,11 @@
  */
 struct DBUField {
     /*
+     * field name
+     */
+    cxmutstr name;
+    
+    /*
      * called, if the field is null (optional)
      */
     int (*initDefaultValue)(DBUField *f, const CxAllocator *a, DBUObject obj);
@@ -133,6 +151,11 @@
     int (*initDoubleValue)(DBUField *f, const CxAllocator *a, DBUObject obj, double value);
     
     /*
+     * set/add an child to obj
+     */
+    int (*initObjValue)(DBUField *f, const CxAllocator *a, DBUObject obj, void *child);
+    
+    /*
      * destructor (optional)
      * 
      * this callback must not free the DBUField* ptr
@@ -216,6 +239,7 @@
 void dbuClassSetPrimaryKeyCxMutStr(DBUClass *cls, const char *column_name, off_t offset);
 
 void dbuClassAddField(DBUClass *cls, const char *name, DBUField *field);
+void dbuClassAddObjField(DBUClass *cls, const char *name, DBUField *field, DBUClass *foreign_cls);
 
 #define dbuClassAdd(cls, type, member) \
     dbuClassAddWithName(cls, type, member, #member)
@@ -271,6 +295,14 @@
 void dbuClassAddFloatDef(DBUClass *cls, const char *name, off_t offset, float def);
 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 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);
+
 #ifdef __cplusplus
 }
 #endif
--- a/dbutils/field.c	Sun Dec 08 15:46:03 2024 +0100
+++ b/dbutils/field.c	Mon Dec 09 23:47:52 2024 +0100
@@ -560,6 +560,7 @@
         bool nonnull)
 {
     DBUObjLenField *field = malloc(sizeof(DBUObjLenField));
+    memset(field, 0, sizeof(DBUObjLenField));
     field->field.initDefaultValue = NULL;
     field->field.initValue = (DBUFieldInitFunc)init;
     field->field.nonnull = nonnull;
@@ -975,3 +976,45 @@
             false,
             (union DBUDefValue){ 0 });
 }
+
+
+// add complex members
+
+static int set_obj_ptr(DBUField *f, const CxAllocator *a, DBUObject obj, void *child) {
+    DBUOffsetField *field = (DBUOffsetField*)f;
+    *(void**)(obj+field->offset) = child;
+    return 0;
+}
+
+void dbuClassAddObj(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls) {
+    DBUOffsetField *field = malloc(sizeof(DBUOffsetField));
+    memset(field, 0, sizeof(DBUOffsetField));
+    field->field.initObjValue = set_obj_ptr;
+    field->offset = offset;
+    field->def.def = 0;
+    dbuClassAddObjField(cls, name, (DBUField*)field, 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 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/object.c	Sun Dec 08 15:46:03 2024 +0100
+++ b/dbutils/object.c	Mon Dec 09 23:47:52 2024 +0100
@@ -31,78 +31,251 @@
 #include <string.h>
 
 #include <cx/array_list.h>
+#include <cx/linked_list.h>
 #include <cx/hash_map.h>
 
 #include "object.h"
 
 
-CxList* dbuQuerySingleType(DBUContext *ctx, DBUQuery *query, const char *type) {
-    DBUClass *cls = cxMapGet(ctx->classes, type);
-    if(!cls) {
+
+
+
+DBUObjectBuilder* dbuObjectBuilder(DBUClass *type, DBUQuery *query, const CxAllocator *a) {
+    CxList *additionalQueries = cxLinkedListCreateSimple(sizeof(DBUBuilderQuery));
+    if(!additionalQueries) {
+        return NULL;
+    }
+    CxMap *subQueries = cxHashMapCreateSimple(CX_STORE_POINTERS);
+    if(!subQueries) {
+        cxListDestroy(additionalQueries);
+        return NULL;
+    }
+    
+    DBUObjectBuilder *builder = malloc(sizeof(DBUObjectBuilder));
+    if(!builder) {
+        cxListDestroy(additionalQueries);
+        cxMapDestroy(subQueries);
         return NULL;
     }
     
+    memset(builder, 0, sizeof(DBUObjectBuilder));
+    builder->allocator = a;
+    builder->ctx = type->context;
+    builder->mainQuery = query;
+    builder->resultType = type;
+    builder->subQueries = subQueries;
+    builder->additionalQueries = additionalQueries;
+    
+    return builder;
+}
+
+int dbuObjectBuilderAddSubquery(DBUObjectBuilder *builder, DBUClass *type, DBUQuery *subquery) {
+    return cxMapPut(builder->subQueries, type->name, subquery);
+}
+
+int dbuObjectBuilderAddAdditionalQuery(DBUObjectBuilder *builder, DBUClass *type, DBUQuery *query) {
+    return cxListAdd(builder->additionalQueries, &(DBUBuilderQuery){ type, query });
+}
+
+
+
+
+
+// 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 int list_result_add(DBUObjectBuilderResult *result, void *obj) {
+    return cxListAdd(result->userdata1, obj);
+}
+
+CxList* dbuObjectBuilderGetList(DBUObjectBuilder *builder) {
+    CxList *result_list = cxArrayListCreate(builder->allocator, NULL, CX_STORE_POINTERS, 8);
+    if(!result_list) {
+        return NULL;
+    }
+    // TODO: the class needs a obj destructor func, because we need to be able
+    //       to destroy partialy created lists
+    DBUObjectBuilderResult result = { 
+        .userdata1 = result_list,
+        .userdata2 = NULL,
+        .int1 = CX_STORE_POINTERS,
+        .create = list_result_create,
+        .add = list_result_add
+    };
+    if(dbuObjectBuilderGet(builder, &result)) {
+        cxListDestroy(result_list);
+        return NULL;
+    }
+    return result_list;
+}
+
+
+CxList* dbuObjectBuilderGetValueList(DBUObjectBuilder *builder) {
+    CxList *result_list = cxArrayListCreate(builder->allocator, NULL, builder->resultType->obj_size, 8);
+    if(!result_list) {
+        return NULL;
+    }
+    // TODO: the class needs a obj destructor func, because we need to be able
+    //       to destroy partialy created lists
+    DBUObjectBuilderResult result = { 
+        .userdata1 = result_list,
+        .userdata2 = malloc(builder->resultType->obj_size),
+        .int1 = builder->resultType->obj_size,
+        .create = list_result_create,
+        .add = list_result_add
+    };
+    if(!result.userdata2) {
+        cxListDestroy(result_list);
+        return NULL;
+    }
+    if(dbuObjectBuilderGet(builder, &result)) {
+        cxListDestroy(result_list);
+        return NULL;
+    }
+    return result_list;
+}
+
+int dbuObjectBuilderGetArray(DBUObjectBuilder *builder, void **array, size_t *size) {
+    
+}
+
+void dbuObjectBuilderDestroy(DBUObjectBuilder *builder) {
+    
+}
+
+
+
+/* ------------------------- Object Builder -----------------------------*/
+
+int dbuObjectBuilderGet(DBUObjectBuilder *builder, DBUObjectBuilderResult *objresult) {
+    DBUClass *cls = builder->resultType; // TODO: rename var
+    
+    // TODO: execute additional queries
+    
     // execute sql
+    DBUQuery *query = builder->mainQuery;
     if(query->exec(query)) {
         query->free(query);
-        return NULL;
+        return 1;
     }
     
     DBUResult *result = query->getResult(query);
     if(!result) {
-        return NULL;
+        return 1;
     }
     query->free(query);
     
-    // prepare list
-    CxList *list = cxArrayListCreateSimple(CX_STORE_POINTERS, 16);
-    if(!list) {
-        result->free(result);
-    }
-    
     // map result to class fields
     int numcols = result->numFields(result);
-    DBUFieldMapping *fields = calloc(numcols, sizeof(DBUFieldMapping));
-    if(!fields) {
+    DBUFieldMapping *field_mapping = calloc(numcols, sizeof(DBUFieldMapping));
+    if(!field_mapping) {
         result->free(result);
-        cxListDestroy(list);
-        return NULL;
+        return 1;
     }
-    int numfields = 0;
+    
+    bool is_main = true;
+    DBUClass *field_class = cls;
+    
     for(int i=0;i<numcols;i++) {
-        DBUField *field = cxMapGet(cls->fields, result->fieldName(result, i));
+        cxstring fieldname = cx_str(result->fieldName(result, i));
+        DBUField *field = NULL;
+        if(cx_strprefix(fieldname, CX_STR("__"))) {
+            // columns starting with __ are reserved for marking table names
+            // __<table>__<fieldname>
+            // __<table>   (value ignored)
+            //
+            // after a column is marked as the start of the new table
+            // all following columns will be treated as columns of this table
+            cxstring tabname = cx_strsubs(fieldname, 2);
+            DBUClass *fcls = cxMapGet(builder->ctx->classes, tabname);
+            if(fcls) {
+                field_class = fcls;
+                cxstring remaining = cx_strstr(tabname, CX_STR("__"));
+                if(remaining.length > 2) {
+                    field = cxMapGet(cls->fields, cx_strsubs(remaining, 2));
+                } else {
+                    field_mapping[i].cls = fcls;
+                }
+            }
+        } else {
+            field = cxMapGet(cls->fields, fieldname);
+        }
+        
         if(field) {
             DBUFieldMapping mapping;
+            mapping.cls = field_class;
             mapping.field = field;
-            mapping.index = i;
             mapping.type = result->fieldType(result, i);
-            fields[numfields++] = mapping;
+            mapping.is_main = is_main;
+            field_mapping[i] = mapping;
         }
     }
     
-    const CxAllocator *a = cxDefaultAllocator;
+    const CxAllocator *a = builder->allocator;
     
-    // get result
+     // get result
+    int err = 0;
     while(result->hasData(result)) {
-        void *obj = malloc(cls->obj_size);
+        // create main result obj
+        void *obj = objresult->create(objresult, cls, a);
         if(!obj) {
             break;
         }
         memset(obj, 0, sizeof(cls->obj_size));
+        if(cls->init) {
+            cls->init(obj, a);
+        }
         
-        for(int i=0;i<numfields;i++) {
-            DBUFieldMapping field = fields[i];
-            int isnull = result->isNull(result, field.index);
+        void *current_obj = obj;
+        void *child_obj = NULL;
+        DBUClass *child_cls = NULL;
+        DBUClass *current_cls = cls;
+        for(int i=0;i<numcols;i++) {
+            DBUFieldMapping field = field_mapping[i];
+            int isnull = result->isNull(result, i);
             
-            if(isnull) {
-                field.field->initDefaultValue(field.field, a, obj);
-            } else {
-                cxstring text = result->getText(result, field.index);
-                field.field->initValue(field.field, a, obj, text.ptr, text.length);
+            if(field.cls != current_cls) {
+                // check if an object of this class can be added to the main obj
+                DBUField *obj_field = cxMapGet(cls->obj_fields, field.cls->name);
+                if(obj_field && obj_field->initObjValue) {
+                    child_obj = objresult->create(objresult, field.cls, a);
+                    if(!child_obj) {
+                        err = 1;
+                        break;
+                    }
+
+                    if(field.cls->init) {
+                        field.cls->init(child_obj, a); // TODO: check return
+                    }
+                    
+                    obj_field->initObjValue(obj_field, a, obj, child_obj);
+                }
+            }
+            
+            DBUField *type_field = field.field;
+            if(type_field) {
+                if(isnull) {
+                    field.field->initDefaultValue(field.field, a, current_obj);
+                } else {
+                    cxstring text = result->getText(result, i);
+                    field.field->initValue(field.field, a, current_obj, text.ptr, text.length);
+                }
             }
         }
         
-        cxListAdd(list, obj);
+        if(err) {
+            break;
+        }
+        
+        objresult->add(objresult, obj);
         
         // load next row
         result->nextRow(result);
@@ -110,5 +283,6 @@
     
     result->free(result);
     
-    return list;
+    
+    return err;
 }
--- a/dbutils/object.h	Sun Dec 08 15:46:03 2024 +0100
+++ b/dbutils/object.h	Mon Dec 09 23:47:52 2024 +0100
@@ -37,11 +37,69 @@
 #endif
 
 typedef struct DBUFieldMapping {
+    DBUClass *cls;
     DBUField *field;
     DBUFieldType type;
-    int index;
+    bool is_main;
 } DBUFieldMapping;
 
+typedef struct DBUBuilderQuery DBUBuilderQuery;
+struct DBUBuilderQuery {
+    DBUClass *type;
+    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);
+};
+
+struct DBUObjectBuilder {
+    const CxAllocator *allocator;
+    
+    DBUContext *ctx;
+    
+    /*
+     * Main query, that us used for generating the result list.
+     */
+    DBUQuery *mainQuery;
+    
+    /*
+     * result type
+     */
+    DBUClass *resultType;
+    
+    /*
+     * key: type name
+     * value: DBUQuery*
+     * 
+     * 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.
+     */
+    CxMap *subQueries;
+    
+    /*
+     * value: DBUBuilderQuery
+     * 
+     * Additional queries are executed before the main query and the result
+     * objects are cached and later added as children to the main result.
+     */
+    CxList *additionalQueries;
+    
+    /*
+     * result builder
+     */
+    DBUObjectBuilderResult *result;
+};
+
+int dbuObjectBuilderGet(DBUObjectBuilder *builder, DBUObjectBuilderResult *objresult);
 
 #ifdef __cplusplus
 }
--- a/dbutils/sqlite.c	Sun Dec 08 15:46:03 2024 +0100
+++ b/dbutils/sqlite.c	Mon Dec 09 23:47:52 2024 +0100
@@ -86,9 +86,10 @@
     query->query.setParamBytes = dbuSQLiteQuerySetParamBytes;
     query->query.exec = dbuSQLiteQueryExec;
     query->query.getResult = dbuSQLiteQueryGetResult;
+    query->query.reset = dbuSQLiteQueryReset;
     query->query.free = dbuSQLiteQueryFree;
     query->db = connection->data;
-    query->ref = 1;
+    query->query.ref = 1;
     return (DBUQuery*)query;
 }
 
@@ -166,7 +167,7 @@
         return NULL;
     }
     memset(result, 0, sizeof(DBUSQLiteResult));
-    query->ref++;
+    query->query.ref++;
     result->query = query;
     query->result = result;
     
@@ -190,9 +191,15 @@
     return (DBUResult*)result;
 }
 
+int dbuSQLiteQueryReset(DBUQuery *q) {
+    DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+    // TODO
+    return 0;
+}
+
 void dbuSQLiteQueryFree(DBUQuery *q) {
     DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
-    if(--query->ref > 0) {
+    if(--query->query.ref > 0) {
         return;
     }
     sqlite3_finalize(query->stmt);
--- a/dbutils/sqlite.h	Sun Dec 08 15:46:03 2024 +0100
+++ b/dbutils/sqlite.h	Mon Dec 09 23:47:52 2024 +0100
@@ -52,7 +52,6 @@
     sqlite3_stmt *stmt;
     DBUSQLiteResult *result;
     int step;
-    int ref;
 };
 
 struct DBUSQLiteResult {
@@ -75,6 +74,7 @@
 int dbuSQLiteQuerySetParamBytes(DBUQuery *q, int index, void *bytes, int len);
 int dbuSQLiteQueryExec(DBUQuery *q);
 DBUResult* dbuSQLiteQueryGetResult(DBUQuery *q);
+int dbuSQLiteQueryReset(DBUQuery *q);
 void dbuSQLiteQueryFree(DBUQuery *q);
 
 int dbuSQLiteResultNumFields(DBUResult *result);
--- a/test/main.c	Sun Dec 08 15:46:03 2024 +0100
+++ b/test/main.c	Mon Dec 09 23:47:52 2024 +0100
@@ -93,7 +93,9 @@
     DBUConnection *conn = dbuSQLiteConnectionFromDB(db, true);
     DBUQuery *query = conn->createQuery(conn, NULL);
     dbuQuerySetSQL(query, "select * from Person;");
-    CxList *persons = dbuQuerySingleType(ctx, query, "person");
+    
+    DBUObjectBuilder *builder = dbuObjectBuilder(person, query, cxDefaultAllocator);
+    CxList *persons = dbuObjectBuilderGetList(builder);
     if(persons) {
         CxIterator i = cxListIterator(persons);
         cx_foreach(Person *, p, i) {

mercurial