Mon, 09 Dec 2024 23:47:52 +0100
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) {