aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/libstat/CMakeLists.txt3
-rw-r--r--src/libstat/backends/backends.h1
-rw-r--r--src/libstat/backends/sqlite3_backend.c360
3 files changed, 363 insertions, 1 deletions
diff --git a/src/libstat/CMakeLists.txt b/src/libstat/CMakeLists.txt
index f31cf4cf4..2fa16cf98 100644
--- a/src/libstat/CMakeLists.txt
+++ b/src/libstat/CMakeLists.txt
@@ -7,7 +7,8 @@ SET(TOKENIZERSSRC ${CMAKE_CURRENT_SOURCE_DIR}/tokenizers/tokenizers.c
SET(CLASSIFIERSSRC ${CMAKE_CURRENT_SOURCE_DIR}/classifiers/bayes.c)
-SET(BACKENDSSRC ${CMAKE_CURRENT_SOURCE_DIR}/backends/mmaped_file.c)
+SET(BACKENDSSRC ${CMAKE_CURRENT_SOURCE_DIR}/backends/mmaped_file.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/backends/sqlite3_backend.c)
IF(ENABLE_HIREDIS MATCHES "ON")
SET(BACKENDSSRC ${BACKENDSSRC}
${CMAKE_CURRENT_SOURCE_DIR}/backends/redis.c)
diff --git a/src/libstat/backends/backends.h b/src/libstat/backends/backends.h
index bfa828cf6..8180e1baf 100644
--- a/src/libstat/backends/backends.h
+++ b/src/libstat/backends/backends.h
@@ -83,5 +83,6 @@ struct rspamd_stat_backend {
RSPAMD_STAT_BACKEND_DEF(mmaped_file);
RSPAMD_STAT_BACKEND_DEF(redis);
+RSPAMD_STAT_BACKEND_DEF(sqlite3);
#endif /* BACKENDS_H_ */
diff --git a/src/libstat/backends/sqlite3_backend.c b/src/libstat/backends/sqlite3_backend.c
new file mode 100644
index 000000000..fa8018be3
--- /dev/null
+++ b/src/libstat/backends/sqlite3_backend.c
@@ -0,0 +1,360 @@
+/*
+ * Copyright (c) 2015, Vsevolod Stakhov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "stat_internal.h"
+#include "main.h"
+#include "sqlite3.h"
+
+#define SQLITE3_BACKEND_TYPE "sqlite3"
+
+struct rspamd_sqlite3_prstmt;
+
+struct rspamd_stat_sqlite3_db {
+ sqlite3 *sqlite;
+ struct rspamd_sqlite3_prstmt *prstmt;
+};
+
+struct rspamd_stat_sqlite3_ctx {
+ GHashTable *files;
+};
+
+static const char *create_tables_sql =
+ "BEGIN;"
+ "CREATE TABLE users("
+ "id INTEGER PRIMARY KEY,"
+ "name TEXT,"
+ "learns INTEGER"
+ ");"
+ "CREATE TABLE languages("
+ "id INTEGER PRIMARY KEY,"
+ "name TEXT,"
+ "learns INTEGER"
+ ");"
+ "CREATE TABLE tokens("
+ "token INTEGER,"
+ "user INTEGER REFERENCES users(id) ON DELETE CASCADE,"
+ "language INTEGER REFERENCES users(id) ON DELETE CASCADE,"
+ "PRIMARY KEY (token,user,language)"
+ ");"
+ "CREATE UNIQUE INDEX IF NOT EXISTS un ON users(name);"
+ "CREATE UNIQUE INDEX IF NOT EXISTS ln ON languages(name);"
+ "COMMIT;";
+
+enum rspamd_stat_sqlite3_stmt_idx {
+ RSPAMD_STAT_BACKEND_TRANSACTION_START = 0,
+ RSPAMD_STAT_BACKEND_TRANSACTION_COMMIT,
+ RSPAMD_STAT_BACKEND_TRANSACTION_ROLLBACK,
+ RSPAMD_STAT_BACKEND_MAX
+};
+
+static struct rspamd_sqlite3_prstmt {
+ enum rspamd_stat_sqlite3_stmt_idx idx;
+ const gchar *sql;
+ const gchar *args;
+ sqlite3_stmt *stmt;
+ gint result;
+} prepared_stmts[RSPAMD_STAT_BACKEND_MAX] =
+{
+ {
+ .idx = RSPAMD_STAT_BACKEND_TRANSACTION_START,
+ .sql = "BEGIN TRANSACTION;",
+ .args = "",
+ .stmt = NULL,
+ .result = SQLITE_DONE
+ },
+ {
+ .idx = RSPAMD_STAT_BACKEND_TRANSACTION_COMMIT,
+ .sql = "COMMIT;",
+ .args = "",
+ .stmt = NULL,
+ .result = SQLITE_DONE
+ },
+ {
+ .idx = RSPAMD_STAT_BACKEND_TRANSACTION_ROLLBACK,
+ .sql = "ROLLBACK;",
+ .args = "",
+ .stmt = NULL,
+ .result = SQLITE_DONE
+ },
+};
+
+static GQuark
+rspamd_sqlite3_quark (void)
+{
+ return g_quark_from_static_string ("sqlite3-stat-backend");
+}
+
+static gboolean
+rspamd_sqlite3_init_prstmt (struct rspamd_stat_sqlite3_db *db, GError **err)
+{
+ int i;
+
+ for (i = 0; i < RSPAMD_STAT_BACKEND_MAX; i ++) {
+ if (db->prstmt[i].stmt != NULL) {
+ /* Skip already prepared statements */
+ continue;
+ }
+ if (sqlite3_prepare_v2 (db->sqlite, db->prstmt[i].sql, -1,
+ &db->prstmt[i].stmt, NULL) != SQLITE_OK) {
+ g_set_error (err, rspamd_sqlite3_quark (),
+ -1, "Cannot initialize prepared sql `%s`: %s",
+ db->prstmt[i].sql, sqlite3_errmsg (db->sqlite));
+
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static int
+rspamd_sqlite3_run_prstmt (struct rspamd_stat_sqlite3_db *db, int idx, ...)
+{
+ int retcode;
+ va_list ap;
+ sqlite3_stmt *stmt;
+ int i;
+ const char *argtypes;
+
+ if (idx < 0 || idx >= RSPAMD_STAT_BACKEND_MAX) {
+
+ return -1;
+ }
+
+ stmt = db->prstmt[idx].stmt;
+ if (stmt == NULL) {
+ if ((retcode = sqlite3_prepare_v2 (db->sqlite, db->prstmt[idx].sql, -1,
+ &db->prstmt[idx].stmt, NULL)) != SQLITE_OK) {
+ msg_err ("Cannot initialize prepared sql `%s`: %s",
+ db->prstmt[idx].sql, sqlite3_errmsg (db->sqlite));
+
+ return retcode;
+ }
+ stmt = db->prstmt[idx].stmt;
+ }
+
+ msg_debug ("executing `%s`", db->prstmt[idx].sql);
+ argtypes = db->prstmt[idx].args;
+ sqlite3_reset (stmt);
+ va_start (ap, idx);
+
+ for (i = 0; argtypes[i] != '\0'; i++) {
+ switch (argtypes[i]) {
+ case 'T':
+ sqlite3_bind_text (stmt, i + 1, va_arg (ap, const char*), -1,
+ SQLITE_STATIC);
+ break;
+ case 'I':
+ sqlite3_bind_int64 (stmt, i + 1, va_arg (ap, gint64));
+ break;
+ case 'S':
+ sqlite3_bind_int (stmt, i + 1, va_arg (ap, gint));
+ break;
+ }
+ }
+
+ va_end (ap);
+ retcode = sqlite3_step (stmt);
+
+ if (retcode == db->prstmt[idx].result) {
+ return SQLITE_OK;
+ }
+ else if (retcode != SQLITE_DONE) {
+ msg_debug ("failed to execute query %s: %d, %s", db->prstmt[idx].sql,
+ retcode, sqlite3_errmsg (db->sqlite));
+ }
+
+ return retcode;
+}
+
+static void
+rspamd_sqlite3_close_prstmt (struct rspamd_stat_sqlite3_db *db)
+{
+ int i;
+
+ for (i = 0; i < RSPAMD_STAT_BACKEND_MAX; i++) {
+ if (db->prstmt[i].stmt != NULL) {
+ sqlite3_finalize (db->prstmt[i].stmt);
+ db->prstmt[i].stmt = NULL;
+ }
+ }
+
+ return;
+}
+
+static struct rspamd_stat_sqlite3_db *
+rspamd_sqlite3_opendb (const gchar *path, GError **err)
+{
+ struct rspamd_stat_sqlite3_db *bk;
+ sqlite3 *sqlite;
+ int rc;
+
+ if ((rc = sqlite3_open_v2 (path, &sqlite,
+ SQLITE_OPEN_READWRITE, NULL)) != SQLITE_OK) {
+ g_set_error (err, rspamd_sqlite3_quark (),
+ rc, "Cannot open sqlite db %s: %d",
+ path, rc);
+
+ return NULL;
+ }
+
+ bk = g_slice_alloc (sizeof (*bk));
+ bk->sqlite = sqlite;
+ bk->prstmt = g_slice_alloc0 (sizeof (prepared_stmts));
+ memcpy (bk->prstmt, prepared_stmts, sizeof (prepared_stmts));
+
+ if (!rspamd_sqlite3_init_prstmt (bk, err)) {
+ return NULL;
+ }
+
+ return bk;
+}
+
+gpointer
+rspamd_sqlite3_init (struct rspamd_stat_ctx *ctx,
+ struct rspamd_config *cfg)
+{
+ struct rspamd_stat_sqlite3_ctx *new;
+ struct rspamd_classifier_config *clf;
+ struct rspamd_statfile_config *stf;
+ GList *cur, *curst;
+ const ucl_object_t *filenameo;
+ const gchar *filename;
+ struct rspamd_stat_sqlite3_db *bk;
+ GError *err = NULL;
+
+ new = rspamd_mempool_alloc0 (cfg->cfg_pool, sizeof (*new));
+ new->files = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ /* Iterate over all classifiers and load matching statfiles */
+ cur = cfg->classifiers;
+
+ while (cur) {
+ clf = cur->data;
+
+ curst = clf->statfiles;
+ while (curst) {
+ stf = curst->data;
+
+ if (strcmp (stf->backend, SQLITE3_BACKEND_TYPE)) {
+ /*
+ * Check configuration sanity
+ */
+ filenameo = ucl_object_find_key (stf->opts, "filename");
+ if (filenameo == NULL || ucl_object_type (filenameo) != UCL_STRING) {
+ filenameo = ucl_object_find_key (stf->opts, "path");
+ if (filenameo == NULL || ucl_object_type (filenameo) != UCL_STRING) {
+ msg_err ("statfile %s has no filename defined", stf->symbol);
+ curst = curst->next;
+ continue;
+ }
+ }
+
+ filename = ucl_object_tostring (filenameo);
+
+ if ((bk = rspamd_sqlite3_opendb (filename, &err)) == NULL) {
+ msg_err ("cannot open sqlite3 db: %e", err);
+ g_error_free (err);
+ }
+
+ if (bk != NULL) {
+ g_hash_table_insert (new->files, stf, bk);
+ }
+
+ ctx->statfiles ++;
+ }
+
+ curst = curst->next;
+ }
+
+ cur = g_list_next (cur);
+ }
+
+ return (gpointer)new;
+}
+
+gpointer
+rspamd_sqlite3_runtime (struct rspamd_task *task,
+ struct rspamd_statfile_config *stcf, gboolean learn, gpointer p)
+{
+ struct rspamd_stat_sqlite3_ctx *ctx = p;
+ return g_hash_table_lookup (ctx->files, stcf);
+}
+
+gboolean
+rspamd_sqlite3_process_token (struct token_node_s *tok,
+ struct rspamd_token_result *res, gpointer ctx)
+{
+ return FALSE;
+}
+
+gboolean
+rspamd_sqlite3_learn_token (struct token_node_s *tok,
+ struct rspamd_token_result *res, gpointer ctx)
+{
+ return FALSE;
+}
+
+void
+rspamd_sqlite3_finalize_learn (struct rspamd_statfile_runtime *runtime,
+ gpointer ctx)
+{
+ return;
+}
+
+gulong
+rspamd_sqlite3_total_learns (struct rspamd_statfile_runtime *runtime,
+ gpointer ctx)
+{
+ return 0;
+}
+
+gulong
+rspamd_sqlite3_inc_learns (struct rspamd_statfile_runtime *runtime,
+ gpointer ctx)
+{
+ return 0;
+}
+
+gulong
+rspamd_sqlite3_dec_learns (struct rspamd_statfile_runtime *runtime,
+ gpointer ctx)
+{
+ return 0;
+}
+
+gulong
+rspamd_sqlite3_learns (struct rspamd_statfile_runtime *runtime,
+ gpointer ctx)
+{
+ return 0;
+}
+
+ucl_object_t *
+rspamd_sqlite3_get_stat (struct rspamd_statfile_runtime *runtime,
+ gpointer ctx)
+{
+ return NULL;
+}