1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /*
3 * Copyright © 2011 Igalia S.L.
4 *
5 * This file is part of Epiphany.
6 *
7 * Epiphany is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * Epiphany is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with Epiphany. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "config.h"
22 #include "ephy-sqlite-connection.h"
23
24 #include "ephy-lib-type-builtins.h"
25
26 #include <errno.h>
27 #include <glib/gstdio.h>
28 #include <sqlite3.h>
29
30 struct _EphySQLiteConnection {
31 GObject parent_instance;
32
33 char *database_path;
34 sqlite3 *database;
35 EphySQLiteConnectionMode mode;
36 };
37
38 G_DEFINE_TYPE (EphySQLiteConnection, ephy_sqlite_connection, G_TYPE_OBJECT);
39
40 enum {
41 PROP_0,
42 PROP_MODE,
43 PROP_DATABASE_PATH,
44 LAST_PROP
45 };
46
47 static GParamSpec *obj_properties[LAST_PROP];
48
49 static void
ephy_sqlite_connection_finalize(GObject * self)50 ephy_sqlite_connection_finalize (GObject *self)
51 {
52 g_free (EPHY_SQLITE_CONNECTION (self)->database_path);
53 ephy_sqlite_connection_close (EPHY_SQLITE_CONNECTION (self));
54 G_OBJECT_CLASS (ephy_sqlite_connection_parent_class)->finalize (self);
55 }
56
57 static void
ephy_sqlite_connection_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)58 ephy_sqlite_connection_set_property (GObject *object,
59 guint property_id,
60 const GValue *value,
61 GParamSpec *pspec)
62 {
63 EphySQLiteConnection *self = EPHY_SQLITE_CONNECTION (object);
64
65 switch (property_id) {
66 case PROP_MODE:
67 self->mode = g_value_get_enum (value);
68 break;
69 case PROP_DATABASE_PATH:
70 self->database_path = g_value_dup_string (value);
71 break;
72 default:
73 G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
74 break;
75 }
76 }
77
78 static void
ephy_sqlite_connection_class_init(EphySQLiteConnectionClass * klass)79 ephy_sqlite_connection_class_init (EphySQLiteConnectionClass *klass)
80 {
81 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
82
83 gobject_class->finalize = ephy_sqlite_connection_finalize;
84 gobject_class->set_property = ephy_sqlite_connection_set_property;
85
86 obj_properties[PROP_MODE] =
87 g_param_spec_enum ("mode",
88 "SQLite connection mode",
89 "Whether the SQLite connection is read-only or writable",
90 EPHY_TYPE_SQ_LITE_CONNECTION_MODE,
91 EPHY_SQLITE_CONNECTION_MODE_READWRITE,
92 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
93
94 obj_properties[PROP_DATABASE_PATH] =
95 g_param_spec_string ("database-path",
96 "Database path",
97 "The path of the SQLite database file",
98 NULL,
99 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
100
101 g_object_class_install_properties (gobject_class, LAST_PROP, obj_properties);
102 }
103
104 static void
ephy_sqlite_connection_init(EphySQLiteConnection * self)105 ephy_sqlite_connection_init (EphySQLiteConnection *self)
106 {
107 self->database = NULL;
108 }
109
110 GQuark
ephy_sqlite_error_quark(void)111 ephy_sqlite_error_quark (void)
112 {
113 return g_quark_from_static_string ("ephy-sqlite");
114 }
115
116 static void
set_error_from_string(const char * string,GError ** error)117 set_error_from_string (const char *string,
118 GError **error)
119 {
120 if (error)
121 *error = g_error_new_literal (ephy_sqlite_error_quark (), 0, string);
122 }
123
124 EphySQLiteConnection *
ephy_sqlite_connection_new(EphySQLiteConnectionMode mode,const char * database_path)125 ephy_sqlite_connection_new (EphySQLiteConnectionMode mode,
126 const char *database_path)
127 {
128 return EPHY_SQLITE_CONNECTION (g_object_new (EPHY_TYPE_SQLITE_CONNECTION,
129 "mode", mode,
130 "database-path", database_path,
131 NULL));
132 }
133
134 gboolean
ephy_sqlite_connection_open(EphySQLiteConnection * self,GError ** error)135 ephy_sqlite_connection_open (EphySQLiteConnection *self,
136 GError **error)
137 {
138 if (self->database) {
139 set_error_from_string ("Connection already open.", error);
140 return FALSE;
141 }
142
143 if (sqlite3_open_v2 (self->database_path,
144 &self->database,
145 self->mode == EPHY_SQLITE_CONNECTION_MODE_MEMORY ? SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_MEMORY
146 : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
147 NULL) != SQLITE_OK) {
148 ephy_sqlite_connection_get_error (self, error);
149 self->database = NULL;
150 return FALSE;
151 }
152
153 /* Create a local copy of current database for in memory usage */
154 if (self->mode == EPHY_SQLITE_CONNECTION_MODE_MEMORY) {
155 sqlite3 *init_db;
156 int rc;
157
158 rc = sqlite3_open_v2 (self->database_path, &init_db, SQLITE_OPEN_READONLY, 0);
159 if (rc == SQLITE_OK) {
160 sqlite3_backup *backup;
161
162 backup = sqlite3_backup_init (self->database, "main", init_db, "main");
163 rc = sqlite3_backup_step (backup, -1);
164
165 if (rc != SQLITE_DONE)
166 g_warning ("Failed to copy history to in-memory database: %s", sqlite3_errstr (rc));
167
168 sqlite3_backup_finish (backup);
169 }
170
171 sqlite3_close (init_db);
172 } else {
173 ephy_sqlite_connection_execute (self, "PRAGMA main.journal_mode=WAL", error);
174 ephy_sqlite_connection_execute (self, "PRAGMA main.synchronous=NORMAL", error);
175 ephy_sqlite_connection_execute (self, "PRAGMA main.cache_size=10000", error);
176 }
177
178 return TRUE;
179 }
180
181 void
ephy_sqlite_connection_close(EphySQLiteConnection * self)182 ephy_sqlite_connection_close (EphySQLiteConnection *self)
183 {
184 if (self->database) {
185 sqlite3_close (self->database);
186 self->database = NULL;
187 }
188 }
189
190 void
ephy_sqlite_connection_delete_database(EphySQLiteConnection * self)191 ephy_sqlite_connection_delete_database (EphySQLiteConnection *self)
192 {
193 g_autofree char *journal = NULL;
194 g_autofree char *shm = NULL;
195
196 g_assert (EPHY_IS_SQLITE_CONNECTION (self));
197
198 if (g_file_test (self->database_path, G_FILE_TEST_EXISTS) && g_unlink (self->database_path) == -1)
199 g_warning ("Failed to delete database at %s: %s", self->database_path, g_strerror (errno));
200
201 journal = g_strdup_printf ("%s-wal", self->database_path);
202 if (g_file_test (journal, G_FILE_TEST_EXISTS) && g_unlink (journal) == -1)
203 g_warning ("Failed to delete database journal at %s: %s", journal, g_strerror (errno));
204
205 shm = g_strdup_printf ("%s-shm", self->database_path);
206 if (g_file_test (shm, G_FILE_TEST_EXISTS) && g_unlink (shm) == -1)
207 g_warning ("Failed to delete database shm at %s: %s", shm, g_strerror (errno));
208 }
209
210 void
ephy_sqlite_connection_get_error(EphySQLiteConnection * self,GError ** error)211 ephy_sqlite_connection_get_error (EphySQLiteConnection *self,
212 GError **error)
213 {
214 if (error)
215 *error = g_error_new_literal (ephy_sqlite_error_quark (), sqlite3_errcode (self->database), sqlite3_errmsg (self->database));
216 }
217
218 gboolean
ephy_sqlite_connection_execute(EphySQLiteConnection * self,const char * sql,GError ** error)219 ephy_sqlite_connection_execute (EphySQLiteConnection *self,
220 const char *sql,
221 GError **error)
222 {
223 if (self->database == NULL) {
224 set_error_from_string ("Connection not open.", error);
225 return FALSE;
226 }
227
228 if (sqlite3_exec (self->database, sql, NULL, NULL, NULL) != SQLITE_OK) {
229 ephy_sqlite_connection_get_error (self, error);
230 return FALSE;
231 }
232 return TRUE;
233 }
234
235 EphySQLiteStatement *
ephy_sqlite_connection_create_statement(EphySQLiteConnection * self,const char * sql,GError ** error)236 ephy_sqlite_connection_create_statement (EphySQLiteConnection *self,
237 const char *sql,
238 GError **error)
239 {
240 sqlite3_stmt *prepared_statement;
241
242 if (self->database == NULL) {
243 set_error_from_string ("Connection not open.", error);
244 return NULL;
245 }
246
247 if (sqlite3_prepare_v2 (self->database, sql, -1, &prepared_statement, NULL) != SQLITE_OK) {
248 ephy_sqlite_connection_get_error (self, error);
249 return NULL;
250 }
251
252 return EPHY_SQLITE_STATEMENT (g_object_new (EPHY_TYPE_SQLITE_STATEMENT,
253 "prepared-statement", prepared_statement,
254 "connection", self,
255 NULL));
256 }
257
258 gint64
ephy_sqlite_connection_get_last_insert_id(EphySQLiteConnection * self)259 ephy_sqlite_connection_get_last_insert_id (EphySQLiteConnection *self)
260 {
261 return sqlite3_last_insert_rowid (self->database);
262 }
263
264 void
ephy_sqlite_connection_enable_foreign_keys(EphySQLiteConnection * self)265 ephy_sqlite_connection_enable_foreign_keys (EphySQLiteConnection *self)
266 {
267 GError *error = NULL;
268
269 g_assert (EPHY_IS_SQLITE_CONNECTION (self));
270
271 ephy_sqlite_connection_execute (self, "PRAGMA foreign_keys=ON", &error);
272 if (error) {
273 g_warning ("Failed to enable foreign keys pragma: %s", error->message);
274 g_error_free (error);
275 }
276 }
277
278 gboolean
ephy_sqlite_connection_begin_transaction(EphySQLiteConnection * self,GError ** error)279 ephy_sqlite_connection_begin_transaction (EphySQLiteConnection *self,
280 GError **error)
281 {
282 return ephy_sqlite_connection_execute (self, "BEGIN TRANSACTION", error);
283 }
284
285 gboolean
ephy_sqlite_connection_commit_transaction(EphySQLiteConnection * self,GError ** error)286 ephy_sqlite_connection_commit_transaction (EphySQLiteConnection *self,
287 GError **error)
288 {
289 return ephy_sqlite_connection_execute (self, "COMMIT", error);
290 }
291
292 gboolean
ephy_sqlite_connection_table_exists(EphySQLiteConnection * self,const char * table_name)293 ephy_sqlite_connection_table_exists (EphySQLiteConnection *self,
294 const char *table_name)
295 {
296 GError *error = NULL;
297 gboolean table_exists = FALSE;
298
299 EphySQLiteStatement *statement = ephy_sqlite_connection_create_statement (self,
300 "SELECT COUNT(type) FROM sqlite_master WHERE type='table' and name=?", &error);
301 if (error) {
302 g_warning ("Could not detect table existence: %s", error->message);
303 g_error_free (error);
304 return FALSE;
305 }
306
307 ephy_sqlite_statement_bind_string (statement, 0, table_name, &error);
308 if (error) {
309 g_object_unref (statement);
310 g_warning ("Could not detect table existence: %s", error->message);
311 g_error_free (error);
312 return FALSE;
313 }
314
315 ephy_sqlite_statement_step (statement, &error);
316 if (error) {
317 g_object_unref (statement);
318 g_warning ("Could not detect table existence: %s", error->message);
319 g_error_free (error);
320 return FALSE;
321 }
322
323 table_exists = ephy_sqlite_statement_get_column_as_int (statement, 0);
324 g_object_unref (statement);
325 return table_exists;
326 }
327