summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Otte <otte@gnome.org>2009-07-08 13:13:38 +0200
committerBenjamin Otte <otte@gnome.org>2009-07-08 13:13:38 +0200
commit9e969883459bbc31c8709ea76dc741d6c1e6d64b (patch)
tree66c3d4fca3fdf32bf308786bc398cde5b8f9fb50
parent982fa0b5800de513bd9e6bb33245b6aad611bf48 (diff)
add custom functions "match", "markup" and "regexp"
This also allows using match and regexp in where clauses.
-rw-r--r--ephy-history.c189
1 files changed, 188 insertions, 1 deletions
diff --git a/ephy-history.c b/ephy-history.c
index 26d99c5..9d8d5b6 100644
--- a/ephy-history.c
+++ b/ephy-history.c
@@ -22,6 +22,7 @@
#endif
#include <sqlite3.h>
+#include <string.h>
#include "ephy-history.h"
@@ -173,11 +174,174 @@ check_sqlite_result (EphyHistory * history,
return FALSE;
}
+static void
+_g_string_append_escaped (GString *string,
+ const char *text,
+ gssize len)
+{
+ char *s = g_markup_escape_text (text, len);
+ g_string_append (string, s);
+ g_free (s);
+}
+
+static void
+custom_function_markup (sqlite3_context *context,
+ int argc,
+ sqlite3_value** argv)
+{
+ const char *text;
+ GRegex *regex;
+ GString *string;
+ GMatchInfo *match;
+ int i, current, end;
+
+ if (argc < 2)
+ {
+ sqlite3_result_null (context);
+ return;
+ }
+
+ text = (const char *) sqlite3_value_text (argv[0]);
+ if (text == NULL || text[0] == 0)
+ {
+ sqlite3_result_null (context);
+ return;
+ }
+
+ regex = sqlite3_get_auxdata (context, 0);
+ if (regex == NULL)
+ {
+ regex = g_regex_new (text,
+ G_REGEX_CASELESS | G_REGEX_OPTIMIZE,
+ G_REGEX_MATCH_NOTEMPTY,
+ NULL);
+
+ sqlite3_set_auxdata (context, 0, regex, (GDestroyNotify) g_regex_unref);
+ }
+
+ string = g_string_new ("");
+ current = end = 0;
+ for (i = 1; i < argc; i++)
+ {
+ const char *argument = (const char *) sqlite3_value_text (argv[1]);
+ if (i > 1)
+ g_string_append_c (string, '\n');
+ if (!g_regex_match (regex,
+ argument,
+ G_REGEX_MATCH_NOTEMPTY,
+ &match))
+ {
+ _g_string_append_escaped (string, argument, -1);
+ continue;
+ }
+
+ do {
+ int from, to;
+ if (!g_match_info_fetch_pos (match, 0, &from, &to))
+ continue;
+ g_assert (from >= 0 && to >= 0);
+ if (from > end)
+ {
+ if (end > current)
+ {
+ g_string_append (string, "<b>");
+ _g_string_append_escaped (string, argument + current, end - current);
+ g_string_append (string, "</b>");
+ }
+ _g_string_append_escaped (string, argument + end, from - end);
+ current = from;
+ }
+ end = MAX (end, to);
+ } while (g_match_info_next (match, NULL));
+ if (end > current)
+ {
+ g_string_append (string, "<b>");
+ _g_string_append_escaped (string, argument + current, end - current);
+ g_string_append (string, "</b>");
+ }
+ _g_string_append_escaped (string, argument + end, -1);
+ g_match_info_free (match);
+ }
+
+ sqlite3_result_text (context,
+ string->str,
+ string->len,
+ g_free);
+ g_string_free (string, FALSE);
+}
+
+static void
+custom_function_match (sqlite3_context *context,
+ int argc,
+ sqlite3_value** argv)
+{
+ const char *has_match, *haystack, *needle;
+
+ haystack = (char *) sqlite3_value_text (argv[1]);
+ needle = (char *) sqlite3_value_text (argv[0]);
+#if 0
+ /*
+ * - requires _GNU_SOURCE when including string.h
+ * - the version below is 50% faster (huh?)
+ */
+ has_match = strcasestr (haystack, needle);
+#else
+ {
+ gsize needle_len = strlen (needle);
+ char find[3] = { g_ascii_tolower (*needle), g_ascii_toupper (*needle), 0 };
+ has_match = haystack;
+ while ((has_match = strpbrk (has_match, find)))
+ {
+ if (strncasecmp (has_match, needle, needle_len) == 0)
+ break;
+ has_match++;
+ }
+ }
+#endif
+ sqlite3_result_int (context, has_match ? 1 : 0);
+}
+
+static void
+custom_function_regexp (sqlite3_context *context,
+ int argc,
+ sqlite3_value** argv)
+{
+ GRegex *regex;
+ gboolean has_match;
+
+ regex = sqlite3_get_auxdata (context, 0);
+ if (regex == NULL)
+ {
+ regex = g_regex_new ((char *) sqlite3_value_text (argv[0]),
+ G_REGEX_CASELESS | G_REGEX_OPTIMIZE,
+ G_REGEX_MATCH_NOTEMPTY,
+ NULL);
+
+ sqlite3_set_auxdata (context, 0, regex, (GDestroyNotify) g_regex_unref);
+ }
+
+ has_match = g_regex_match (regex,
+ (char *) sqlite3_value_text (argv[1]),
+ G_REGEX_MATCH_NOTEMPTY,
+ NULL);
+ sqlite3_result_int (context, has_match ? 1 : 0);
+}
+
static gboolean
ephy_history_open_database (EphyHistory * history,
GSimpleAsyncResult *res)
{
+ static const struct {
+ const char *name;
+ int n_args;
+ void (* func) (sqlite3_context *, int, sqlite3_value **);
+ } custom_functions[] = {
+ { "markup", -1, custom_function_markup },
+ { "match", 2, custom_function_match },
+ { "regexp", 2, custom_function_regexp }
+ };
EphyHistoryPrivate *priv = history->priv;
+ guint i;
if (priv->db)
return TRUE;
@@ -189,8 +353,28 @@ ephy_history_open_database (EphyHistory * history,
NULL)))
return FALSE;
+ /* register custom functions */
+ for (i = 0; i < G_N_ELEMENTS (custom_functions); i++)
+ {
+ if (!check_sqlite_result (history, res,
+ sqlite3_create_function (priv->db,
+ custom_functions[i].name,
+ custom_functions[i].n_args,
+ SQLITE_UTF8,
+ NULL,
+ custom_functions[i].func,
+ NULL,
+ NULL)))
+ goto fail;
+ }
+
/* FIXME: create database if it doesn't exist */
return TRUE;
+
+fail:
+ sqlite3_close (priv->db);
+ priv->db = NULL;
+ return FALSE;
}
GQuark
@@ -298,8 +482,11 @@ ephy_history_select_thread (GSimpleAsyncResult *res,
g_value_init (&array->values[i], G_TYPE_DOUBLE);
g_value_set_int64 (&array->values[i], sqlite3_column_double (stmt, i));
break;
- case SQLITE_BLOB:
case SQLITE_NULL:
+ g_value_init (&array->values[i], G_TYPE_STRING);
+ /* leave values as NULL */
+ break;
+ case SQLITE_BLOB:
/* FIXME: implement */
default:
g_warning ("unhandled column type %d", sqlite3_column_type (stmt, i));