comparison libcore/core/save.c @ 172:6250883b81f0

core: improve save module, closes #2507 - Databases can now be opened in read-only or in read-write modes, - Module is fully reentrant, - SQL commands are in separate files, - Improve tests, - API to get/set property is using a dedicated structure.
author David Demelier <markand@malikania.fr>
date Wed, 21 Oct 2020 12:43:25 +0200
parents aab824406d3d
children 76afe639fd72
comparison
equal deleted inserted replaced
171:d841cff1017c 172:6250883b81f0
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */ 17 */
18 18
19 #include <assert.h> 19 #include <assert.h>
20 #include <stdio.h> 20 #include <stdio.h>
21 #include <stdlib.h>
21 #include <string.h> 22 #include <string.h>
22 23
23 #include <sqlite3.h> 24 #include <sqlite3.h>
25
26 #include <core/assets/sql/init.h>
27 #include <core/assets/sql/property-get.h>
28 #include <core/assets/sql/property-remove.h>
29 #include <core/assets/sql/property-set.h>
24 30
25 #include "error.h" 31 #include "error.h"
26 #include "save.h" 32 #include "save.h"
27 #include "sys.h" 33 #include "sys.h"
28 34 #include "util.h"
29 static sqlite3 *db; 35
30 36 #define SQL_BEGIN "BEGIN EXCLUSIVE TRANSACTION"
31 static const char *sinit = 37 #define SQL_COMMIT "COMMIT"
32 "BEGIN EXCLUSIVE TRANSACTION;" 38 #define SQL_ROLLBACK "ROLLBACK"
33 ""
34 "CREATE TABLE IF NOT EXISTS property("
35 " id INTEGER PRIMARY KEY AUTOINCREMENT,"
36 " key TEXT NOT NULL UNIQUE,"
37 " value TEXT NOT NULL"
38 ");"
39 ""
40 "COMMIT"
41 ;
42
43 static const char *sbegin =
44 "BEGIN EXCLUSIVE TRANSACTION"
45 ;
46
47 static const char *scommit =
48 "COMMIT"
49 ;
50
51 static const char *srollback =
52 "ROLLBACK"
53 ;
54
55 static const char *sset_property =
56 "INSERT OR REPLACE INTO property("
57 " key,"
58 " value"
59 ")"
60 "VALUES("
61 " ?,"
62 " ?"
63 ");"
64 ;
65
66 static const char *sget_property =
67 "SELECT value"
68 " FROM property"
69 " WHERE key = ?"
70 ;
71
72 static const char *sremove_property =
73 "DELETE"
74 " FROM property"
75 " WHERE key = ?"
76 ;
77 39
78 static bool 40 static bool
79 exec(const char *sql) 41 exec(struct save *db, const char *sql)
80 { 42 {
81 if (sqlite3_exec(db, sql, NULL, NULL, NULL) != SQLITE_OK) 43 if (sqlite3_exec(db->handle, sql, NULL, NULL, NULL) != SQLITE_OK)
82 return errorf("%s", sqlite3_errmsg(db)); 44 return errorf("%s", sqlite3_errmsg(db->handle));
83 45
84 return true; 46 return true;
85 } 47 }
86 48
87 bool 49 static bool
88 save_open(unsigned int idx) 50 execu(struct save *db, const unsigned char *sql)
89 { 51 {
90 return save_open_path(sys_savepath(idx)); 52 return exec(db, (const char *)sql);
91 } 53 }
92 54
93 bool 55 bool
94 save_open_path(const char *path) 56 save_open(struct save *db, unsigned int idx, enum save_mode mode)
95 { 57 {
58 assert(db);
59
60 return save_open_path(db, sys_savepath(idx), mode);
61 }
62
63 bool
64 verify(struct save *db)
65 {
66 struct {
67 time_t *date;
68 struct save_property prop;
69 } table[] = {
70 { .date = &db->created, { .key = "molko.create-date" } },
71 { .date = &db->updated, { .key = "molko.update-date" } },
72 };
73
74 /* Ensure create and update dates are present. */
75 for (size_t i = 0; i < NELEM(table); ++i) {
76 if (!save_get_property(db, &table[i].prop)) {
77 sqlite3_close(db->handle);
78 return errorf("database not initialized correctly");
79 }
80
81 *table[i].date = strtoull(table[i].prop.value, NULL, 10);
82 }
83
84 return true;
85 }
86
87 bool
88 save_open_path(struct save *db, const char *path, enum save_mode mode)
89 {
90 assert(db);
96 assert(path); 91 assert(path);
97 92
98 if (sqlite3_open(path, &db) != SQLITE_OK) 93 int flags = 0;
99 return errorf("database open error: %s", sqlite3_errmsg(db)); 94
100 if (sqlite3_exec(db, sinit, NULL, NULL, NULL) != SQLITE_OK) 95 switch (mode) {
101 return errorf("database init error: %s", sqlite3_errmsg(db)); 96 case SAVE_MODE_WRITE:
102 97 flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
103 return true; 98 break;
104 } 99 default:
105 100 flags = SQLITE_OPEN_READONLY;
106 bool 101 break;
107 save_set_property(const char *key, const char *value) 102 }
108 { 103
109 assert(key); 104 if (sqlite3_open_v2(path, (sqlite3**)&db->handle, flags, NULL) != SQLITE_OK)
110 assert(value && strlen(value) <= SAVE_PROPERTY_VALUE_MAX); 105 return errorf("%s", sqlite3_errmsg(db->handle));
106
107 if (mode == SAVE_MODE_WRITE) {
108 if (!execu(db, sql_init)) {
109 sqlite3_close(db->handle);
110 return false;
111 }
112 }
113
114 return verify(db);
115 }
116
117 bool
118 save_set_property(struct save *db, const struct save_property *prop)
119 {
120 assert(db);
121 assert(prop);
111 122
112 sqlite3_stmt *stmt = NULL; 123 sqlite3_stmt *stmt = NULL;
113 124
114 if (!exec(sbegin)) 125 if (!exec(db, SQL_BEGIN))
115 return false; 126 return false;
116 if (sqlite3_prepare(db, sset_property, -1, &stmt, NULL) != SQLITE_OK) 127 if (sqlite3_prepare(db->handle, (const char *)sql_property_set, -1, &stmt, NULL) != SQLITE_OK)
117 goto sqlite3_err; 128 goto sqlite3_err;
118 if (sqlite3_bind_text(stmt, 1, key, -1, NULL) != SQLITE_OK || 129 if (sqlite3_bind_text(stmt, 1, prop->key, -1, NULL) != SQLITE_OK ||
119 sqlite3_bind_text(stmt, 2, value, -1, NULL) != SQLITE_OK) 130 sqlite3_bind_text(stmt, 2, prop->value, -1, NULL) != SQLITE_OK)
120 goto sqlite3_err; 131 goto sqlite3_err;
121 if (sqlite3_step(stmt) != SQLITE_DONE) 132 if (sqlite3_step(stmt) != SQLITE_DONE)
122 goto sqlite3_err; 133 goto sqlite3_err;
123 134
124 sqlite3_finalize(stmt); 135 sqlite3_finalize(stmt);
125 136
126 return exec(scommit); 137 return exec(db, SQL_COMMIT);
127 138
128 sqlite3_err: 139 sqlite3_err:
129 if (stmt) { 140 errorf("%s", sqlite3_errmsg(db->handle));
141
142 if (stmt)
130 sqlite3_finalize(stmt); 143 sqlite3_finalize(stmt);
131 exec(srollback); 144
132 } 145 exec(db, SQL_ROLLBACK);
133 146
134 return errorf("%s", sqlite3_errmsg(db)); 147 return false;
135 } 148 }
136 149
137 const char * 150 bool
138 save_get_property(const char *key) 151 save_get_property(struct save *db, struct save_property *prop)
139 { 152 {
140 assert(key); 153 assert(db);
141 154 assert(prop);
142 static char value[SAVE_PROPERTY_VALUE_MAX + 1]; 155
143 const char *ret = value;
144 sqlite3_stmt *stmt = NULL; 156 sqlite3_stmt *stmt = NULL;
145 157 bool ret = true;
146 memset(value, 0, sizeof (value)); 158
147 159 if (sqlite3_prepare(db->handle, (const char *)sql_property_get,
148 if (sqlite3_prepare(db, sget_property, -1, &stmt, NULL) != SQLITE_OK) 160 sizeof (sql_property_get), &stmt, NULL) != SQLITE_OK)
149 goto sqlite3_err; 161 goto sqlite3_err;
150 if (sqlite3_bind_text(stmt, 1, key, -1, NULL) != SQLITE_OK) 162 if (sqlite3_bind_text(stmt, 1, prop->key, -1, NULL) != SQLITE_OK)
151 goto sqlite3_err; 163 goto sqlite3_err;
152 164
153 switch (sqlite3_step(stmt)) { 165 switch (sqlite3_step(stmt)) {
154 case SQLITE_DONE: 166 case SQLITE_DONE:
155 /* Not found. */ 167 /* Not found. */
156 ret = NULL; 168 ret = errorf("property '%s' was not found", prop->key);
157 break; 169 break;
158 case SQLITE_ROW: 170 case SQLITE_ROW:
159 /* Found. */ 171 /* Found. */
160 snprintf(value, sizeof (value), "%s", sqlite3_column_text(stmt, 0)); 172 snprintf(prop->value, sizeof (prop->value), "%s", sqlite3_column_text(stmt, 0));
161 break; 173 break;
162 default: 174 default:
163 /* Error. */ 175 /* Error. */
164 goto sqlite3_err; 176 goto sqlite3_err;
165 } 177 }
167 sqlite3_finalize(stmt); 179 sqlite3_finalize(stmt);
168 180
169 return ret; 181 return ret;
170 182
171 sqlite3_err: 183 sqlite3_err:
184 errorf("%s", sqlite3_errmsg(db->handle));
185
172 if (stmt) 186 if (stmt)
173 sqlite3_finalize(stmt); 187 sqlite3_finalize(stmt);
174 188
175 errorf("%s", sqlite3_errmsg(db)); 189 return false;
176 190 }
177 return NULL; 191
178 } 192 bool
179 193 save_remove_property(struct save *db, const struct save_property *prop)
180 bool 194 {
181 save_remove_property(const char *key) 195 assert(db);
182 { 196 assert(prop);
183 assert(key);
184 197
185 sqlite3_stmt *stmt = NULL; 198 sqlite3_stmt *stmt = NULL;
186 199
187 if (!exec(sbegin)) 200 if (!exec(db, SQL_BEGIN))
188 return false; 201 return false;
189 if (sqlite3_prepare(db, sremove_property, -1, &stmt, NULL) != SQLITE_OK) 202 if (sqlite3_prepare(db->handle, (const char *)sql_property_remove,
190 goto sqlite3_err; 203 sizeof (sql_property_remove), &stmt, NULL) != SQLITE_OK)
191 if (sqlite3_bind_text(stmt, 1, key, -1, NULL) != SQLITE_OK) 204 goto sqlite3_err;
205 if (sqlite3_bind_text(stmt, 1, prop->key, -1, NULL) != SQLITE_OK)
192 goto sqlite3_err; 206 goto sqlite3_err;
193 if (sqlite3_step(stmt) != SQLITE_DONE) 207 if (sqlite3_step(stmt) != SQLITE_DONE)
194 goto sqlite3_err; 208 goto sqlite3_err;
195 209
196 sqlite3_finalize(stmt); 210 sqlite3_finalize(stmt);
197 211
198 return exec(scommit); 212 return exec(db, SQL_COMMIT);
199 213
200 sqlite3_err: 214 sqlite3_err:
215 errorf("%s", sqlite3_errmsg(db->handle));
216
201 if (stmt) 217 if (stmt)
202 sqlite3_finalize(stmt); 218 sqlite3_finalize(stmt);
203 219
204 errorf("%s", sqlite3_errmsg(db)); 220 exec(db, SQL_ROLLBACK);
205 221
206 return false; 222 return false;
207 } 223 }
208 224
209 void 225 void
210 save_finish(void) 226 save_finish(struct save *db)
211 { 227 {
212 if (db) 228 assert(db);
213 sqlite3_close(db); 229
214 } 230 if (db->handle)
231 sqlite3_close(db->handle);
232
233 memset(db, 0, sizeof (*db));
234 }