<commit_before>/* discofs - disconnected file system
 * Copyright (c) 2012 <NAME>
 * see LICENSE for full license (BSD 2-Clause)
 */

#include "config.h"
#include "db.h"

#include "log.h"
#include "funcs.h"
#include "queue.h"
#include "job.h"
#include "sync.h"

#include <errno.h>
#include <stdio.h>
#include <pthread.h>
#include <limits.h>
#include <sys/types.h>


/*=============*
 * DEFINITIONS *
 *=============*/

/*------------------*
 * tables & schemas *
 *------------------*/

#define TABLE_CFG "config"
#define SCHEMA_CFG "option TEXT UNIQUE, nval INTEGER, tval TEXT"

#define TABLE_JOB "job"
#define SCHEMA_JOB                  \
    "rowid INTEGER PRIMARY KEY,"    \
    "prio INTEGER,"                 \
    "op INTEGER,"                   \
    "time INTEGER,"                 \
    "attempts INTEGER,"             \
    "path TEXT,"                    \
    "n1 INTEGER,"                   \
    "n2 INTEGER,"                   \
    "s1 TEXT,"                      \
    "s2 TEXT"

#define TABLE_SYNC "sync"
#define SCHEMA_SYNC                 \
    "path TEXT UNIQUE NOT NULL,"    \
    "mtime_s INTEGER,"              \
    "mtime_ns INTEGER,"             \
    "ctime_s INTEGER,"              \
    "ctime_ns INTEGER"

#define TABLE_HARDLINK "hardlink"
#define SCHEMA_HARDLINK             \
    "path TEXT UNIQUE NOT NULL,"    \
    "inode INTEGER"


/*--------------------*
 * convenience macros *
 *--------------------*/

#define ERRMSG(msg) ERROR(msg ": %s\n", sqlite3_errmsg(db))

#define PREPARE(sql, stmt)                                                  \
do {                                                                        \
    if (sqlite3_prepare_v2(db, sql, -1, stmt, NULL) != SQLITE_OK)           \
    {                                                                       \
        ERRMSG("preparing statement");                                      \
        db_close();                                                         \
        return DB_ERROR;                                                    \
    }                                                                       \
} while (0)


/* database object */
static sqlite3 *db;

/* mutex because only one function should access the database */
static pthread_mutex_t m_db = PTHREAD_MUTEX_INITIALIZER;


/*-------------------*
 * static prototypes *
 *-------------------*/

static char *column_text(sqlite3_stmt *stmt, int n);
static void db_open(void);
static void db_close(void);


/*==================*
 * STATIC FUNCTIONS *
 *==================*/

static char *column_text(sqlite3_stmt *stmt, int n)
{
    const unsigned char *p = sqlite3_column_text(stmt, n);

    if (!p)
        return NULL;

    return strdup((const char*)p);
}

static void db_open(void)
{
    pthread_mutex_lock(&m_db);
}

static void db_close(void)
{
    pthread_mutex_unlock(&m_db);
}


/*====================*
 * EXPORTED FUNCTIONS *
 *====================*/

int db_init(const char *path, int clear)
{
    VERBOSE("initializing db in %s\n", path);

    if (sqlite3_open(path, &db) != SQLITE_OK)
    {
        ERROR("error initializing db: %s\n", sqlite3_errmsg(db));
        return -1;
    }

    db_open();

#define NEW_TABLE(t, sql)                                                   \
    {                                                                       \
        if (sqlite3_exec(db, "CREATE TABLE " t " ( " sql " );",             \
                    NULL, NULL, NULL))                                      \
        {                                                                   \
            db_close();                                                     \
            ERROR("couldn't create table " t "\n");                         \
            return -1;                                                      \
        }                                                                   \
    }

#define CREATE_TABLE(t, sql)                                                \
    {                                                                       \
        if (clear)                                                          \
            sqlite3_exec(db, "DROP TABLE " t ";", NULL, NULL, NULL);        \
                                                                            \
        if (sqlite3_exec(db, "SELECT * FROM " t " LIMIT 1;",                \
                    NULL, NULL, NULL))                                      \
        {                                                                   \
            NEW_TABLE(t, sql);                                              \
        }                                                                   \
    }

    /* create tables */
    CREATE_TABLE(TABLE_CFG, SCHEMA_CFG);
    CREATE_TABLE(TABLE_JOB, SCHEMA_JOB);
    CREATE_TABLE(TABLE_SYNC, SCHEMA_SYNC);
    CREATE_TABLE(TABLE_HARDLINK, SCHEMA_HARDLINK);

#undef NEW_TABLE
#undef CREATE_TABLE

    DEBUG("db initialization finished\n");
    db_close();
    return 0;
}

int db_destroy(void)
{
    VERBOSE("closing database connection\n");
    sqlite3_close(db);
    return 0;
}


/*--------*
 * config *
 *--------*/

int db_cfg_delete(const char *option)
{
    int res = DB_OK;
    sqlite3_stmt *stmt;

    db_open();

    PREPARE("DELETE FROM " TABLE_CFG " WHERE option=?;", &stmt);

    sqlite3_bind_text(stmt, 1, option, -1, SQLITE_STATIC);

    if (sqlite3_step(stmt) != SQLITE_DONE)
    {
        ERRMSG("deleting config option");
        res = DB_ERROR;
    }

    sqlite3_finalize(stmt);
    db_close();
    return res;
}

int db_cfg_set_int(const char *option, int val)
{
    int res = DB_OK;
    sqlite3_stmt *stmt;

    db_open();

    PREPARE("INSERT OR REPLACE INTO " TABLE_CFG " (option, nval) VALUES (?, ?);", &stmt);
    sqlite3_bind_text(stmt, 1, option, -1, SQLITE_STATIC);
    sqlite3_bind_int (stmt, 2, val);

    if (sqlite3_step(stmt) != SQLITE_DONE)
    {
        ERRMSG("db_cfg_set_int");
        res = DB_ERROR;
    }

    sqlite3_finalize(stmt);
    db_close();
    return res;
}

int db_cfg_set_str(char *option, const char *val)
{
    int res = DB_OK;
    sqlite3_stmt *stmt;

    db_open();

    PREPARE("INSERT INTO " TABLE_CFG " (option, tval) VALUES (?, ?);", &stmt);
    sqlite3_bind_text(stmt, 1, option, -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 2, val, -1, SQLITE_STATIC);

    if (sqlite3_step(stmt) != SQLITE_DONE)
    {
        ERRMSG("db_cfg_set_str");
        res = DB_ERROR;
    }

    sqlite3_finalize(stmt);
    db_close();
    return res;
}

int db_cfg_get_int(const char *option, int *buf)
{
    int res = DB_OK, sql_res;
    sqlite3_stmt *stmt;

    if (!buf)
        return DB_ERROR;

    db_open();

    PREPARE("SELECT nval FROM " TABLE_CFG " WHERE option=?;", &stmt);
    sqlite3_bind_text(stmt, 1, option, -1, SQLITE_STATIC);

    sql_res = sqlite3_step(stmt);

    if (sql_res == SQLITE_ROW)
        *buf = sqlite3_column_int(stmt, 0);
    else if (sql_res != SQLITE_DONE)
    {
        ERRMSG("db_cfg_get_int");
        res = DB_ERROR;
    }
    else
        res = DB_NOTFOUND;

    sqlite3_finalize(stmt);
    db_close();
    return res;
}

int db_cfg_get_str(const char *option, char **buf)
{
    int res = DB_OK, sql_res;
    sqlite3_stmt *stmt;

    db_open();

    PREPARE("SELECT tval FROM " TABLE_CFG " WHERE option=?;", &stmt);
    sqlite3_bind_text(stmt, 1, option, -1, SQLITE_STATIC);

    sql_res = sqlite3_step(stmt);

    if (sql_res == SQLITE_ROW)
        *buf = column_text(stmt, 0);

    else if (sql_res != SQLITE_DONE)
    {
        ERRMSG("db_cfg_get_str");
        res = DB_ERROR;
    }
    else
        res = DB_NOTFOUND;

    sqlite3_finalize(stmt);
    db_close();
    return res;
}


/*-----*
 * job *
 *-----*/

int db_job_store(const struct job *j)
{
    int res = DB_OK;
    sqlite3_stmt *stmt;

    if (!j->path)
    {
        errno = EINVAL;
        return DB_ERROR;
    }

    DEBUG("storing %s on %s in db\n", job_opstr(j->op), j->path);

    db_open();

#define COLS "rowid, prio, op, time, attempts, path, n1, n2, s1, s2"
#define VALS "?, ?, ?, ?, ?, ?, ?, ?, ?, ?"
    PREPARE("INSERT OR REPLACE INTO " TABLE_JOB " (" COLS ") VALUES (" VALS ");", &stmt);
#undef COLS
#undef VALS

    if (j->id <= 0)
        sqlite3_bind_null (stmt, 1);
    else
        sqlite3_bind_int64(stmt, 1, j->id);

    sqlite3_bind_int  (stmt,  2, OP_PRIO(j->op));
    sqlite3_bind_int  (stmt,  3, j->op);

    sqlite3_bind_int64(stmt,  4, j->time);
    sqlite3_bind_int  (stmt,  5, j->attempts);

    sqlite3_bind_text (stmt,  6, j->path,   -1, SQLITE_STATIC);

    sqlite3_bind_int64(stmt,  7, j->n1);
    sqlite3_bind_int64(stmt,  8, j->n2);

    sqlite3_bind_text (stmt,  9, j->s1,     -1, SQLITE_STATIC);
    sqlite3_bind_text (stmt, 10, j->s2,     -1, SQLITE_STATIC);

    if (sqlite3_step(stmt) != SQLITE_DONE)
    {
        ERRMSG("db_store_job:");
        res = DB_ERROR;
    }

    sqlite3_finalize(stmt);
    db_close();
    return res;
}

int db_job_get(struct job **j)
{
    int res = DB_OK, sql_res;
    sqlite3_stmt *stmt;
    time_t now = time(NULL);
    struct job *p;

    *j = NULL;

    db_open();

    PREPARE("SELECT rowid, op, time, attempts, path, n1, n2, s1, s2 FROM " TABLE_JOB \
        "WHERE time <? ORDER BY prio DESC, time ASC LIMIT 1;", &stmt);
    sqlite3_bind_int64(stmt, 1, now);

    sql_res = sqlite3_step(stmt);

    if (sql_res == SQLITE_ROW)
    {
        p = job_alloc();

        if (!p)
        {
            res = DB_ERROR;
        }
        else
        {<commit_msg>fix SQL syntax error (missing space)<commit_after>/* discofs - disconnected file system
 * Copyright (c) 2012 <NAME>
 * see LICENSE for full license (BSD 2-Clause)
 */

#include "config.h"
#include "db.h"

#include "log.h"
#include "funcs.h"
#include "queue.h"
#include "job.h"
#include "sync.h"

#include <errno.h>
#include <stdio.h>
#include <pthread.h>
#include <limits.h>
#include <sys/types.h>


/*=============*
 * DEFINITIONS *
 *=============*/

/*------------------*
 * tables & schemas *
 *------------------*/

#define TABLE_CFG "config"
#define SCHEMA_CFG "option TEXT UNIQUE, nval INTEGER, tval TEXT"

#define TABLE_JOB "job"
#define SCHEMA_JOB                  \
    "rowid INTEGER PRIMARY KEY,"    \
    "prio INTEGER,"                 \
    "op INTEGER,"                   \
    "time INTEGER,"                 \
    "attempts INTEGER,"             \
    "path TEXT,"                    \
    "n1 INTEGER,"                   \
    "n2 INTEGER,"                   \
    "s1 TEXT,"                      \
    "s2 TEXT"

#define TABLE_SYNC "sync"
#define SCHEMA_SYNC                 \
    "path TEXT UNIQUE NOT NULL,"    \
    "mtime_s INTEGER,"              \
    "mtime_ns INTEGER,"             \
    "ctime_s INTEGER,"              \
    "ctime_ns INTEGER"

#define TABLE_HARDLINK "hardlink"
#define SCHEMA_HARDLINK             \
    "path TEXT UNIQUE NOT NULL,"    \
    "inode INTEGER"


/*--------------------*
 * convenience macros *
 *--------------------*/

#define ERRMSG(msg) ERROR(msg ": %s\n", sqlite3_errmsg(db))

#define PREPARE(sql, stmt)                                                  \
do {                                                                        \
    if (sqlite3_prepare_v2(db, sql, -1, stmt, NULL) != SQLITE_OK)           \
    {                                                                       \
        ERRMSG("preparing statement");                                      \
        db_close();                                                         \
        return DB_ERROR;                                                    \
    }                                                                       \
} while (0)


/* database object */
static sqlite3 *db;

/* mutex because only one function should access the database */
static pthread_mutex_t m_db = PTHREAD_MUTEX_INITIALIZER;


/*-------------------*
 * static prototypes *
 *-------------------*/

static char *column_text(sqlite3_stmt *stmt, int n);
static void db_open(void);
static void db_close(void);


/*==================*
 * STATIC FUNCTIONS *
 *==================*/

static char *column_text(sqlite3_stmt *stmt, int n)
{
    const unsigned char *p = sqlite3_column_text(stmt, n);

    if (!p)
        return NULL;

    return strdup((const char*)p);
}

static void db_open(void)
{
    pthread_mutex_lock(&m_db);
}

static void db_close(void)
{
    pthread_mutex_unlock(&m_db);
}


/*====================*
 * EXPORTED FUNCTIONS *
 *====================*/

int db_init(const char *path, int clear)
{
    VERBOSE("initializing db in %s\n", path);

    if (sqlite3_open(path, &db) != SQLITE_OK)
    {
        ERROR("error initializing db: %s\n", sqlite3_errmsg(db));
        return -1;
    }

    db_open();

#define NEW_TABLE(t, sql)                                                   \
    {                                                                       \
        if (sqlite3_exec(db, "CREATE TABLE " t " ( " sql " );",             \
                    NULL, NULL, NULL))                                      \
        {                                                                   \
            db_close();                                                     \
            ERROR("couldn't create table " t "\n");                         \
            return -1;                                                      \
        }                                                                   \
    }

#define CREATE_TABLE(t, sql)                                                \
    {                                                                       \
        if (clear)                                                          \
            sqlite3_exec(db, "DROP TABLE " t ";", NULL, NULL, NULL);        \
                                                                            \
        if (sqlite3_exec(db, "SELECT * FROM " t " LIMIT 1;",                \
                    NULL, NULL, NULL))                                      \
        {                                                                   \
            NEW_TABLE(t, sql);                                              \
        }                                                                   \
    }

    /* create tables */
    CREATE_TABLE(TABLE_CFG, SCHEMA_CFG);
    CREATE_TABLE(TABLE_JOB, SCHEMA_JOB);
    CREATE_TABLE(TABLE_SYNC, SCHEMA_SYNC);
    CREATE_TABLE(TABLE_HARDLINK, SCHEMA_HARDLINK);

#undef NEW_TABLE
#undef CREATE_TABLE

    DEBUG("db initialization finished\n");
    db_close();
    return 0;
}

int db_destroy(void)
{
    VERBOSE("closing database connection\n");
    sqlite3_close(db);
    return 0;
}


/*--------*
 * config *
 *--------*/

int db_cfg_delete(const char *option)
{
    int res = DB_OK;
    sqlite3_stmt *stmt;

    db_open();

    PREPARE("DELETE FROM " TABLE_CFG " WHERE option=?;", &stmt);

    sqlite3_bind_text(stmt, 1, option, -1, SQLITE_STATIC);

    if (sqlite3_step(stmt) != SQLITE_DONE)
    {
        ERRMSG("deleting config option");
        res = DB_ERROR;
    }

    sqlite3_finalize(stmt);
    db_close();
    return res;
}

int db_cfg_set_int(const char *option, int val)
{
    int res = DB_OK;
    sqlite3_stmt *stmt;

    db_open();

    PREPARE("INSERT OR REPLACE INTO " TABLE_CFG " (option, nval) VALUES (?, ?);", &stmt);
    sqlite3_bind_text(stmt, 1, option, -1, SQLITE_STATIC);
    sqlite3_bind_int (stmt, 2, val);

    if (sqlite3_step(stmt) != SQLITE_DONE)
    {
        ERRMSG("db_cfg_set_int");
        res = DB_ERROR;
    }

    sqlite3_finalize(stmt);
    db_close();
    return res;
}

int db_cfg_set_str(char *option, const char *val)
{
    int res = DB_OK;
    sqlite3_stmt *stmt;

    db_open();

    PREPARE("INSERT INTO " TABLE_CFG " (option, tval) VALUES (?, ?);", &stmt);
    sqlite3_bind_text(stmt, 1, option, -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 2, val, -1, SQLITE_STATIC);

    if (sqlite3_step(stmt) != SQLITE_DONE)
    {
        ERRMSG("db_cfg_set_str");
        res = DB_ERROR;
    }

    sqlite3_finalize(stmt);
    db_close();
    return res;
}

int db_cfg_get_int(const char *option, int *buf)
{
    int res = DB_OK, sql_res;
    sqlite3_stmt *stmt;

    if (!buf)
        return DB_ERROR;

    db_open();

    PREPARE("SELECT nval FROM " TABLE_CFG " WHERE option=?;", &stmt);
    sqlite3_bind_text(stmt, 1, option, -1, SQLITE_STATIC);

    sql_res = sqlite3_step(stmt);

    if (sql_res == SQLITE_ROW)
        *buf = sqlite3_column_int(stmt, 0);
    else if (sql_res != SQLITE_DONE)
    {
        ERRMSG("db_cfg_get_int");
        res = DB_ERROR;
    }
    else
        res = DB_NOTFOUND;

    sqlite3_finalize(stmt);
    db_close();
    return res;
}

int db_cfg_get_str(const char *option, char **buf)
{
    int res = DB_OK, sql_res;
    sqlite3_stmt *stmt;

    db_open();

    PREPARE("SELECT tval FROM " TABLE_CFG " WHERE option=?;", &stmt);
    sqlite3_bind_text(stmt, 1, option, -1, SQLITE_STATIC);

    sql_res = sqlite3_step(stmt);

    if (sql_res == SQLITE_ROW)
        *buf = column_text(stmt, 0);

    else if (sql_res != SQLITE_DONE)
    {
        ERRMSG("db_cfg_get_str");
        res = DB_ERROR;
    }
    else
        res = DB_NOTFOUND;

    sqlite3_finalize(stmt);
    db_close();
    return res;
}


/*-----*
 * job *
 *-----*/

int db_job_store(const struct job *j)
{
    int res = DB_OK;
    sqlite3_stmt *stmt;

    if (!j->path)
    {
        errno = EINVAL;
        return DB_ERROR;
    }

    DEBUG("storing %s on %s in db\n", job_opstr(j->op), j->path);

    db_open();

#define COLS "rowid, prio, op, time, attempts, path, n1, n2, s1, s2"
#define VALS "?, ?, ?, ?, ?, ?, ?, ?, ?, ?"
    PREPARE("INSERT OR REPLACE INTO " TABLE_JOB " (" COLS ") VALUES (" VALS ");", &stmt);
#undef COLS
#undef VALS

    if (j->id <= 0)
        sqlite3_bind_null (stmt, 1);
    else
        sqlite3_bind_int64(stmt, 1, j->id);

    sqlite3_bind_int  (stmt,  2, OP_PRIO(j->op));
    sqlite3_bind_int  (stmt,  3, j->op);

    sqlite3_bind_int64(stmt,  4, j->time);
    sqlite3_bind_int  (stmt,  5, j->attempts);

    sqlite3_bind_text (stmt,  6, j->path,   -1, SQLITE_STATIC);

    sqlite3_bind_int64(stmt,  7, j->n1);
    sqlite3_bind_int64(stmt,  8, j->n2);

    sqlite3_bind_text (stmt,  9, j->s1,     -1, SQLITE_STATIC);
    sqlite3_bind_text (stmt, 10, j->s2,     -1, SQLITE_STATIC);

    if (sqlite3_step(stmt) != SQLITE_DONE)
    {
        ERRMSG("db_store_job:");
        res = DB_ERROR;
    }

    sqlite3_finalize(stmt);
    db_close();
    return res;
}

int db_job_get(struct job **j)
{
    int res = DB_OK, sql_res;
    sqlite3_stmt *stmt;
    time_t now = time(NULL);
    struct job *p;

    *j = NULL;

    db_open();

    PREPARE("SELECT rowid, op, time, attempts, path, n1, n2, s1, s2 FROM "
            TABLE_JOB " WHERE time < ? ORDER BY prio DESC, time ASC LIMIT 1;",
            &stmt);
    sqlite3_bind_int64(stmt, 1, now);

    sql_res = sqlite3_step(stmt);

    if (sql_res == SQLITE_ROW)
    {
        p = job_alloc();

        if (!p)
        {
            res = DB_ERROR;
        }
        else
        {
{'fraction_non_alphanumeric': 0.14354593574760233, 'fraction_numerical': 0.01299722236780043, 'mean_word_length': 1.4583870136562742, 'pattern_counts': {'":': 2, '<': 19, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 102, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5020513', 'n_tokens_mistral': 6425, 'n_tokens_neox': 6072, 'n_words': 1355}