| |
1 /* |
| |
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. |
| |
3 * |
| |
4 * Copyright 2025 Olaf Wintermann. All rights reserved. |
| |
5 * |
| |
6 * Redistribution and use in source and binary forms, with or without |
| |
7 * modification, are permitted provided that the following conditions are met: |
| |
8 * |
| |
9 * 1. Redistributions of source code must retain the above copyright |
| |
10 * notice, this list of conditions and the following disclaimer. |
| |
11 * |
| |
12 * 2. Redistributions in binary form must reproduce the above copyright |
| |
13 * notice, this list of conditions and the following disclaimer in the |
| |
14 * documentation and/or other materials provided with the distribution. |
| |
15 * |
| |
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| |
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| |
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| |
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| |
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| |
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| |
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| |
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| |
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| |
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| |
26 * POSSIBLIITY OF SUCH DAMAGE. |
| |
27 */ |
| |
28 |
| |
29 #include "database.h" |
| |
30 |
| |
31 #include <stdio.h> |
| |
32 #include <stdlib.h> |
| |
33 #include <errno.h> |
| |
34 #include <unistd.h> |
| |
35 |
| |
36 #include <cx/buffer.h> |
| |
37 #include <cx/streams.h> |
| |
38 |
| |
39 #define TEST_DB "test.db" |
| |
40 #define TEST_DATA_FILE "testdata.sql" |
| |
41 |
| |
42 |
| |
43 // create test.db and execute testdata script |
| |
44 int init_test_db(void) { |
| |
45 sqlite3 *db; |
| |
46 char *err_msg = NULL; |
| |
47 |
| |
48 FILE *f = fopen(TEST_DATA_FILE, "r"); |
| |
49 if(!f) { |
| |
50 fprintf(stderr, "Cannot open test data file %s: %s\n", TEST_DATA_FILE, strerror(errno)); |
| |
51 } |
| |
52 |
| |
53 if(unlink(TEST_DB)) { |
| |
54 if(errno != ENOENT) { |
| |
55 fprintf(stderr, "Cannot unlink %s: %s\n", TEST_DB, strerror(errno)); |
| |
56 fclose(f); |
| |
57 return 1; |
| |
58 } |
| |
59 } |
| |
60 if(sqlite3_open(TEST_DB, &db) != SQLITE_OK) { |
| |
61 fprintf(stderr, "Cannot open database %s: %s\n", TEST_DB, sqlite3_errmsg(db)); |
| |
62 fclose(f); |
| |
63 return 1; |
| |
64 } |
| |
65 |
| |
66 CxBuffer *buf = cxBufferCreate(NULL, 2048, NULL, CX_BUFFER_AUTO_EXTEND | CX_BUFFER_FREE_CONTENTS); |
| |
67 cx_stream_copy(f, buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite); |
| |
68 int err = 0; |
| |
69 if(buf->size > 0) { |
| |
70 cxBufferTerminate(buf); |
| |
71 |
| |
72 if(sqlite3_exec(db, buf->space, 0, 0, &err_msg) != SQLITE_OK) { |
| |
73 fprintf(stderr, "SQL error: %s\n", err_msg); |
| |
74 sqlite3_free(err_msg); |
| |
75 err = 1; |
| |
76 } |
| |
77 } else { |
| |
78 fprintf(stderr, "Error: no file content\n"); |
| |
79 err = 1; |
| |
80 } |
| |
81 cxBufferFree(buf); |
| |
82 sqlite3_close(db); |
| |
83 |
| |
84 return err; |
| |
85 } |
| |
86 |
| |
87 |
| |
88 |
| |
89 static DBUContext *ctx; |
| |
90 static DBUConnection *conn; |
| |
91 |
| |
92 typedef struct Address { |
| |
93 int64_t address_id; |
| |
94 |
| |
95 cxmutstr street; |
| |
96 cxmutstr zip; |
| |
97 cxmutstr city; |
| |
98 } Address; |
| |
99 |
| |
100 typedef struct Person { |
| |
101 int64_t person_id; |
| |
102 |
| |
103 cxmutstr name; |
| |
104 cxmutstr email; |
| |
105 int age; |
| |
106 bool iscustomer; |
| |
107 uint64_t hash; |
| |
108 |
| |
109 Address *address; |
| |
110 |
| |
111 CxList *roles; |
| |
112 } Person; |
| |
113 |
| |
114 typedef struct Role { |
| |
115 int64_t role_id; |
| |
116 int64_t person_id; |
| |
117 cxmutstr name; |
| |
118 } Role; |
| |
119 |
| |
120 int init_db_tests(void) { |
| |
121 ctx = dbuContextCreate(); |
| |
122 |
| |
123 DBUClass *address = dbuRegisterClass(ctx, "address", Address, address_id); |
| |
124 dbuClassAdd(address, Address, street); |
| |
125 dbuClassAdd(address, Address, zip); |
| |
126 dbuClassAdd(address, Address, city); |
| |
127 |
| |
128 DBUClass *role = dbuRegisterClass(ctx, "role", Role, role_id); |
| |
129 |
| |
130 DBUClass *person = dbuRegisterClass(ctx, "person", Person, person_id); |
| |
131 dbuClassAdd(person, Person, name); |
| |
132 dbuClassAdd(person, Person, email); |
| |
133 dbuClassAdd(person, Person, age); |
| |
134 dbuClassAdd(person, Person, iscustomer); |
| |
135 dbuClassAdd(person, Person, hash); |
| |
136 dbuClassAddObj(person, "address_id", offsetof(Person, address), address); |
| |
137 dbuClassAddCxLinkedList(person, "person_id", offsetof(Person, roles), role); |
| |
138 |
| |
139 dbuClassAddForeignKey(role, Role, person_id, person); |
| |
140 dbuClassAdd(role, Role, name); |
| |
141 |
| |
142 return 0; |
| |
143 } |
| |
144 |
| |
145 void cleanup_db_tests(void) { |
| |
146 if(conn) { |
| |
147 dbuConnectionFree(conn); |
| |
148 } |
| |
149 dbuContextFree(ctx); |
| |
150 } |
| |
151 |
| |
152 CX_TEST(testSqliteConnection) { |
| |
153 CX_TEST_DO { |
| |
154 conn = dbuSQLiteConnection(TEST_DB); |
| |
155 CX_TEST_ASSERT(conn); |
| |
156 CX_TEST_ASSERT(dbuConnectionIsActive(conn)); |
| |
157 } |
| |
158 } |
| |
159 |
| |
160 CX_TEST(testConnectionExec) { |
| |
161 CX_TEST_DO { |
| |
162 CX_TEST_ASSERT(conn); |
| |
163 |
| |
164 int t1 = dbuConnectionExec(conn, "create table ExecTest1(a int);"); |
| |
165 CX_TEST_ASSERT(t1 == 0); |
| |
166 |
| |
167 int t2 = dbuConnectionExec(conn, "insert into ExecTest1(a) values (1);"); |
| |
168 CX_TEST_ASSERT(t2 == 0); |
| |
169 |
| |
170 int t3 = dbuConnectionExec(conn, "drop table ExecTest1;"); |
| |
171 CX_TEST_ASSERT(t3 == 0); |
| |
172 |
| |
173 int fail = dbuConnectionExec(conn, "select * from Fail;"); |
| |
174 CX_TEST_ASSERT(fail != 0); |
| |
175 } |
| |
176 } |
| |
177 |
| |
178 CX_TEST(testSingleValueQuery) { |
| |
179 CX_TEST_DO { |
| |
180 CX_TEST_ASSERT(conn); |
| |
181 |
| |
182 DBUQuery *q = dbuConnectionQuery(conn, NULL); |
| |
183 CX_TEST_ASSERT(q); |
| |
184 CX_TEST_ASSERT(dbuQuerySetSQL(q, "select 12;") == 0); |
| |
185 CX_TEST_ASSERT(dbuQueryExec(q) == 0); |
| |
186 |
| |
187 DBUResult *r = dbuQueryGetResult(q); |
| |
188 CX_TEST_ASSERT(r); |
| |
189 int value; |
| |
190 CX_TEST_ASSERT(dbuResultAsValue(r, &value) == 0); |
| |
191 CX_TEST_ASSERT(value == 12); |
| |
192 |
| |
193 dbuQueryFree(q); |
| |
194 |
| |
195 } |
| |
196 } |