Mon, 16 Dec 2024 18:38:11 +0100
prepare for dense queries
/* * 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) { 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->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, 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)) { 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 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) { cxListDestroy(result_list); return NULL; } if(dbuObjectExec(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 -----------------------------*/ 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) { CxIterator i = cxListIterator(builder->additionalQueries); cx_foreach(DBUBuilderQuery *, q, i) { DBUObjectResult result = { .userdata1 = q, .userdata2 = builder, .int1 = CX_STORE_POINTERS, .create = result_create_obj, .add = add_to_parent }; ret = dbuObjectExecuteQuery(builder, q->query, q->type, &result, false); if(ret) { break; } } } if(builder->cache) { cxMapDestroy(builder->cache); } builder->cache = NULL; return ret; } void dbufkelm_free(DBUFK *elm) { free(elm->cache_key); } int dbuObjectExecuteQuery(DBUObjectBuilder *builder, DBUQuery *query, DBUClass *type, DBUObjectResult *objresult, bool dense) { DBUClass *cls = type; // TODO: execute additional queries // 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; 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; if(field == cls->primary_key) { main_pk_index = 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; 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; if(dense) { cxstring text = result->getText(result, main_pk_index); cxmutstr cache_key = cx_asprintf("%.*s::%.*s", (int)cls->name.length, cls->name.ptr, (int)text.length, text.ptr); if(!cache_key.ptr) { err = 1; break; } DBUBuilderObjCache *cached_obj = cxMapGet(builder->cache, cache_key); free(cache_key.ptr); if(cached_obj && cached_obj->class == cls) { obj = cached_obj->obj; addobj = false; skip_fields = end_main_fields; } } 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=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 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 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); // load next row result->nextRow(result); } cxListDestroy(fklist); result->free(result); return err; }