test/database.c

Tue, 09 Dec 2025 18:24:48 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Tue, 09 Dec 2025 18:24:48 +0100
changeset 26
dc36aa437249
parent 25
0bb91d1f9bba
permissions
-rw-r--r--

implement json primitives serialization

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2025 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
 * POSSIBLIITY OF SUCH DAMAGE.
 */

#include "database.h"

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>

#include <cx/buffer.h>
#include <cx/streams.h>
#include <cx/mempool.h>

#define TEST_DB        "test.db"
#define TEST_DATA_FILE "testdata.sql"


// create test.db and execute testdata script
int init_test_db(void) {
    sqlite3 *db;
    char *err_msg = NULL;
    
    FILE *f = fopen(TEST_DATA_FILE, "r");
    if(!f) {
        fprintf(stderr, "Cannot open test data file %s: %s\n", TEST_DATA_FILE, strerror(errno));
    }
    
    if(unlink(TEST_DB)) {
        if(errno != ENOENT) {
            fprintf(stderr, "Cannot unlink %s: %s\n", TEST_DB, strerror(errno));
            fclose(f);
            return 1;
        }
    }
    if(sqlite3_open(TEST_DB, &db) != SQLITE_OK) {
        fprintf(stderr, "Cannot open database %s: %s\n", TEST_DB, sqlite3_errmsg(db));
        fclose(f);
        return 1;
    }
    
    CxBuffer *buf = cxBufferCreate(NULL, 2048, NULL, CX_BUFFER_AUTO_EXTEND | CX_BUFFER_FREE_CONTENTS);
    cx_stream_copy(f, buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite);
    int err = 0;
    if(buf->size > 0) {
        cxBufferTerminate(buf);
        
        if(sqlite3_exec(db, buf->space, 0, 0, &err_msg) != SQLITE_OK) {
            fprintf(stderr, "SQL error: %s\n", err_msg);
            sqlite3_free(err_msg);
            err = 1;
        }
    } else {
        fprintf(stderr, "Error: no file content\n");
        err = 1;
    }
    cxBufferFree(buf);
    sqlite3_close(db);
    
    return err;
}



static DBUContext *ctx;
static DBUConnection *conn;

static DBUClass *address;
static DBUClass *person;
static DBUClass *role;

typedef struct Address {
    int64_t address_id;
    
    cxmutstr street;
    cxmutstr zip;
    cxmutstr city;
} Address;

typedef struct Person {
    int64_t person_id;
    
    cxmutstr name;
    cxmutstr email;
    int      age;
    bool     iscustomer;
    uint64_t hash;
    
    Address *address;
    
    CxList *roles;
} Person;

typedef struct Role {
    int64_t role_id;
    int64_t person_id;
    cxmutstr name;
} Role;

int init_db_tests(void) {
    ctx = dbuContextCreate();
    
    address = dbuRegisterClass(ctx, "address", Address, address_id);
    dbuClassAdd(address, Address, street);
    dbuClassAdd(address, Address, zip);
    dbuClassAdd(address, Address, city);
    
    role = dbuRegisterClass(ctx, "role", Role, role_id);
    
    person = dbuRegisterClass(ctx, "person", Person, person_id);
    dbuClassAdd(person, Person, name);
    dbuClassAdd(person, Person, email);
    dbuClassAdd(person, Person, age);
    dbuClassAdd(person, Person, iscustomer);
    dbuClassAdd(person, Person, hash);
    dbuClassAddObj(person, "address_id", offsetof(Person, address), address);
    dbuClassAddCxLinkedList(person, "person_id", offsetof(Person, roles), role);
    
    dbuClassAddForeignKey(role, Role, person_id, person);
    dbuClassAdd(role, Role, name);
    
    return 0;
}

void cleanup_db_tests(void) {
    if(conn) {
        dbuConnectionFree(conn);
    }
    dbuContextFree(ctx);
}

CX_TEST(testSqliteConnection) {
    CX_TEST_DO {
        conn = dbuSQLiteConnection(TEST_DB);
        CX_TEST_ASSERT(conn);
        CX_TEST_ASSERT(dbuConnectionIsActive(conn));
    }
}

CX_TEST(testConnectionExec) {
    CX_TEST_DO {
        CX_TEST_ASSERT(conn);
        
        int t1 = dbuConnectionExec(conn, "create table ExecTest1(a int);");
        CX_TEST_ASSERT(t1 == 0);
        
        int t2 = dbuConnectionExec(conn, "insert into ExecTest1(a) values (1);");
        CX_TEST_ASSERT(t2 == 0);
        
        int t3 = dbuConnectionExec(conn, "drop table ExecTest1;");
        CX_TEST_ASSERT(t3 == 0);
        
        int fail = dbuConnectionExec(conn, "select * from Fail;");
        CX_TEST_ASSERT(fail != 0);
    }
}

CX_TEST(testSingleValueQuery) {
    CX_TEST_DO {
        CX_TEST_ASSERT(conn);
        
        DBUQuery *q = dbuConnectionQuery(conn, NULL);
        CX_TEST_ASSERT(q);
        CX_TEST_ASSERT(dbuQuerySetSQL(q, "select 12;") == 0);
        CX_TEST_ASSERT(dbuQueryExec(q) == 0);
        
        DBUResult *r = dbuQueryGetResult(q);
        CX_TEST_ASSERT(r);
        int value;
        CX_TEST_ASSERT(dbuResultAsValue(r, &value) == 0);
        CX_TEST_ASSERT(value == 12);
        
        dbuQueryFree(q);
        
    }
}

CX_TEST(testSqlExec) {
    CX_TEST_DO {
        CX_TEST_ASSERT(conn);
        
        DBUResult *r = dbuSqlExec(conn, NULL, "select email from person where name = 'alice';");
        CX_TEST_ASSERT(r);
        cxmutstr value;
        CX_TEST_ASSERT(dbuResultAsValue(r, &value) == 0);
        CX_TEST_ASSERT(!cx_strcmp(value, "alice@example.com"));
        free(value.ptr);
    }
}

CX_TEST(testSqlExecParam) {
    CX_TEST_DO {
        CX_TEST_ASSERT(conn);
        
        DBUResult *r = dbuSqlExecParam(conn, NULL, "select email from person where name = ?;", "alice");
        CX_TEST_ASSERT(r);
        cxmutstr value;
        CX_TEST_ASSERT(dbuResultAsValue(r, &value) == 0);
        CX_TEST_ASSERT(!cx_strcmp(value, "alice@example.com"));
        free(value.ptr);
        
        r = dbuSqlExecParam(conn, NULL, "select name from person where hash = ?;", 987654321);
        CX_TEST_ASSERT(r);
        CX_TEST_ASSERT(dbuResultAsValue(r, &value) == 0);
        CX_TEST_ASSERT(!cx_strcmp(value, "bob"));
        free(value.ptr);
    }
}

CX_TEST(testSingleTableQuery) {
    CxMempool *mp = cxMempoolCreateSimple(64);
    
    CX_TEST_DO {
        DBUQuery *query = dbuQueryCreate(conn, mp->allocator, "select * from address order by street;");
        DBUObjectBuilder *builder = dbuObjectBuilder(address, query, mp->allocator);
        CxList *adr = dbuObjectBuilderGetList(builder);
        
        CX_TEST_ASSERT(adr);
        CX_TEST_ASSERT(cxListSize(adr) == 2);
        
        Address *a0 = cxListAt(adr, 0);
        Address *a1 = cxListAt(adr, 1);
        CX_TEST_ASSERT(a0);
        CX_TEST_ASSERT(a1);
        CX_TEST_ASSERT(!cx_strcmp(a0->street, "street 1"));
        CX_TEST_ASSERT(!cx_strcmp(a1->street, "street 2"));
        CX_TEST_ASSERT(!cx_strcmp(a0->zip, "12343"));
        CX_TEST_ASSERT(!cx_strcmp(a1->zip, "23456"));
        CX_TEST_ASSERT(!cx_strcmp(a0->city, "city 17"));
        CX_TEST_ASSERT(!cx_strcmp(a1->city, "city 18"));
        
        dbuObjectBuilderDestroy(builder);
    }
    
    cxMempoolFree(mp);
}

mercurial