Fri, 13 Dec 2024 11:24:55 +0100
prepare for list members
| dbutils/class.c | file | annotate | diff | comparison | revisions | |
| dbutils/dbutils/dbutils.h | file | annotate | diff | comparison | revisions | |
| dbutils/field.c | file | annotate | diff | comparison | revisions | |
| dbutils/object.c | file | annotate | diff | comparison | revisions | |
| dbutils/object.h | file | annotate | diff | comparison | revisions | |
| test/main.c | file | annotate | diff | comparison | revisions |
--- a/dbutils/class.c Wed Dec 11 21:53:31 2024 +0100 +++ b/dbutils/class.c Fri Dec 13 11:24:55 2024 +0100 @@ -50,6 +50,7 @@ cls->fields->collection.simple_destructor = (cx_destructor_func)field_destructor; cls->obj_fields = cxHashMapCreateSimple(CX_STORE_POINTERS); cls->obj_fields->collection.simple_destructor = (cx_destructor_func)field_destructor; + cls->foreign_keys = cxHashMapCreateSimple(sizeof(DBUForeignKeyField)); return cls; } @@ -67,9 +68,17 @@ cxMapPut(cls->fields, name, field); } +void dbuClassAddFKField(DBUClass *cls, const char *name, DBUField *field, DBUClass *fkcls) { + DBUForeignKeyField val; + val.field = field; + val.cls = fkcls; + field->foreignKeyClass = fkcls; + cxMapPut(cls->foreign_keys, name, &val); +} + void dbuClassAddObjField(DBUClass *cls, const char *name, DBUField *field, DBUClass *foreign_cls) { free(field->name.ptr); - field->name = cx_strdup(cx_str(name)); + field->name = name ? cx_strdup(cx_str(name)) : (cxmutstr){NULL,0}; cxMapPut(cls->obj_fields, foreign_cls->name, field); }
--- a/dbutils/dbutils/dbutils.h Wed Dec 11 21:53:31 2024 +0100 +++ b/dbutils/dbutils/dbutils.h Fri Dec 13 11:24:55 2024 +0100 @@ -48,6 +48,7 @@ typedef struct DBUField DBUField; typedef struct DBUObjectBuilder DBUObjectBuilder; +typedef struct DBUForeignKeyField DBUForeignKeyField; typedef char* DBUObject; @@ -100,6 +101,14 @@ CxMap *obj_fields; /* + * foreign keys + * + * key: field name + * value: DBUForeignKeyField + */ + CxMap *foreign_keys; + + /* * object size used for allocation, typically sizeof(some_struct) */ size_t obj_size; @@ -111,6 +120,22 @@ int (*init)(void *obj, const CxAllocator *a); }; +typedef struct DBUObjectResult DBUObjectResult; +struct DBUObjectResult { + void *userdata1; + void *userdata2; + int int1; + int int2; + DBUObject (*create)(DBUObjectResult *result, DBUClass *type, const CxAllocator *a); + int (*add)(DBUObjectResult *result, DBUObject parent, void *obj, CxList *fk, const CxAllocator *a); + void (*free)(DBUObjectResult *result); +}; + +struct DBUForeignKeyField { + DBUField *field; + DBUClass *cls; +}; + /* * abstract field */ @@ -163,10 +188,20 @@ void (*destructor)(DBUField *f); /* + * object result builder callback struct (optional for list/array types) + * + * DBUObjectResult contains callback for adding objects to + * list/array-fields + */ + DBUObjectResult builder; + + DBUClass *foreignKeyClass; + + /* * if true, null values are not accepted and result in an error */ bool nonnull; - bool query_length; + bool query_length; // TODO: remove }; DBUContext* dbuContextCreate(void); @@ -239,6 +274,7 @@ void dbuClassSetPrimaryKeyCxMutStr(DBUClass *cls, const char *column_name, off_t offset); void dbuClassAddField(DBUClass *cls, const char *name, DBUField *field); +void dbuClassAddFKField(DBUClass *cls, const char *name, DBUField *field, DBUClass *fkcls); void dbuClassAddObjField(DBUClass *cls, const char *name, DBUField *field, DBUClass *foreign_cls); #define dbuClassAdd(cls, type, member) \ @@ -260,6 +296,16 @@ cxmutstr: dbuClassAddCXMutStr) \ (cls, member_name, offsetof(type, member), 0) +#define dbuClassAddForeignKey(cls, type, member, foreigncls) \ + _Generic(((type*)0)->member, \ + int32_t: dbuClassAddInt32FK, \ + uint32_t: dbuClassAddUInt32FK, \ + int64_t: dbuClassAddInt64FK, \ + uint64_t: dbuClassAddUInt64FK, \ + char*: dbuClassAddStringFK, \ + cxmutstr: dbuClassAddCXMutStrFK) \ + (cls, #member, offsetof(type, member), foreigncls, 0) + void dbuClassAddInt(DBUClass *cls, const char *name, off_t offset, bool nonnull); void dbuClassAddUInt(DBUClass *cls, const char *name, off_t offset, bool nonnull); void dbuClassAddInt8(DBUClass *cls, const char *name, off_t offset, bool nonnull); @@ -282,6 +328,13 @@ void dbuClassAddBuf(DBUClass *cls, const char *name, off_t offset, off_t size_offset, bool nonnull); void dbuClassAddBufIntLen(DBUClass *cls, const char *name, off_t offset, off_t int_offset, bool nonnull); +void dbuClassAddInt32FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull); +void dbuClassAddUInt32FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull); +void dbuClassAddInt64FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull); +void dbuClassAddUInt64FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull); +void dbuClassAddStringFK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull); +void dbuClassAddCXMutStrFK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull); + void dbuClassAddIntDef(DBUClass *cls, const char *name, off_t offset, int def); void dbuClassAddUIntDef(DBUClass *cls, const char *name, off_t offset, unsigned int def); void dbuClassAddInt16Def(DBUClass *cls, const char *name, off_t offset, int16_t def); @@ -296,10 +349,10 @@ void dbuClassAddDoubleDef(DBUClass *cls, const char *name, off_t offset, double def); void dbuClassAddObj(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls); -void dbuClassAddLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls); -void dbuClassAddValueLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls); -void dbuClassAddCxArrayList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls); -void dbuClassAddValueArrayList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls); +void dbuClassAddCxLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls); +void dbuClassAddValueCxLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls); +void dbuClassAddCxArrayList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_clst); +void dbuClassAddValueCxArrayList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls); void dbuClassAddArray(DBUClass *cls, const char *name, off_t array_offset, off_t size_offset, DBUClass *foreign_cls); void dbuClassAddValueArray(DBUClass *cls, const char *name, off_t array_offset, off_t size_offset, DBUClass *foreign_cls);
--- a/dbutils/field.c Wed Dec 11 21:53:31 2024 +0100 +++ b/dbutils/field.c Fri Dec 13 11:24:55 2024 +0100 @@ -31,6 +31,9 @@ #include <string.h> #include <limits.h> +#include <cx/linked_list.h> +#include <cx/array_list.h> + #include "field.h" /* -------------------- Default Initializer Functions -------------------- */ @@ -503,6 +506,17 @@ dbuClassAddField(cls, name, create_offset_def_field(offset, def_init, init, nonnull, def)); } +static void add_offset_fk_field( + DBUClass *cls, + const char *name, + off_t offset, + DBUClass *fkcls, + DBUFieldInitFunc init, + bool nonnull) +{ + dbuClassAddFKField(cls, name, create_offset_def_field(offset, NULL, init, nonnull, (union DBUDefValue){ 0 }), fkcls); +} + static void add_bool( DBUClass *cls, const char *name, @@ -821,6 +835,67 @@ } +void dbuClassAddInt32FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull) { + add_offset_fk_field( + cls, + name, + offset, + foreign_cls, + (DBUFieldInitFunc)field_init_int32, + nonnull); +} + +void dbuClassAddUInt32FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull){ + add_offset_fk_field( + cls, + name, + offset, + foreign_cls, + (DBUFieldInitFunc)field_init_uint32, + nonnull); +} + +void dbuClassAddInt64FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull){ + add_offset_fk_field( + cls, + name, + offset, + foreign_cls, + (DBUFieldInitFunc)field_init_int64, + nonnull); +} + +void dbuClassAddUInt64FK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull){ + add_offset_fk_field( + cls, + name, + offset, + foreign_cls, + (DBUFieldInitFunc)field_init_uint64, + nonnull); +} + +void dbuClassAddStringFK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull){ + add_offset_fk_field( + cls, + name, + offset, + foreign_cls, + (DBUFieldInitFunc)field_init_str, + nonnull); +} + +void dbuClassAddCXMutStrFK(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls, bool nonnull){ + add_offset_fk_field( + cls, + name, + offset, + foreign_cls, + (DBUFieldInitFunc)field_init_cxmutstr, + nonnull); +} + + void dbuClassAddIntDef(DBUClass *cls, const char *name, off_t offset, int def) { union DBUDefValue defvalue; defvalue.def = def; @@ -995,8 +1070,50 @@ dbuClassAddObjField(cls, name, (DBUField*)field, foreign_cls); } -void dbuClassAddLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls) { - +static int linked_list_add(DBUField *f, const CxAllocator *a, DBUObject obj, void *child) { + DBUOffsetField *field = (DBUOffsetField*)f; + CxList **list = (CxList**)(obj+field->offset); + if(*list) { + *list = cxLinkedListCreate(a, NULL, CX_STORE_POINTERS); + if(!(*list)) { + return 1; + } + } + return cxListAdd(*list, child);; +} + +static DBUObject linkedlist_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 int linkedlist_add(DBUObjectResult *result, DBUObject parent, void *obj, CxList *fk, const CxAllocator *a) { + DBUOffsetField *field = result->userdata1; + DBUClass *cls = result->userdata2; + CxList **list = (CxList**)(parent+field->offset); + if(*list) { + *list = cxLinkedListCreate(a, NULL, CX_STORE_POINTERS); + if(!(*list)) { + return 1; + } + } + cxListAdd(*list, obj);; + return 0; +} + +void dbuClassAddCxLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls) { + DBUOffsetField *field = malloc(sizeof(DBUOffsetField)); + memset(field, 0, sizeof(DBUOffsetField)); + field->offset = offset; + field->def.def = 0; + field->field.builder.userdata1 = field; + field->field.builder.userdata2 = foreign_cls; + field->field.builder.create = linkedlist_create_obj; + field->field.builder.add = linkedlist_add; + dbuClassAddObjField(cls, NULL, (DBUField*)field, foreign_cls); + //dbuClassAddParentObjField(foreign_cls, name, cls, (DBUField*)field); } void dbuClassAddValueLinkedList(DBUClass *cls, const char *name, off_t offset, DBUClass *foreign_cls) {
--- a/dbutils/object.c Wed Dec 11 21:53:31 2024 +0100 +++ b/dbutils/object.c Fri Dec 13 11:24:55 2024 +0100 @@ -64,7 +64,6 @@ builder->ctx = type->context; builder->mainQuery = query; builder->resultType = type; - builder->subQueries = subQueries; builder->additionalQueries = additionalQueries; return builder; @@ -84,16 +83,20 @@ // 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 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 int list_result_add(DBUObjectBuilderResult *result, void *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, void *obj, CxList *fk, const CxAllocator *a) { return cxListAdd(result->userdata1, obj); } @@ -104,11 +107,11 @@ } // TODO: the class needs a obj destructor func, because we need to be able // to destroy partialy created lists - DBUObjectBuilderResult result = { + DBUObjectResult result = { .userdata1 = result_list, .userdata2 = NULL, .int1 = CX_STORE_POINTERS, - .create = list_result_create, + .create = result_create_obj, .add = list_result_add }; if(dbuObjectExec(builder, &result)) { @@ -126,11 +129,11 @@ } // TODO: the class needs a obj destructor func, because we need to be able // to destroy partialy created lists - DBUObjectBuilderResult result = { + DBUObjectResult result = { .userdata1 = result_list, .userdata2 = malloc(builder->resultType->obj_size), .int1 = builder->resultType->obj_size, - .create = list_result_create, + .create = valuelist_result_create, .add = list_result_add }; if(!result.userdata2) { @@ -156,11 +159,14 @@ /* ------------------------- Object Builder -----------------------------*/ -static int result_add_noop(DBUObjectBuilderResult *result, void *obj) { +static int add_to_parent(DBUObjectResult *result, DBUObject parent, void *obj, CxList *fk, const CxAllocator *a) { + DBUBuilderQuery *query = result->userdata1; + + return 0; } -int dbuObjectExec(DBUObjectBuilder *builder, DBUObjectBuilderResult *objresult) { +int dbuObjectExec(DBUObjectBuilder *builder, DBUObjectResult *objresult) { if(cxListSize(builder->additionalQueries) > 0) { builder->cache = cxHashMapCreateSimple(sizeof(DBUBuilderObjCache)); if(!builder->cache) { @@ -171,16 +177,16 @@ int ret = dbuObjectExecuteQuery(builder, builder->mainQuery, builder->resultType, objresult); if(!ret) { CxIterator i = cxListIterator(builder->additionalQueries); - cx_foreach(DBUBuilderQuery *, a, i) { - DBUObjectBuilderResult result = { - .userdata1 = NULL, + cx_foreach(DBUBuilderQuery *, q, i) { + DBUObjectResult result = { + .userdata1 = q, .userdata2 = NULL, .int1 = CX_STORE_POINTERS, - .create = list_result_create, - .add = result_add_noop + .create = result_create_obj, + .add = add_to_parent }; - ret = dbuObjectExecuteQuery(builder, a->query, a->type, &result); + ret = dbuObjectExecuteQuery(builder, q->query, q->type, &result); if(ret) { break; } @@ -194,7 +200,7 @@ return ret; } -int dbuObjectExecuteQuery(DBUObjectBuilder *builder, DBUQuery *query, DBUClass *type, DBUObjectBuilderResult *objresult) { +int dbuObjectExecuteQuery(DBUObjectBuilder *builder, DBUQuery *query, DBUClass *type, DBUObjectResult *objresult) { DBUClass *cls = type; // TODO: execute additional queries @@ -264,6 +270,9 @@ const CxAllocator *a = builder->allocator; + CxList *fklist = cxArrayListCreateSimple(CX_STORE_POINTERS, 4); + fklist->collection.simple_destructor = free; + // get result int err = 0; while(result->hasData(result)) { @@ -318,18 +327,25 @@ // if obj caching is enabled and the current field contains // the primary key, add this object to the cache if(builder->cache && 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; - } - int r = cxMapPut(builder->cache, cache_key, current_obj); - free(cache_key.ptr); - if(r) { - err = 1; - break; + 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; + } + int r = cxMapPut(builder->cache, cache_key, current_obj); + 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, current_cls->name.ptr, + (int)text.length, text.ptr); + cxListAdd(fklist, fkey.ptr); } } } @@ -340,12 +356,16 @@ break; } - objresult->add(objresult, obj); + objresult->add(objresult, NULL, obj, fklist, a); + + cxListClear(fklist); // load next row result->nextRow(result); } + cxListDestroy(fklist); + result->free(result);
--- a/dbutils/object.h Wed Dec 11 21:53:31 2024 +0100 +++ b/dbutils/object.h Fri Dec 13 11:24:55 2024 +0100 @@ -49,17 +49,6 @@ DBUQuery *query; }; -typedef struct DBUObjectBuilderResult DBUObjectBuilderResult; -struct DBUObjectBuilderResult { - void *userdata1; - void *userdata2; - int int1; - int int2; - DBUObject (*create)(DBUObjectBuilderResult *result, DBUClass *type, const CxAllocator *a); - int (*add)(DBUObjectBuilderResult *result, void *obj); - void (*free)(DBUObjectBuilderResult *result); -}; - typedef struct DBUBuilderObjCache { DBUClass *class; void *obj; @@ -86,7 +75,7 @@ * * Subqueries, that should be executed for all main result rows. * Before a subquery is executed, the first parameter is set to the - * foreign key, that is stored in the main table. + * primary key of the main table. */ CxMap *subQueries; @@ -101,7 +90,7 @@ /* * result builder */ - DBUObjectBuilderResult *result; + DBUObjectResult *result; /* * object cache @@ -113,9 +102,9 @@ CxMap *cache; }; -int dbuObjectExec(DBUObjectBuilder *builder, DBUObjectBuilderResult *objresult); +int dbuObjectExec(DBUObjectBuilder *builder, DBUObjectResult *objresult); -int dbuObjectExecuteQuery(DBUObjectBuilder *builder, DBUQuery *query, DBUClass *type, DBUObjectBuilderResult *objresult); +int dbuObjectExecuteQuery(DBUObjectBuilder *builder, DBUQuery *query, DBUClass *type, DBUObjectResult *objresult); #ifdef __cplusplus }
--- a/test/main.c Wed Dec 11 21:53:31 2024 +0100 +++ b/test/main.c Fri Dec 13 11:24:55 2024 +0100 @@ -50,12 +50,16 @@ "zip text, " "city text);"; +const char *sql_create_table_role = +"create table if not exists Role (" +"role_id integer primary key autoincrement, " +"person_id integer, " +"name text);"; + const char *sql_check_table = "select person_id from Person limit 1;"; - - const char *sql_create_test_data1 = "insert into address (street, zip, city) " "values " @@ -65,8 +69,16 @@ const char *sql_create_test_data2 = "insert into person (name, email, age, iscustomer, hash, address_id) " "values " -"('alice', 'alice@example.com', 30, 1, 123456789, (select address_id from address where street = 'street 1')), " -"('bob', 'bob@example.com', 25, 0, 987654321, (select address_id from address where street = 'street 2'));"; +"('alice', 'alice@example.com', 30, 0, 123456789, (select address_id from address where street = 'street 1')), " +"('bob', 'bob@example.com', 25, 1, 987654321, (select address_id from address where street = 'street 2'));"; + +const char *sql_create_test_data3 = +"insert into role (person_id, name) " +"values " +"(1, 'finance'), " +"(1, 'dev'), " +"(1, 'manager'), " +"(2, 'extern');"; typedef struct Address { int64_t address_id; @@ -86,8 +98,15 @@ uint64_t hash; Address *address; + + CxList *roles; } Person; +typedef struct Role { + int64_t role_id; + int64_t person_id; + cxmutstr name; +} Role; static int create_test_data(sqlite3 *db); @@ -102,6 +121,8 @@ dbuClassAdd(address, Address, zip); dbuClassAdd(address, Address, city); + DBUClass *role = dbuRegisterClass(ctx, "role", Role, role_id); + DBUClass *person = dbuRegisterClass(ctx, "person", Person, person_id); dbuClassAdd(person, Person, name); dbuClassAdd(person, Person, email); @@ -109,7 +130,10 @@ 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); // Open or create the database sqlite3 *db; @@ -128,7 +152,11 @@ DBUQuery *query = conn->createQuery(conn, NULL); dbuQuerySetSQL(query, "select p.*, a.address_id as [__address__address_id], a.street, a.zip, a.city from Person p inner join Address a on p.address_id = a.address_id;"); + DBUQuery *roleQuery = conn->createQuery(conn, NULL); + dbuQuerySetSQL(roleQuery, "select * from role;"); + DBUObjectBuilder *builder = dbuObjectBuilder(person, query, cxDefaultAllocator); + dbuObjectBuilderAddAdditionalQuery(builder, role, roleQuery); CxList *persons = dbuObjectBuilderGetList(builder); if(persons) { CxIterator i = cxListIterator(persons); @@ -147,7 +175,13 @@ static int create_test_data(sqlite3 *db) { char *err_msg = NULL; - int rc = sqlite3_exec(db, sql_create_table_person, 0, 0, &err_msg); + sqlite3_stmt *stmt; + int rc = sqlite3_prepare_v2(db, sql_check_table, -1, &stmt, 0); + if(rc == SQLITE_OK) { + return 0; + } + + rc = sqlite3_exec(db, sql_create_table_person, 0, 0, &err_msg); if(rc != SQLITE_OK) { fprintf(stderr, "SQLite error: %s\n", err_msg); sqlite3_free(err_msg); @@ -163,25 +197,14 @@ return 1; } - - sqlite3_stmt *stmt; - rc = sqlite3_prepare_v2(db, sql_check_table, -1, &stmt, 0); + rc = sqlite3_exec(db, sql_create_table_role, 0, 0, &err_msg); if(rc != SQLITE_OK) { - fprintf(stderr, "SQLite error: %s\n", sqlite3_errmsg(db)); + fprintf(stderr, "SQLite error: %s\n", err_msg); + sqlite3_free(err_msg); sqlite3_close(db); return 1; } - int exists = 0; - if(sqlite3_step(stmt) == SQLITE_ROW) { - exists = 1; - } - sqlite3_finalize(stmt); - - if(exists) { - return 0; - } - rc = sqlite3_exec(db, sql_create_test_data1, 0, 0, &err_msg); if(rc != SQLITE_OK) { fprintf(stderr, "SQLite error: %s\n", err_msg); @@ -198,5 +221,13 @@ return 1; } + rc = sqlite3_exec(db, sql_create_test_data3, 0, 0, &err_msg); + if(rc != SQLITE_OK) { + fprintf(stderr, "SQLite error: %s\n", err_msg); + sqlite3_free(err_msg); + sqlite3_close(db); + return 1; + } + return 0; }