Wed, 11 Dec 2024 20:57:19 +0100
add support for child objects in the object builder
/* * 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 "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) { 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 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; 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(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; } } const CxAllocator *a = builder->allocator; // get result int err = 0; while(result->hasData(result)) { // 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); } void *current_obj = obj; void *child_obj = NULL; DBUClass *current_cls = cls; for(int i=0;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 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) { 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); } } } 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); } } } if(err) { break; } objresult->add(objresult, obj); // load next row result->nextRow(result); } result->free(result); return err; }