dbutils/object.c

Wed, 11 Dec 2024 20:57:19 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 11 Dec 2024 20:57:19 +0100
changeset 5
f0c66b2139ea
parent 4
1908c8b1599f
child 6
d6981b56ab30
permissions
-rw-r--r--

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

mercurial