Mercurial > molko
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 } |