Sun, 16 Feb 2025 15:14:45 +0100
add function for registering classes without PK
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2024 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <cx/array_list.h> #include <cx/linked_list.h> #include <cx/hash_map.h> #include <cx/printf.h> #include "object.h" 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) { cxListFree(additionalQueries); return NULL; } DBUObjectBuilder *builder = malloc(sizeof(DBUObjectBuilder)); if(!builder) { cxListFree(additionalQueries); cxMapFree(subQueries); return NULL; } memset(builder, 0, sizeof(DBUObjectBuilder)); builder->allocator = a; builder->ctx = type->context; builder->mainQuery = query; builder->resultType = type; builder->additionalQueries = additionalQueries; return builder; } void dbuObjectBuilderSetDenseResult(DBUObjectBuilder *builder, bool dense) { builder->denseResult = dense; } 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, NULL, query }); } // builder result constructors 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 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, DBUClass *type, void *obj, CxList *fk, const CxAllocator *a) { 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 DBUObjectResult result = { .userdata1 = result_list, .userdata2 = NULL, .int1 = CX_STORE_POINTERS, .create = result_create_obj, .add = list_result_add }; if(dbuObjectExec(builder, &result)) { cxListFree(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 DBUObjectResult result = { .userdata1 = result_list, .userdata2 = malloc(builder->resultType->obj_size), .int1 = builder->resultType->obj_size, .create = valuelist_result_create, .add = list_result_add }; if(!result.userdata2) { cxListFree(result_list); return NULL; } if(dbuObjectExec(builder, &result)) { cxListFree(result_list); return NULL; } return result_list; } int dbuObjectBuilderGetArray(DBUObjectBuilder *builder, void **array, size_t *size) { } void dbuObjectBuilderDestroy(DBUObjectBuilder *builder) { } /* ------------------------- Object Builder -----------------------------*/ static int add_to_parent(DBUObjectResult *result, DBUObject parent, DBUClass *type, void *obj, CxList *fklist, const CxAllocator *a) { DBUBuilderQuery *query = result->userdata1; DBUObjectBuilder *builder = result->userdata2; CxIterator i = cxListIterator(fklist); cx_foreach(DBUFK*, fk, i) { // check if the elm can be added DBUField *field = cxMapGet(fk->cls->obj_fields, type->name); if(!field) { continue; } // find cached object for all foreign keys DBUBuilderObjCache *parent_cached = cxMapGet(builder->cache, fk->cache_key); if(!parent_cached) { continue; } if(field->builder.add) { field->builder.add(&field->builder, parent_cached->obj, type, obj, NULL, a); } } return 0; } int dbuObjectExec(DBUObjectBuilder *builder, DBUObjectResult *objresult) { if(cxListSize(builder->additionalQueries) > 0 || builder->denseResult) { builder->cache = cxHashMapCreateSimple(sizeof(DBUBuilderObjCache)); if(!builder->cache) { return 1; } } int ret = dbuObjectExecuteQuery(builder, builder->mainQuery, builder->resultType, objresult, builder->denseResult); if(!ret) { // execute additional queries CxIterator i = cxListIterator(builder->additionalQueries); cx_foreach(DBUBuilderQuery *, q, i) { // default result builder for additional queries // uses malloc to allocate objects and adds checks all foreign keys DBUObjectResult result = { .userdata1 = q, .userdata2 = builder, .int1 = CX_STORE_POINTERS, .create = result_create_obj, .add = add_to_parent }; DBUObjectResult *objresult = &result; if(q->field && q->field->builder.add) { objresult = &q->field->builder; } ret = dbuObjectExecuteQuery(builder, q->query, q->type, objresult, false); if(ret) { break; } } } if(builder->cache) { cxMapFree(builder->cache); } builder->cache = NULL; return ret; } void dbufkelm_free(DBUFK *elm) { free(elm->cache_key); } static void* create_or_get_obj(CxAllocator *a, DBUObjectResult *objresult, DBUClass *type, int pk_index, DBUResult *result) { return NULL; } int dbuObjectExecuteQuery(DBUObjectBuilder *builder, DBUQuery *query, DBUClass *type, DBUObjectResult *objresult, bool dense) { DBUClass *cls = type; // execute sql if(query->exec(query)) { query->free(query); return 1; } DBUResult *result = query->getResult(query); if(!result) { return 1; } query->free(query); // map result to class fields int numcols = result->numFields(result); DBUFieldMapping *field_mapping = calloc(numcols, sizeof(DBUFieldMapping)); if(!field_mapping) { result->free(result); return 1; } bool is_main = true; DBUClass *field_class = cls; int main_pk_index = -1; int end_main_fields = numcols; // 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) DBUResultType result_types_static[16]; size_t result_types_capacity = 16; size_t result_types_size = 0; DBUResultType *result_types = result_types_static; CxArrayReallocator ar = cx_array_reallocator(NULL, result_types_static); DBUResultType mainResult = { .cls = cls }; cx_array_add(&result_types, &result_types_size, &result_types_capacity, sizeof(DBUResultType), &mainResult, &ar); for(int i=0;i<numcols;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); cxstring remaining = cx_strstr(tabname, CX_STR("__")); DBUClass *fcls = NULL; if(remaining.length > 2) { tabname.length = remaining.ptr - tabname.ptr; } fcls = cxMapGet(builder->ctx->classes, tabname); if(fcls) { if(fcls != field_class) { DBUResultType resultCls = { .cls = fcls }; cx_array_add(&result_types, &result_types_size, &result_types_capacity, sizeof(DBUResultType), &resultCls, &ar); } if(remaining.length > 2) { field = cxMapGet(fcls->fields, cx_strsubs(remaining, 2)); } field_mapping[i].cls = fcls; field_class = fcls; } } else { field = cxMapGet(field_class->fields, fieldname); } if(field) { DBUFieldMapping mapping; mapping.cls = field_class; mapping.field = field; mapping.type = result->fieldType(result, i); mapping.is_main = is_main; field_mapping[i] = mapping; if(field == cls->primary_key) { main_pk_index = i; } if(field == field_class->primary_key) { result_types[result_types_size-1].pk_col = i; } if(end_main_fields == numcols && field_class != cls) { end_main_fields = i; } } } if(main_pk_index < 0) { dense = false; } const CxAllocator *a = builder->allocator; // collect all foreign keys per row CxList *fklist = cxArrayListCreateSimple(sizeof(DBUFK), 4); fklist->collection.simple_destructor = (cx_destructor_func)dbufkelm_free; // get result int err = 0; while(result->hasData(result)) { // create main result obj bool addobj = true; void *obj = NULL; int skip_fields = 0; int cls_index = 0; if(dense) { cxstring text = result->getText(result, main_pk_index); cxmutstr prev_pk = result_types[0].prev_key; if(prev_pk.ptr && !cx_strcmp(text, cx_strcast(prev_pk))) { obj = result_types[0].prev_obj; addobj = false; if(1 < result_types_size) { skip_fields = result_types[1].pk_col; } } } if(!obj) { //printf("create obj [%s]\n", cls->name.ptr); obj = objresult->create(objresult, cls, a); memset(obj, 0, sizeof(cls->obj_size)); if(cls->init) { cls->init(obj, a); } } result_types[0].prev_obj = obj; if(!obj) { break; } void *current_obj = obj; void *child_obj = NULL; DBUClass *current_cls = cls; for(int i=skip_fields;i<numcols;i++) { DBUFieldMapping field = field_mapping[i]; int isnull = result->isNull(result, i); if(field.cls && field.cls != current_cls) { // following columns are for this new class cls_index++; //printf("new class[%d]: %s\n", cls_index, field.cls->name.ptr); if(dense) { // check if this object was already added if(cls_index < result_types_size) { cxstring text = result->getText(result, result_types[cls_index].pk_col); cxmutstr prev_pk = result_types[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) { i = result_types[cls_index+1].pk_col-1; // -1 because i++ by loop //printf("next col %d\n", i); continue; } } } } current_cls = field.cls; if(field.field) { // 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) { //printf("create child obj [%s]\n", field.cls->name.ptr); child_obj = objresult->create(objresult, field.cls, a); current_obj = child_obj; 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); } else { //printf("list field\n"); DBUField *parent_field = NULL; for(int c=0;c<result_types_size;c++) { DBUResultType parentType = result_types[c]; DBUField *f = cxMapGet(parentType.cls->obj_fields, field.cls->name); if(f && f->builder.create) { current_obj = f->builder.create(&f->builder, field.cls, a); void *parent_obj = result_types[c].prev_obj; f->builder.add(&f->builder, parent_obj, field.cls, current_obj, fklist, a); break; } } if(!current_obj) { //printf("ignore\n"); } } } result_types[cls_index].prev_obj = current_obj; } DBUField *type_field = field.field; if(type_field && current_obj) { if(isnull) { if(field.field->initDefaultValue) { field.field->initDefaultValue(field.field, a, current_obj); } } else { cxstring text = result->getText(result, i); //printf("getText(%d) = %s\n", i, text.ptr); field.field->initValue(field.field, a, current_obj, text.ptr, text.length); // if obj caching is enabled and the current field contains // the primary key, add this object to the cache if(builder->cache) { 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; } DBUBuilderObjCache cache_elm; cache_elm.class = current_cls; cache_elm.obj = current_obj; int r = cxMapPut(builder->cache, cache_key, &cache_elm); 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, field.field->foreignKeyClass->name.ptr, (int)text.length, text.ptr); DBUFK fkelm; fkelm.cache_key = fkey.ptr; fkelm.cls = field.field->foreignKeyClass; cxListAdd(fklist, &fkelm); } } } } } if(err) { break; } if(addobj) { objresult->add(objresult, NULL, cls, obj, fklist, a); } cxListClear(fklist); if(dense) { for(int i=0;i<result_types_size;i++) { cxstring text = result->getText(result, result_types[i].pk_col); free(result_types[i].prev_key.ptr); result_types[i].prev_key = cx_strdup(text); } } // load next row result->nextRow(result); //printf("next row\n"); } cxListFree(fklist); for(int i=0;i<result_types_size;i++) { free(result_types[i].prev_key.ptr); } if(result_types != result_types_static) { free(result_types); } result->free(result); return err; }