add DB abstraction layer

Sun, 08 Dec 2024 15:46:03 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 08 Dec 2024 15:46:03 +0100
changeset 3
69ea9040d896
parent 2
4c12c95f4846
child 4
1908c8b1599f

add DB abstraction layer

dbutils/Makefile file | annotate | diff | comparison | revisions
dbutils/db.c file | annotate | diff | comparison | revisions
dbutils/db.h file | annotate | diff | comparison | revisions
dbutils/dbutils/db.h file | annotate | diff | comparison | revisions
dbutils/dbutils/dbutils.h file | annotate | diff | comparison | revisions
dbutils/dbutils/sqlite.h file | annotate | diff | comparison | revisions
dbutils/object.c file | annotate | diff | comparison | revisions
dbutils/object.h file | annotate | diff | comparison | revisions
dbutils/sqlite.c file | annotate | diff | comparison | revisions
dbutils/sqlite.h file | annotate | diff | comparison | revisions
test/main.c file | annotate | diff | comparison | revisions
--- a/dbutils/Makefile	Sat Dec 07 23:10:03 2024 +0100
+++ b/dbutils/Makefile	Sun Dec 08 15:46:03 2024 +0100
@@ -33,6 +33,7 @@
 SRC += class.c
 SRC += field.c
 SRC += db.c
+SRC += object.c
 SRC += sqlite.c
 
 OBJ = $(SRC:%.c=../build/dbutils/%$(OBJ_EXT))
--- a/dbutils/db.c	Sat Dec 07 23:10:03 2024 +0100
+++ b/dbutils/db.c	Sun Dec 08 15:46:03 2024 +0100
@@ -27,4 +27,44 @@
  */
 
 #include "db.h"
+#include "dbutils/db.h"
 
+int dbuQuerySetSQL(DBUQuery *q, const char *sql) {
+    return q->setSQL(q, sql);
+}
+
+int dbuQuerySetParamString(DBUQuery *q, int index, cxstring str) {
+    return q->setParamString(q, index, str);
+}
+
+int dbuQuerySetParamInt(DBUQuery *q, int index, int i) {
+    return q->setParamInt(q, index, i);
+}
+
+int dbuQuerySetParamInt64(DBUQuery *q, int index, int64_t i) {
+    return q->setParamInt64(q, index, i);
+}
+
+int dbuQuerySetParamDouble(DBUQuery *q, int index, double d) {
+    return q->setParamDouble(q, index, d);
+}
+
+int dbuQuerySetParamNull(DBUQuery *q, int index) {
+    return q->setParamNull(q, index);
+}
+
+int dbuQuerySetParamBytes(DBUQuery *q, int index, void *bytes, int len) {
+    return q->setParamBytes(q, index, bytes, len);
+}
+
+int dbuQueryExec(DBUQuery *q) {
+    return q->exec(q);
+}
+
+DBUResult* dbuQueryGetResult(DBUQuery *q) {
+    return q->getResult(q);
+}
+
+void dbuQueryFree(DBUQuery *q) {
+    q->free(q);
+}
--- a/dbutils/db.h	Sat Dec 07 23:10:03 2024 +0100
+++ b/dbutils/db.h	Sun Dec 08 15:46:03 2024 +0100
@@ -30,15 +30,12 @@
 #define DBU_DB_H
 
 #include "dbutils/dbutils.h"
+#include "dbutils/db.h"
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-typedef struct DBUFieldMapping {
-    DBUField *field;
-    int result_index;
-} DBUFieldMapping;
 
 
 #ifdef __cplusplus
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dbutils/dbutils/db.h	Sun Dec 08 15:46:03 2024 +0100
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+#ifndef LIBDBU_DB_H
+#define LIBDBU_DB_H
+
+#include "dbutils.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DBUConnection DBUConnection;
+typedef struct DBUQuery      DBUQuery;
+typedef struct DBUResult     DBUResult;    
+
+typedef enum DBUFieldType DBUFieldType;
+
+enum DBUFieldType {
+    DBU_FIELD_NULL = 0,
+    DBU_FIELD_TEXT,
+    DBU_FIELD_INT,
+    DBU_FIELD_DOUBLE,
+    DBU_FIELD_BINARY
+};
+
+typedef struct DBUBytes {
+    const unsigned char *bytes;
+    size_t length;
+} DBUBytes;
+    
+struct DBUConnection {
+    DBUQuery* (*createQuery)(DBUConnection *connection, const CxAllocator *a);
+    int (*isActive)(DBUConnection *connection);
+    void (*free)(DBUConnection *connection);
+    void *data;
+};
+
+struct DBUQuery {
+    int (*setSQL)(DBUQuery *q, const char *sql);
+    int (*setParamString)(DBUQuery *q, int index, cxstring str);
+    int (*setParamInt)(DBUQuery *q, int index, int i);
+    int (*setParamInt64)(DBUQuery *q, int index, int64_t i);
+    int (*setParamDouble)(DBUQuery *q, int index, double d);
+    int (*setParamNull)(DBUQuery *q, int index);
+    int (*setParamBytes)(DBUQuery *q, int index, void *bytes, int len);
+    int (*exec)(DBUQuery *q);
+    DBUResult* (*getResult)(DBUQuery *q);
+    void (*free)(DBUQuery *q);
+    DBUConnection *connection;
+    const CxAllocator *allocator;
+};
+
+struct DBUResult {
+    int (*optional_numRows)(DBUResult *result);
+    int (*numFields)(DBUResult *result);
+    int (*hasData)(DBUResult *result);
+    int (*isOk)(DBUResult *result);
+    int (*nextRow)(DBUResult *result);
+    const char* (*fieldName)(DBUResult *result, int field);
+    DBUFieldType (*fieldType)(DBUResult *result, int field);
+    int (*isNull)(DBUResult *result, int field);
+    cxstring (*getText)(DBUResult *result, int field);
+    int64_t (*optional_getInt)(DBUResult *result, int field);
+    double (*optional_getDouble)(DBUResult *result, int field);
+    DBUBytes (*optional_getBinary)(DBUResult *result, int field);
+    void (*free)(DBUResult *result);
+    const CxAllocator *allocator;
+    int rowIndex;
+};
+
+
+int dbuQuerySetSQL(DBUQuery *q, const char *sql);
+int dbuQuerySetParamString(DBUQuery *q, int index, cxstring str);
+int dbuQuerySetParamInt(DBUQuery *q, int index, int i);
+int dbuQuerySetParamInt64(DBUQuery *q, int index, int64_t i);
+int dbuQuerySetParamDouble(DBUQuery *q, int index, double d);
+int dbuQuerySetParamNull(DBUQuery *q, int index);
+int dbuQuerySetParamBytes(DBUQuery *q, int index, void *bytes, int len);
+int dbuQueryExec(DBUQuery *q);
+DBUResult* dbuQueryGetResult(DBUQuery *q);
+void dbuQueryFree(DBUQuery *q);
+
+
+CxList* dbuQuerySingleType(DBUContext *ctx, DBUQuery *query, const char *type);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LIBDBU_DB_H */
+
--- a/dbutils/dbutils/dbutils.h	Sat Dec 07 23:10:03 2024 +0100
+++ b/dbutils/dbutils/dbutils.h	Sun Dec 08 15:46:03 2024 +0100
@@ -47,6 +47,8 @@
 typedef struct DBUClass   DBUClass;
 typedef struct DBUField   DBUField;
 
+typedef struct DBUObjectQuery DBUObjectQuery;
+
 typedef char* DBUObject;
 
 typedef int(*DBUFieldDefInitFunc)(DBUField *f, const CxAllocator *a, DBUObject obj);
--- a/dbutils/dbutils/sqlite.h	Sat Dec 07 23:10:03 2024 +0100
+++ b/dbutils/dbutils/sqlite.h	Sun Dec 08 15:46:03 2024 +0100
@@ -30,15 +30,15 @@
 #define LIBDBU_SQLITE_H
 
 #include "dbutils.h"
-
+#include "db.h"
 #include <sqlite3.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-CxList *dbuSQLiteQuerySingleTable(DBUContext *ctx, sqlite3 *db, const char *type, const char *sql);
-
+DBUConnection* dbuSQLiteConnection(const char *filename);
+DBUConnection* dbuSQLiteConnectionFromDB(sqlite3 *db, bool autoclose);
 
 #ifdef __cplusplus
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dbutils/object.c	Sun Dec 08 15:46:03 2024 +0100
@@ -0,0 +1,114 @@
+/*
+ * 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/hash_map.h>
+
+#include "object.h"
+
+
+CxList* dbuQuerySingleType(DBUContext *ctx, DBUQuery *query, const char *type) {
+    DBUClass *cls = cxMapGet(ctx->classes, type);
+    if(!cls) {
+        return NULL;
+    }
+    
+    // execute sql
+    if(query->exec(query)) {
+        query->free(query);
+        return NULL;
+    }
+    
+    DBUResult *result = query->getResult(query);
+    if(!result) {
+        return NULL;
+    }
+    query->free(query);
+    
+    // prepare list
+    CxList *list = cxArrayListCreateSimple(CX_STORE_POINTERS, 16);
+    if(!list) {
+        result->free(result);
+    }
+    
+    // map result to class fields
+    int numcols = result->numFields(result);
+    DBUFieldMapping *fields = calloc(numcols, sizeof(DBUFieldMapping));
+    if(!fields) {
+        result->free(result);
+        cxListDestroy(list);
+        return NULL;
+    }
+    int numfields = 0;
+    for(int i=0;i<numcols;i++) {
+        DBUField *field = cxMapGet(cls->fields, result->fieldName(result, i));
+        if(field) {
+            DBUFieldMapping mapping;
+            mapping.field = field;
+            mapping.index = i;
+            mapping.type = result->fieldType(result, i);
+            fields[numfields++] = mapping;
+        }
+    }
+    
+    const CxAllocator *a = cxDefaultAllocator;
+    
+    // get result
+    while(result->hasData(result)) {
+        void *obj = malloc(cls->obj_size);
+        if(!obj) {
+            break;
+        }
+        memset(obj, 0, sizeof(cls->obj_size));
+        
+        for(int i=0;i<numfields;i++) {
+            DBUFieldMapping field = fields[i];
+            int isnull = result->isNull(result, field.index);
+            
+            if(isnull) {
+                field.field->initDefaultValue(field.field, a, obj);
+            } else {
+                cxstring text = result->getText(result, field.index);
+                field.field->initValue(field.field, a, obj, text.ptr, text.length);
+            }
+        }
+        
+        cxListAdd(list, obj);
+        
+        // load next row
+        result->nextRow(result);
+    }
+    
+    result->free(result);
+    
+    return list;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dbutils/object.h	Sun Dec 08 15:46:03 2024 +0100
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#ifndef DBU_OBJECT_H
+#define DBU_OBJECT_H
+
+#include "dbutils/dbutils.h"
+#include "dbutils/db.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct DBUFieldMapping {
+    DBUField *field;
+    DBUFieldType type;
+    int index;
+} DBUFieldMapping;
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* DBU_OBJECT_H */
+
--- a/dbutils/sqlite.c	Sat Dec 07 23:10:03 2024 +0100
+++ b/dbutils/sqlite.c	Sun Dec 08 15:46:03 2024 +0100
@@ -36,77 +36,248 @@
 #include <cx/array_list.h>
 
 
-CxList *dbuSQLiteQuerySingleTable(DBUContext *ctx, sqlite3 *db, const char *type, const char *sql) {
-    DBUClass *cls = cxMapGet(ctx->classes, type);
-    if(!cls) {
-        return NULL;
-    }
-    
-    // execute sql
-    sqlite3_stmt *stmt;
-    int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
+
+DBUConnection* dbuSQLiteConnection(const char *filename) {
+    sqlite3 *db;
+    int rc = sqlite3_open(filename, &db);
     if(rc != SQLITE_OK) {
         return NULL;
     }
     
-    // prepare list
-    CxList *list = cxArrayListCreateSimple(CX_STORE_POINTERS, 16);
-    if(!list) {
-        sqlite3_finalize(stmt);
-        return NULL;
+    DBUConnection *conn = dbuSQLiteConnectionFromDB(db, true);
+    if(!conn) {
+        sqlite3_close(db);
     }
-    
-    // map result to class fields
-    int numcols = sqlite3_column_count(stmt);
-    DBUFieldMapping *fields = calloc(numcols, sizeof(DBUFieldMapping));
-    if(!fields) {
-        sqlite3_finalize(stmt);
-        cxListDestroy(list);
+    return conn;
+}
+
+DBUConnection* dbuSQLiteConnectionFromDB(sqlite3 *db, bool autoclose) {
+    DBUSQLiteConnection *connection = malloc(sizeof(DBUSQLiteConnection));
+    if(!connection) {
         return NULL;
     }
-    int numfields = 0;
-    for(int i=0;i<numcols;i++) {
-        DBUField *field = cxMapGet(cls->fields, sqlite3_column_name(stmt, i));
-        if(field) {
-            DBUFieldMapping mapping;
-            mapping.field = field;
-            mapping.result_index = i;
-            fields[numfields++] = mapping;
-        }
-    }
-    
-    const CxAllocator *a = cxDefaultAllocator;
+    memset(connection, 0, sizeof(DBUSQLiteConnection));
+    connection->connection.createQuery = dbuSQLiteConnCreateQuery;
+    connection->connection.isActive = dbuSQLiteConnIsActive;
+    connection->connection.free = dbuSQLiteConnFree;
+    connection->connection.data = db;
+    connection->db = db;
+    connection->autoclose = autoclose;
     
-    // get result
-    while(sqlite3_step(stmt) == SQLITE_ROW) {
-        void *obj = malloc(cls->obj_size);
-        if(!obj) {
-            break;
-        }
-        memset(obj, 0, sizeof(cls->obj_size));
-        
-        for(int i=0;i<numfields;i++) {
-            DBUFieldMapping field = fields[i];
-            int isnull = sqlite3_column_type(stmt, field.result_index) == SQLITE_NULL;
-            
-            if(isnull) {
-                field.field->initDefaultValue(field.field, a, obj);
-            } else {
-                const char *text = (const char *)sqlite3_column_text(stmt, i);
-                int length = 0;
-                if(field.field->query_length) {
-                    length = sqlite3_column_bytes(stmt, field.result_index);
-                }
-                
-                field.field->initValue(field.field, a, obj, text, length);
-            }
-        }
-        
-        cxListAdd(list, obj);
+    return (DBUConnection*)connection;
+}
+
+/* ------------------- SQLite Connection Implementation  -------------------*/
+
+DBUQuery* dbuSQLiteConnCreateQuery(DBUConnection *connection, const CxAllocator *a) {
+    DBUSQLiteQuery *query = malloc(sizeof(DBUSQLiteQuery));
+    if(!query) {
+        return NULL;
     }
-    sqlite3_finalize(stmt);
-    return list;
+    memset(query, 0, sizeof(DBUSQLiteQuery));
+    query->query.allocator = a;
+    query->query.connection = connection;
+    query->query.setSQL = dbuSQLiteQuerySetSQL;
+    query->query.setParamString = dbuSQLiteQuerySetParamString;
+    query->query.setParamInt = dbuSQLiteQuerySetParamInt;
+    query->query.setParamInt64 = dbuSQLiteQuerySetParamInt64;
+    query->query.setParamDouble = dbuSQLiteQuerySetParamDouble;
+    query->query.setParamNull = dbuSQLiteQuerySetParamNull;
+    query->query.setParamBytes = dbuSQLiteQuerySetParamBytes;
+    query->query.exec = dbuSQLiteQueryExec;
+    query->query.getResult = dbuSQLiteQueryGetResult;
+    query->query.free = dbuSQLiteQueryFree;
+    query->db = connection->data;
+    query->ref = 1;
+    return (DBUQuery*)query;
+}
+
+int dbuSQLiteConnIsActive(DBUConnection *connection) {
+    return 1;
+}
+
+void dbuSQLiteConnFree(DBUConnection *connection) {
+    DBUSQLiteConnection *sqlite = (DBUSQLiteConnection*)connection;
+    if(sqlite->autoclose) {
+        sqlite3_close(sqlite->db);
+    }
+    free(connection);
 }
 
 
+/* --------------------- SQLite Query Implementation  ---------------------*/
+
+int dbuSQLiteQuerySetSQL(DBUQuery *q, const char *sql) {
+    DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+    if(query->stmt) {
+        return 1;
+    }
+    return sqlite3_prepare_v2(query->db, sql, -1, &query->stmt, 0) != SQLITE_OK;
+}
+
+int dbuSQLiteQuerySetParamString(DBUQuery *q, int index, cxstring str) {
+    DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+    return sqlite3_bind_text(query->stmt, index, str.ptr, str.length, NULL);
+}
+
+int dbuSQLiteQuerySetParamInt(DBUQuery *q, int index, int i) {
+    DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+    return sqlite3_bind_int(query->stmt, index, i);
+}
+
+int dbuSQLiteQuerySetParamInt64(DBUQuery *q, int index, int64_t i) {
+    DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+    return sqlite3_bind_int64(query->stmt, index, i);
+}
+
+int dbuSQLiteQuerySetParamDouble(DBUQuery *q, int index, double d) {
+    DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+    return sqlite3_bind_double(query->stmt, index, d);
+}
+
+int dbuSQLiteQuerySetParamNull(DBUQuery *q, int index) {
+    DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+    return sqlite3_bind_null(query->stmt, index);
+}
+
+int dbuSQLiteQuerySetParamBytes(DBUQuery *q, int index, void *bytes, int len) {
+    DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+    return sqlite3_bind_blob(query->stmt, index, bytes, len, NULL);
+}
+
+int dbuSQLiteQueryExec(DBUQuery *q) {
+    DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+    int step = sqlite3_step(query->stmt);
+    if(step == SQLITE_ROW || step == SQLITE_DONE) {
+        query->step = step;
+        return 0;
+    }
+    return 1;
+}
+
+DBUResult* dbuSQLiteQueryGetResult(DBUQuery *q) {
+    DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+    if(query->result) {
+        return (DBUResult*)query->result;
+    }
+    
+    DBUSQLiteResult *result = malloc(sizeof(DBUSQLiteResult));
+    if(!result) {
+        return NULL;
+    }
+    memset(result, 0, sizeof(DBUSQLiteResult));
+    query->ref++;
+    result->query = query;
+    query->result = result;
+    
+    result->result.optional_numRows = NULL;
+    result->result.numFields = dbuSQLiteResultNumFields;
+    result->result.hasData = dbuSQLiteResultHasData;
+    result->result.isOk = dbuSQLiteResultIsOk;
+    result->result.nextRow = dbuSQLiteResultNextRow;
+    result->result.fieldName = dbuSQLiteResultFieldName;
+    result->result.fieldType = dbuSQLiteResultFieldType;
+    result->result.isNull = dbuSQLiteResultIsNull;
+    result->result.getText = dbuSQLiteResultGetText;
+    result->result.optional_getInt = dbuSQLiteResultGetInt;
+    result->result.optional_getDouble = dbuSQLiteResultGetDouble;
+    result->result.optional_getBinary = dbuSQLiteResultGetBinary;
+    result->result.free = dbuSQLiteResultFree;
+    result->result.allocator = q->allocator;
+    result->stmt = query->stmt;
+    result->step = query->step;
+    
+    return (DBUResult*)result;
+}
+
+void dbuSQLiteQueryFree(DBUQuery *q) {
+    DBUSQLiteQuery *query = (DBUSQLiteQuery*)q;
+    if(--query->ref > 0) {
+        return;
+    }
+    sqlite3_finalize(query->stmt);
+    free(query);
+}
+
+
+/* --------------------- SQLite Result Implementation  ---------------------*/
+
+int dbuSQLiteResultNumFields(DBUResult *result) {
+    DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+    return sqlite3_column_count(r->stmt);
+}
+
+int dbuSQLiteResultHasData(DBUResult *result) {
+    DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+    return r->step == SQLITE_ROW;
+}
+
+int dbuSQLiteResultIsOk(DBUResult *result) {
+    DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+    return r->step == SQLITE_DONE;
+}
+
+int dbuSQLiteResultNextRow(DBUResult *result) {
+    DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+    r->step = sqlite3_step(r->stmt);
+    int done = r->step == SQLITE_DONE;
+    r->result.rowIndex++;
+    return done;
+}
+
+const char* dbuSQLiteResultFieldName(DBUResult *result, int field) {
+    DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+    return sqlite3_column_name(r->stmt, field);
+}
+
+DBUFieldType dbuSQLiteResultFieldType(DBUResult *result, int field) {
+    DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+    int type = sqlite3_column_type(r->stmt, field);
+    DBUFieldType dbuType;
+    switch(type) {
+        default: dbuType = DBU_FIELD_TEXT; break;
+        case SQLITE_INTEGER: dbuType = DBU_FIELD_INT; break;
+        case SQLITE_FLOAT: dbuType = DBU_FIELD_DOUBLE; break;
+        case SQLITE_TEXT: dbuType = DBU_FIELD_TEXT; break;
+        case SQLITE_BLOB: dbuType = DBU_FIELD_BINARY; break;
+        case SQLITE_NULL: dbuType = DBU_FIELD_NULL; break;
+    }
+    return dbuType;
+}
+
+int dbuSQLiteResultIsNull(DBUResult *result, int field) {
+    DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+    return sqlite3_column_type(r->stmt, field) == SQLITE_NULL;
+}
+
+cxstring dbuSQLiteResultGetText(DBUResult *result, int field) {
+    DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+    const char *s = (const char*)sqlite3_column_text(r->stmt, field);
+    size_t len = (size_t)sqlite3_column_bytes(r->stmt, field);
+    return cx_strn(s, len);
+}
+
+int64_t dbuSQLiteResultGetInt(DBUResult *result, int field) {
+    DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+    return sqlite3_column_int64(r->stmt, field);
+}
+
+double dbuSQLiteResultGetDouble(DBUResult *result, int field) {
+    DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+    return sqlite3_column_double(r->stmt, field);
+}
+
+DBUBytes dbuSQLiteResultGetBinary(DBUResult *result, int field) {
+    DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+    const unsigned char *s = sqlite3_column_blob(r->stmt, field);
+    size_t len = (size_t)sqlite3_column_bytes(r->stmt, field);
+    return (DBUBytes) { s, len };
+}
+
+void dbuSQLiteResultFree(DBUResult *result) {
+    DBUSQLiteResult *r = (DBUSQLiteResult*)result;
+    r->query->query.free((DBUQuery*)r->query);
+    free(r);
+}
+
 #endif /* DBU_SQLITE */
--- a/dbutils/sqlite.h	Sat Dec 07 23:10:03 2024 +0100
+++ b/dbutils/sqlite.h	Sun Dec 08 15:46:03 2024 +0100
@@ -37,8 +37,58 @@
 extern "C" {
 #endif
 
+typedef struct DBUSQLiteQuery DBUSQLiteQuery;
+typedef struct DBUSQLiteResult DBUSQLiteResult;
+    
+typedef struct DBUSQLiteConnection {
+    DBUConnection connection;
+    sqlite3 *db;
+    bool autoclose;
+} DBUSQLiteConnection;
 
+struct DBUSQLiteQuery {
+    DBUQuery query;
+    sqlite3 *db;
+    sqlite3_stmt *stmt;
+    DBUSQLiteResult *result;
+    int step;
+    int ref;
+};
 
+struct DBUSQLiteResult {
+    DBUResult result;
+    sqlite3_stmt *stmt;
+    DBUSQLiteQuery *query;
+    int step;
+};
+
+DBUQuery* dbuSQLiteConnCreateQuery(DBUConnection *connection, const CxAllocator *a) ;
+int dbuSQLiteConnIsActive(DBUConnection *connection);
+void dbuSQLiteConnFree(DBUConnection *connection);
+
+int dbuSQLiteQuerySetSQL(DBUQuery *q, const char *sql);
+int dbuSQLiteQuerySetParamString(DBUQuery *q, int index, cxstring str);
+int dbuSQLiteQuerySetParamInt(DBUQuery *q, int index, int i);
+int dbuSQLiteQuerySetParamInt64(DBUQuery *q, int index, int64_t i);
+int dbuSQLiteQuerySetParamDouble(DBUQuery *q, int index, double d);
+int dbuSQLiteQuerySetParamNull(DBUQuery *q, int index);
+int dbuSQLiteQuerySetParamBytes(DBUQuery *q, int index, void *bytes, int len);
+int dbuSQLiteQueryExec(DBUQuery *q);
+DBUResult* dbuSQLiteQueryGetResult(DBUQuery *q);
+void dbuSQLiteQueryFree(DBUQuery *q);
+
+int dbuSQLiteResultNumFields(DBUResult *result);
+int dbuSQLiteResultHasData(DBUResult *result);
+int dbuSQLiteResultIsOk(DBUResult *result);
+int dbuSQLiteResultNextRow(DBUResult *result);
+const char* dbuSQLiteResultFieldName(DBUResult *result, int field);
+DBUFieldType dbuSQLiteResultFieldType(DBUResult *result, int field);
+int dbuSQLiteResultIsNull(DBUResult *result, int field);
+cxstring dbuSQLiteResultGetText(DBUResult *result, int field);
+int64_t dbuSQLiteResultGetInt(DBUResult *result, int field);
+double dbuSQLiteResultGetDouble(DBUResult *result, int field);
+DBUBytes dbuSQLiteResultGetBinary(DBUResult *result, int field);
+void dbuSQLiteResultFree(DBUResult *result);
 
 #ifdef __cplusplus
 }
--- a/test/main.c	Sat Dec 07 23:10:03 2024 +0100
+++ b/test/main.c	Sun Dec 08 15:46:03 2024 +0100
@@ -31,6 +31,7 @@
 
 #include <dbutils/dbutils.h>
 #include <dbutils/sqlite.h>
+#include <dbutils/db.h>
 
 const char *sql_create_table_person =
 "create table if not exists Person ("
@@ -89,7 +90,10 @@
         return 1;
     }
     
-    CxList *persons = dbuSQLiteQuerySingleTable(ctx, db, "person", "select * from Person;");
+    DBUConnection *conn = dbuSQLiteConnectionFromDB(db, true);
+    DBUQuery *query = conn->createQuery(conn, NULL);
+    dbuQuerySetSQL(query, "select * from Person;");
+    CxList *persons = dbuQuerySingleType(ctx, query, "person");
     if(persons) {
         CxIterator i = cxListIterator(persons);
         cx_foreach(Person *, p, i) {
@@ -100,7 +104,7 @@
         fprintf(stderr, "Error\n");
     }
     
-    sqlite3_close(db);
+    conn->free(conn);
     
     return 0;
 }

mercurial