[almanah] Use iterators for large database queries
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [almanah] Use iterators for large database queries
- Date: Sat, 8 May 2010 20:14:36 +0000 (UTC)
commit fc00ec9948692fffb966eaaa75bfb6d073932f5b
Author: Philip Withnall <philip tecnocode co uk>
Date: Sat May 8 21:13:33 2010 +0100
Use iterators for large database queries
Use iterators for almanah_storage_manager_get_entries() and
almanah_storage_manager_search_entries(), rather than allocating all the
results at once into a list. This saves memory and could be used to make the
interface more responsive for long-running operations.
src/import-export-dialog.c | 23 ++--
src/search-dialog.c | 20 ++--
src/storage-manager.c | 272 +++++++++++++++++++++++++++++---------------
src/storage-manager.h | 15 ++-
4 files changed, 214 insertions(+), 116 deletions(-)
---
diff --git a/src/import-export-dialog.c b/src/import-export-dialog.c
index ef98d62..b962207 100644
--- a/src/import-export-dialog.c
+++ b/src/import-export-dialog.c
@@ -422,8 +422,9 @@ static gboolean
export_text_files (AlmanahImportExportDialog *self, GError **error)
{
AlmanahImportExportDialogPrivate *priv = self->priv;
+ AlmanahStorageManagerIter iter;
GFile *folder;
- GSList *entries;
+ AlmanahEntry *entry;
GtkTextBuffer *buffer;
gboolean success = FALSE;
GError *child_error = NULL;
@@ -435,11 +436,9 @@ export_text_files (AlmanahImportExportDialog *self, GError **error)
/* Build a text buffer to use when getting all the entries */
buffer = gtk_text_buffer_new (NULL);
- /* Get the list of entries */
- entries = almanah_storage_manager_get_entries (almanah->storage_manager);
-
- for (; entries != NULL; entries = g_slist_delete_link (entries, entries)) {
- AlmanahEntry *entry = ALMANAH_ENTRY (entries->data);
+ /* Iterate through the entries */
+ almanah_storage_manager_iter_init (&iter);
+ while ((entry = almanah_storage_manager_get_entries (almanah->storage_manager, &iter)) != NULL) {
GDate date;
gchar *filename, *content;
GFile *file;
@@ -499,8 +498,10 @@ import_database (AlmanahImportExportDialog *self, AlmanahImportResultsDialog *re
GFileInfo *file_info;
gchar *path;
const gchar *display_name;
- GSList *entries, *i, *definitions;
+ GSList *i, *definitions;
+ AlmanahEntry *entry;
AlmanahStorageManager *database;
+ AlmanahStorageManagerIter iter;
AlmanahImportExportDialogPrivate *priv = self->priv;
/* Get the database file to import */
@@ -523,13 +524,12 @@ import_database (AlmanahImportExportDialog *self, AlmanahImportResultsDialog *re
return FALSE;
}
- /* Query for every entry */
- entries = almanah_storage_manager_get_entries (database);
- for (i = entries; i != NULL; i = i->next) {
+ /* Iterate through every entry */
+ almanah_storage_manager_iter_init (&iter);
+ while ((entry = almanah_storage_manager_get_entries (database, &iter)) != NULL) {
GDate date;
gchar *message = NULL;
AlmanahImportStatus status;
- AlmanahEntry *entry = ALMANAH_ENTRY (i->data);
almanah_entry_get_date (entry, &date);
@@ -539,7 +539,6 @@ import_database (AlmanahImportExportDialog *self, AlmanahImportResultsDialog *re
g_object_unref (entry);
}
- g_slist_free (entries);
/* Query for every definition */
definitions = almanah_storage_manager_get_definitions (database);
diff --git a/src/search-dialog.c b/src/search-dialog.c
index ddcd6db..2cc8fbe 100644
--- a/src/search-dialog.c
+++ b/src/search-dialog.c
@@ -140,26 +140,28 @@ sd_response_cb (GtkDialog *dialog, gint response_id, AlmanahSearchDialog *search
void
sd_search_button_clicked_cb (GtkButton *self, AlmanahSearchDialog *search_dialog)
{
- GSList *results, *i;
- GtkTreeIter iter;
+ AlmanahEntry *entry;
+ AlmanahStorageManagerIter iter;
AlmanahSearchDialogPrivate *priv = search_dialog->priv;
+ const gchar *search_string = gtk_entry_get_text (priv->sd_search_entry);
+ /* Clear the results store of previous search results first */
gtk_list_store_clear (search_dialog->priv->sd_results_store);
- results = almanah_storage_manager_search_entries (almanah->storage_manager, gtk_entry_get_text (priv->sd_search_entry));
- for (i = results; i != NULL; i = i->next) {
- AlmanahEntry *entry;
+ /* Search over all entries */
+ almanah_storage_manager_iter_init (&iter);
+ while ((entry = almanah_storage_manager_search_entries (almanah->storage_manager, search_string, &iter)) != NULL) {
GDate date;
gchar formatted_date[100];
+ GtkTreeIter tree_iter;
- entry = ALMANAH_ENTRY (i->data);
almanah_entry_get_date (entry, &date);
/* Translators: This is a strftime()-format string for the dates displayed in search results. */
g_date_strftime (formatted_date, sizeof (formatted_date), _("%A, %e %B %Y"), &date);
- gtk_list_store_append (priv->sd_results_store, &iter);
- gtk_list_store_set (priv->sd_results_store, &iter,
+ gtk_list_store_append (priv->sd_results_store, &tree_iter);
+ gtk_list_store_set (priv->sd_results_store, &tree_iter,
0, g_date_get_day (&date),
1, g_date_get_month (&date),
2, g_date_get_year (&date),
@@ -169,8 +171,6 @@ sd_search_button_clicked_cb (GtkButton *self, AlmanahSearchDialog *search_dialog
g_object_unref (entry);
}
-
- g_slist_free (results);
}
static void
diff --git a/src/storage-manager.c b/src/storage-manager.c
index e38f2e2..7085f9a 100644
--- a/src/storage-manager.c
+++ b/src/storage-manager.c
@@ -861,144 +861,232 @@ almanah_storage_manager_set_entry (AlmanahStorageManager *self, AlmanahEntry *en
}
/**
+ * almanah_storage_manager_iter_init:
+ * @iter: an #AlmanahStorageManagerIter to initialise
+ *
+ * Initialises the given iterator so it can be used by #AlmanahStorageManager functions. Typically, initialisers are allocated on the stack, so need
+ * explicitly initialising before being passed to functions such as almanah_storage_manager_get_entries().
+ *
+ * Since: 0.8.0
+ **/
+void
+almanah_storage_manager_iter_init (AlmanahStorageManagerIter *iter)
+{
+ g_return_if_fail (iter != NULL);
+
+ iter->statement = NULL;
+ iter->user_data = NULL;
+ iter->finished = FALSE;
+}
+
+typedef struct {
+ GtkTextBuffer *text_buffer;
+ gchar *search_string;
+} SearchData;
+
+/**
* almanah_storage_manager_search_entries:
* @self: an #AlmanahStorageManager
* @search_string: string for which to search in entry content
- *
+ * TODO
* Searches for @search_string in the content in entries in the database, and returns the results. If there are no results, %NULL will be returned.
*
* The results are returned in descending date order.
*
* Return value: a #GSList of #AlmanahEntry<!-- -->s, or %NULL; unref elements with g_object_unref(); free list with g_slist_free()
**/
-GSList *
-almanah_storage_manager_search_entries (AlmanahStorageManager *self, const gchar *search_string)
+AlmanahEntry *
+almanah_storage_manager_search_entries (AlmanahStorageManager *self, const gchar *search_string, AlmanahStorageManagerIter *iter)
{
sqlite3_stmt *statement;
GtkTextBuffer *text_buffer;
- GSList *matches = NULL;
- /* Prepare the statement. Query in ascending date order, and then reverse the results by prepending them to the list as we build it,
- * rather than appending them. */
- if (sqlite3_prepare_v2 (self->priv->connection,
- "SELECT content, day, month, year, is_important, edited_day, edited_month, edited_year FROM entries "
- "ORDER BY year ASC, month ASC, day ASC", -1,
- &statement, NULL) != SQLITE_OK) {
+ g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), NULL);
+ g_return_val_if_fail (iter != NULL, NULL);
+ g_return_val_if_fail (iter->statement != NULL || search_string != NULL, NULL);
+ g_return_val_if_fail (iter->statement == NULL || iter->user_data != NULL, NULL);
+
+ if (iter->finished == TRUE)
return NULL;
+
+ if (iter->statement == NULL) {
+ SearchData *data;
+
+ /* Prepare the statement. */
+ if (sqlite3_prepare_v2 (self->priv->connection,
+ "SELECT content, day, month, year, is_important, edited_day, edited_month, edited_year FROM entries "
+ "ORDER BY year DESC, month DESC, day DESC", -1,
+ (sqlite3_stmt**) &(iter->statement), NULL) != SQLITE_OK) {
+ return NULL;
+ }
+
+ /* Set up persistent data for the operation */
+ data = g_slice_new (SearchData);
+ data->text_buffer = gtk_text_buffer_new (NULL);
+ data->search_string = g_strdup (search_string);
+ iter->user_data = data;
}
- text_buffer = gtk_text_buffer_new (NULL);
+ statement = iter->statement;
+ text_buffer = ((SearchData*) iter->user_data)->text_buffer;
+ search_string = ((SearchData*) iter->user_data)->search_string;
/* Execute the statement */
- while (sqlite3_step (statement) == SQLITE_ROW) {
- AlmanahEntry *entry;
- GDate date, last_edited;
- GtkTextIter iter;
-
- g_date_set_dmy (&date, sqlite3_column_int (statement, 1), sqlite3_column_int (statement, 2), sqlite3_column_int (statement, 3));
- entry = almanah_entry_new (&date);
- almanah_entry_set_data (entry, sqlite3_column_blob (statement, 0), sqlite3_column_bytes (statement, 0));
- almanah_entry_set_is_important (entry, (sqlite3_column_int (statement, 4) == 1) ? TRUE : FALSE);
-
- if (g_date_valid_dmy (sqlite3_column_int (statement, 2),
- sqlite3_column_int (statement, 3),
- sqlite3_column_int (statement, 4)) == TRUE) {
- g_date_set_dmy (&last_edited,
- sqlite3_column_int (statement, 5),
- sqlite3_column_int (statement, 6),
- sqlite3_column_int (statement, 7));
- almanah_entry_set_last_edited (entry, &last_edited);
- }
+ switch (sqlite3_step (statement)) {
+ case SQLITE_ROW: {
+ AlmanahEntry *entry;
+ GDate date, last_edited;
+ GtkTextIter text_iter;
+
+ g_date_set_dmy (&date,
+ sqlite3_column_int (statement, 1),
+ sqlite3_column_int (statement, 2),
+ sqlite3_column_int (statement, 3));
+
+ entry = almanah_entry_new (&date);
+ almanah_entry_set_data (entry, sqlite3_column_blob (statement, 0), sqlite3_column_bytes (statement, 0));
+ almanah_entry_set_is_important (entry, (sqlite3_column_int (statement, 4) == 1) ? TRUE : FALSE);
+
+ if (g_date_valid_dmy (sqlite3_column_int (statement, 2),
+ sqlite3_column_int (statement, 3),
+ sqlite3_column_int (statement, 4)) == TRUE) {
+ g_date_set_dmy (&last_edited,
+ sqlite3_column_int (statement, 5),
+ sqlite3_column_int (statement, 6),
+ sqlite3_column_int (statement, 7));
+ almanah_entry_set_last_edited (entry, &last_edited);
+ }
+
+ /* Deserialise the entry into our buffer */
+ gtk_text_buffer_set_text (text_buffer, "", 0);
+ if (almanah_entry_get_content (entry, text_buffer, TRUE, NULL) == FALSE) {
+ /* Error: return the next entry instead */
+ g_object_unref (entry);
+ g_warning (_("Error deserializing entry into buffer while searching."));
+ return almanah_storage_manager_search_entries (self, NULL, iter);
+ }
- /* Deserialise the entry into our buffer */
- gtk_text_buffer_set_text (text_buffer, "", 0);
- if (almanah_entry_get_content (entry, text_buffer, TRUE, NULL) == FALSE) {
+ /* Perform the search */
+ gtk_text_buffer_get_start_iter (text_buffer, &text_iter);
+ if (gtk_text_iter_forward_search (&text_iter, search_string, GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY,
+ NULL, NULL, NULL) == TRUE) {
+ /* A match was found! */
+ return entry;
+ }
+
+ /* Free stuff up and return the next match instead */
g_object_unref (entry);
- g_warning (_("Error deserializing entry into buffer while searching."));
- continue;
+ return almanah_storage_manager_search_entries (self, NULL, iter);
}
-
- /* Perform the search */
- gtk_text_buffer_get_start_iter (text_buffer, &iter);
- if (gtk_text_iter_forward_search (&iter, search_string, GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY,
- NULL, NULL, NULL) == TRUE) {
- /* A match was found */
- matches = g_slist_prepend (matches, g_object_ref (entry));
+ case SQLITE_DONE:
+ case SQLITE_ERROR:
+ case SQLITE_BUSY:
+ case SQLITE_LOCKED:
+ case SQLITE_NOMEM:
+ case SQLITE_IOERR:
+ case SQLITE_CORRUPT:
+ default: {
+ /* Clean up the iter and return */
+ sqlite3_finalize (statement);
+ iter->statement = NULL;
+ g_object_unref (((SearchData*) iter->user_data)->text_buffer);
+ g_free (((SearchData*) iter->user_data)->search_string);
+ g_slice_free (SearchData, iter->user_data);
+ iter->user_data = NULL;
+ iter->finished = TRUE;
+
+ return NULL;
}
-
- /* Free stuff up and continue */
- g_object_unref (entry);
}
- sqlite3_finalize (statement);
- g_object_unref (text_buffer);
-
- return matches;
+ g_assert_not_reached ();
}
/**
* almanah_storage_manager_get_entries:
* @self: an #AlmanahStorageManager
+ * @iter: an #AlmanahStorageManagerIter to keep track of the query
*
- * Returns a list of all #AlmanahEntry<!-- -->s in the database.
+ * Iterates through every single #AlmanahEntry in the database using the given #AlmanahStorageManagerIter. @iter should be initialised with
+ * almanah_storage_manager_iter_init() and passed to almanah_storage_manager_get_entries(). This will then return an #AlmanahEntry every time it's
+ * called with the same @iter until it reaches the end of the result set, when it will return %NULL. It will also finish and return %NULL on error.
*
- * Return value: a #GSList of #AlmanahEntry<!-- -->s, or %NULL; unref elements with g_object_unref(); free list with g_slist_free()
+ * Calling functions must get every result from the result set (i.e. not stop calling almanah_storage_manager_get_entries() until it returns %NULL).
+ *
+ * Return value: an #AlmanahEntry, or %NULL; unref with g_object_unref()
**/
-GSList *
-almanah_storage_manager_get_entries (AlmanahStorageManager *self)
+AlmanahEntry *
+almanah_storage_manager_get_entries (AlmanahStorageManager *self, AlmanahStorageManagerIter *iter)
{
- GSList *entries = NULL;
- int result;
sqlite3_stmt *statement;
+ g_return_val_if_fail (ALMANAH_IS_STORAGE_MANAGER (self), NULL);
+ g_return_val_if_fail (iter != NULL, NULL);
+
/* Just as with almanah_storage_manager_get_entry(), it's necessary to avoid our nice SQLite interface here. It's probably more efficient to
* avoid it anyway. */
- /* Prepare the statement */
- if (sqlite3_prepare_v2 (self->priv->connection,
- "SELECT content, is_important, day, month, year, edited_day, edited_month, edited_year FROM entries", -1,
- &statement, NULL) != SQLITE_OK) {
+ if (iter->finished == TRUE)
return NULL;
- }
- /* Execute the statement */
- while ((result = sqlite3_step (statement)) == SQLITE_ROW) {
- GDate date, last_edited;
- AlmanahEntry *entry;
-
- g_date_set_dmy (&date,
- sqlite3_column_int (statement, 2),
- sqlite3_column_int (statement, 3),
- sqlite3_column_int (statement, 4));
-
- /* Get the data */
- entry = almanah_entry_new (&date);
- almanah_entry_set_data (entry, sqlite3_column_blob (statement, 0), sqlite3_column_bytes (statement, 0));
- almanah_entry_set_is_important (entry, (sqlite3_column_int (statement, 1) == 1) ? TRUE : FALSE);
-
- if (g_date_valid_dmy (sqlite3_column_int (statement, 2),
- sqlite3_column_int (statement, 3),
- sqlite3_column_int (statement, 4)) == TRUE) {
- g_date_set_dmy (&last_edited,
- sqlite3_column_int (statement, 5),
- sqlite3_column_int (statement, 6),
- sqlite3_column_int (statement, 7));
- almanah_entry_set_last_edited (entry, &last_edited);
+ if (iter->statement == NULL) {
+ /* Prepare the statement */
+ if (sqlite3_prepare_v2 (self->priv->connection,
+ "SELECT content, is_important, day, month, year, edited_day, edited_month, edited_year FROM entries", -1,
+ (sqlite3_stmt**) &(iter->statement), NULL) != SQLITE_OK) {
+ return NULL;
}
-
- entries = g_slist_prepend (entries, entry);
}
- sqlite3_finalize (statement);
+ statement = iter->statement;
- /* Check for errors */
- if (result != SQLITE_DONE) {
- g_slist_foreach (entries, (GFunc) g_object_unref, NULL);
- g_slist_free (entries);
- return NULL;
+ /* Execute the statement */
+ switch (sqlite3_step (statement)) {
+ case SQLITE_ROW: {
+ GDate date, last_edited;
+ AlmanahEntry *entry;
+
+ /* Process the row */
+ g_date_set_dmy (&date,
+ sqlite3_column_int (statement, 2),
+ sqlite3_column_int (statement, 3),
+ sqlite3_column_int (statement, 4));
+
+ /* Get the data */
+ entry = almanah_entry_new (&date);
+ almanah_entry_set_data (entry, sqlite3_column_blob (statement, 0), sqlite3_column_bytes (statement, 0));
+ almanah_entry_set_is_important (entry, (sqlite3_column_int (statement, 1) == 1) ? TRUE : FALSE);
+
+ if (g_date_valid_dmy (sqlite3_column_int (statement, 2),
+ sqlite3_column_int (statement, 3),
+ sqlite3_column_int (statement, 4)) == TRUE) {
+ g_date_set_dmy (&last_edited,
+ sqlite3_column_int (statement, 5),
+ sqlite3_column_int (statement, 6),
+ sqlite3_column_int (statement, 7));
+ almanah_entry_set_last_edited (entry, &last_edited);
+ }
+
+ return entry;
+ }
+ case SQLITE_DONE:
+ case SQLITE_ERROR:
+ case SQLITE_BUSY:
+ case SQLITE_LOCKED:
+ case SQLITE_NOMEM:
+ case SQLITE_IOERR:
+ case SQLITE_CORRUPT:
+ default: {
+ /* Clean up the iter and return */
+ sqlite3_finalize (statement);
+ iter->statement = NULL;
+ iter->finished = TRUE;
+
+ return NULL;
+ }
}
- return g_slist_reverse (entries);
+ g_assert_not_reached ();
}
/* NOTE: Free results with g_free. Return value is 0-based. */
diff --git a/src/storage-manager.h b/src/storage-manager.h
index ad09d08..324c0c5 100644
--- a/src/storage-manager.h
+++ b/src/storage-manager.h
@@ -58,6 +58,13 @@ typedef enum {
ALMANAH_STORAGE_MANAGER_ERROR_BAD_VERSION
} AlmanahStorageManagerError;
+typedef struct {
+ /*< private >*/
+ gpointer /*sqlite3_stmt **/ statement;
+ gboolean finished; /* TRUE if the query is finished and the iter has been cleaned up */
+ gpointer user_data; /* to be used by #AlmanahStorageManager functions which need to associate data with a statement */
+} AlmanahStorageManagerIter;
+
typedef gint (*AlmanahQueryCallback) (gpointer user_data, gint columns, gchar **data, gchar **column_names);
typedef struct {
@@ -82,8 +89,12 @@ gboolean almanah_storage_manager_get_statistics (AlmanahStorageManager *self, gu
gboolean almanah_storage_manager_entry_exists (AlmanahStorageManager *self, GDate *date);
AlmanahEntry *almanah_storage_manager_get_entry (AlmanahStorageManager *self, GDate *date);
gboolean almanah_storage_manager_set_entry (AlmanahStorageManager *self, AlmanahEntry *entry);
-GSList *almanah_storage_manager_search_entries (AlmanahStorageManager *self, const gchar *search_string) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
-GSList *almanah_storage_manager_get_entries (AlmanahStorageManager *self) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
+
+void almanah_storage_manager_iter_init (AlmanahStorageManagerIter *iter);
+AlmanahEntry *almanah_storage_manager_search_entries (AlmanahStorageManager *self, const gchar *search_string,
+ AlmanahStorageManagerIter *iter) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
+AlmanahEntry *almanah_storage_manager_get_entries (AlmanahStorageManager *self,
+ AlmanahStorageManagerIter *iter) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
gboolean *almanah_storage_manager_get_month_marked_days (AlmanahStorageManager *self, GDateYear year, GDateMonth month, guint *num_days);
gboolean *almanah_storage_manager_get_month_important_days (AlmanahStorageManager *self, GDateYear year, GDateMonth month, guint *num_days);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]