dbutils/object.c

Mon, 16 Dec 2024 18:38:11 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 16 Dec 2024 18:38:11 +0100
changeset 9
5785a693834c
parent 8
bd08116b8af4
child 10
80f9d007cb52
permissions
-rw-r--r--

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;
}

mercurial