Permalink
Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
2615 lines (2101 sloc) 70.2 KB
/*
* Copyright (C) 2008, Nokia <ivan.frade@nokia.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include <glib/gstdio.h>
#include <sqlite3.h>
#include <stdlib.h>
#include <math.h>
#include <errno.h>
#include <libtracker-common/tracker-date-time.h>
#include <libtracker-common/tracker-locale.h>
#include <libtracker-common/tracker-parser.h>
#include <libtracker-sparql/tracker-sparql.h>
#if HAVE_TRACKER_FTS
#include <libtracker-fts/tracker-fts.h>
#endif
#ifdef HAVE_LIBUNISTRING
/* libunistring versions prior to 9.1.2 need this hack */
#define _UNUSED_PARAMETER_
#include <unistr.h>
#include <unicase.h>
#elif HAVE_LIBICU
#include <unicode/utypes.h>
#include <unicode/uregex.h>
#include <unicode/ustring.h>
#include <unicode/ucol.h>
#endif
#include "tracker-collation.h"
#include "tracker-db-interface-sqlite.h"
#include "tracker-db-manager.h"
#define UNKNOWN_STATUS 0.5
typedef struct {
TrackerDBStatement *head;
TrackerDBStatement *tail;
guint size;
guint max;
} TrackerDBStatementLru;
struct TrackerDBInterface {
GObject parent_instance;
gchar *filename;
sqlite3 *db;
GHashTable *dynamic_statements;
GSList *function_data;
/* Collation and locale change */
gpointer locale_notification_id;
gint collator_reset_requested;
/* Number of active cursors */
gint n_active_cursors;
guint ro : 1;
GCancellable *cancellable;
TrackerDBStatementLru select_stmt_lru;
TrackerDBStatementLru update_stmt_lru;
TrackerBusyCallback busy_callback;
gpointer busy_user_data;
gchar *busy_status;
gchar *fts_insert_str;
gchar *fts_delete_str;
};
struct TrackerDBInterfaceClass {
GObjectClass parent_class;
};
struct TrackerDBCursor {
TrackerSparqlCursor parent_instance;
sqlite3_stmt *stmt;
TrackerDBStatement *ref_stmt;
gboolean finished;
TrackerPropertyType *types;
gint n_types;
gchar **variable_names;
gint n_variable_names;
/* used for direct access as libtracker-sparql is thread-safe and
uses a single shared connection with SQLite mutex disabled */
gboolean threadsafe;
};
struct TrackerDBCursorClass {
TrackerSparqlCursorClass parent_class;
};
struct TrackerDBStatement {
GObject parent_instance;
TrackerDBInterface *db_interface;
sqlite3_stmt *stmt;
gboolean stmt_is_sunk;
TrackerDBStatement *next;
TrackerDBStatement *prev;
};
struct TrackerDBStatementClass {
GObjectClass parent_class;
};
static void tracker_db_interface_initable_iface_init (GInitableIface *iface);
static TrackerDBStatement *tracker_db_statement_sqlite_new (TrackerDBInterface *db_interface,
sqlite3_stmt *sqlite_stmt);
static void tracker_db_statement_sqlite_reset (TrackerDBStatement *stmt);
static TrackerDBCursor *tracker_db_cursor_sqlite_new (sqlite3_stmt *sqlite_stmt,
TrackerDBStatement *ref_stmt,
TrackerPropertyType *types,
gint n_types,
const gchar **variable_names,
gint n_variable_names,
gboolean threadsafe);
static gboolean tracker_db_cursor_get_boolean (TrackerSparqlCursor *cursor,
guint column);
static gboolean db_cursor_iter_next (TrackerDBCursor *cursor,
GCancellable *cancellable,
GError **error);
enum {
PROP_0,
PROP_FILENAME,
PROP_RO
};
enum {
TRACKER_DB_CURSOR_PROP_0,
TRACKER_DB_CURSOR_PROP_N_COLUMNS
};
G_DEFINE_TYPE_WITH_CODE (TrackerDBInterface, tracker_db_interface, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
tracker_db_interface_initable_iface_init));
G_DEFINE_TYPE (TrackerDBStatement, tracker_db_statement, G_TYPE_OBJECT)
G_DEFINE_TYPE (TrackerDBCursor, tracker_db_cursor, TRACKER_SPARQL_TYPE_CURSOR)
void
tracker_db_interface_sqlite_enable_shared_cache (void)
{
sqlite3_enable_shared_cache (1);
}
static void
function_sparql_string_join (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
GString *str = NULL;
const gchar *separator;
gint i;
/* fn:string-join (str1, str2, ..., separator) */
if (sqlite3_value_type (argv[argc-1]) != SQLITE_TEXT) {
sqlite3_result_error (context, "Invalid separator", -1);
return;
}
separator = sqlite3_value_text (argv[argc-1]);
for (i = 0;i < argc-1; i++) {
if (sqlite3_value_type (argv[argc-1]) == SQLITE_TEXT) {
const gchar *text = sqlite3_value_text (argv[i]);
if (text != NULL) {
if (!str) {
str = g_string_new (text);
} else {
g_string_append_printf (str, "%s%s", separator, text);
}
}
}
}
if (str) {
sqlite3_result_text (context, str->str, str->len, g_free);
g_string_free (str, FALSE);
} else {
sqlite3_result_null (context);
}
return;
}
/* Create a title-type string from the filename for replacing missing ones */
static void
function_sparql_string_from_filename (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
gchar *name = NULL;
gchar *suffix = NULL;
if (argc != 1) {
sqlite3_result_error (context, "Invalid argument count", -1);
return;
}
/* "/home/user/path/title_of_the_movie.movie" -> "title of the movie"
* Only for local files currently, do we need to change? */
name = g_filename_display_basename (sqlite3_value_text (argv[0]));
if (!name) {
sqlite3_result_null (context);
return;
}
suffix = g_strrstr (name, ".");
if (suffix) {
*suffix = '\0';
}
g_strdelimit (name, "._", ' ');
sqlite3_result_text (context, name, -1, g_free);
}
static void
function_sparql_uri_is_parent (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
const gchar *uri, *parent, *remaining;
gboolean match = FALSE;
guint parent_len;
if (argc != 2) {
sqlite3_result_error (context, "Invalid argument count", -1);
return;
}
parent = sqlite3_value_text (argv[0]);
uri = sqlite3_value_text (argv[1]);
if (!parent || !uri) {
sqlite3_result_error (context, "Invalid arguments", -1);
return;
}
parent_len = sqlite3_value_bytes (argv[0]);
/* Check only one argument, it's going to
* be compared with the other anyway.
*/
if (!(parent_len >= 7 && (parent[4] == ':' && parent[5] == '/' && parent[6] == '/'))) {
if (strstr (parent, "://") == NULL) {
sqlite3_result_int (context, FALSE);
return;
}
}
/* Remove trailing '/', will
* be checked later on uri.
*/
while (parent[parent_len - 1] == '/') {
parent_len--;
}
if (strncmp (uri, parent, parent_len) == 0 && uri[parent_len] == '/') {
const gchar *slash;
while (uri[parent_len] == '/') {
parent_len++;
}
remaining = &uri[parent_len];
if (*remaining == '\0') {
/* Exact match, not a child */
match = FALSE;
} else if ((slash = strchr (remaining, '/')) == NULL) {
/* Remaining doesn't have uri
* separator, it's a direct child.
*/
match = TRUE;
} else {
/* Check it's not trailing slashes */
while (*slash == '/') {
slash++;
}
match = (*slash == '\0');
}
}
sqlite3_result_int (context, match);
}
static gboolean
check_uri_is_descendant (const gchar *parent,
guint parent_len,
const gchar *uri)
{
const gchar *remaining;
gboolean match = FALSE;
/* Check only one argument, it's going to
* be compared with the other anyway.
*/
if (!(parent_len >= 7 && (parent[4] == ':' && parent[5] == '/' && parent[6] == '/'))) {
if (strstr (parent, "://") == NULL) {
return FALSE;
}
}
/* Remove trailing '/', will
* be checked later on uri.
*/
while (parent[parent_len - 1] == '/') {
parent_len--;
}
if (strncmp (uri, parent, parent_len) == 0 && uri[parent_len] == '/') {
while (uri[parent_len] == '/') {
parent_len++;
}
remaining = &uri[parent_len];
if (remaining && *remaining) {
match = TRUE;
}
}
return match;
}
static void
function_sparql_uri_is_descendant (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
const gchar *child;
gboolean match = FALSE;
gint i;
/* fn:uri-is-descendant (parent1, parent2, ..., parentN, child) */
if (argc < 2) {
sqlite3_result_error (context, "Invalid argument count", -1);
return;
}
if (sqlite3_value_type (argv[argc-1]) != SQLITE_TEXT) {
sqlite3_result_error (context, "Invalid child", -1);
return;
}
if (sqlite3_value_type (argv[0]) != SQLITE_TEXT) {
sqlite3_result_error (context, "Invalid first parent", -1);
return;
}
child = sqlite3_value_text (argv[argc-1]);
for (i = 0; i < argc - 1 && !match; i++) {
if (sqlite3_value_type (argv[i]) == SQLITE_TEXT) {
const gchar *parent = sqlite3_value_text (argv[i]);
guint parent_len = sqlite3_value_bytes (argv[i]);
if (!parent)
continue;
match = check_uri_is_descendant (parent, parent_len, child);
}
}
sqlite3_result_int (context, match);
}
static void
function_sparql_format_time (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
gdouble seconds;
gchar *str;
if (argc != 1) {
sqlite3_result_error (context, "Invalid argument count", -1);
return;
}
if (sqlite3_value_type (argv[0]) == SQLITE_NULL) {
sqlite3_result_null (context);
return;
}
seconds = sqlite3_value_double (argv[0]);
str = tracker_date_to_string (seconds);
sqlite3_result_text (context, str, -1, g_free);
}
static void
function_sparql_cartesian_distance (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
gdouble lat1;
gdouble lat2;
gdouble lon1;
gdouble lon2;
gdouble R;
gdouble a;
gdouble b;
gdouble c;
gdouble d;
if (argc != 4) {
sqlite3_result_error (context, "Invalid argument count", -1);
return;
}
lat1 = sqlite3_value_double (argv[0])*M_PI/180;
lat2 = sqlite3_value_double (argv[1])*M_PI/180;
lon1 = sqlite3_value_double (argv[2])*M_PI/180;
lon2 = sqlite3_value_double (argv[3])*M_PI/180;
R = 6371000;
a = M_PI/2 - lat1;
b = M_PI/2 - lat2;
c = sqrt(a*a + b*b - 2*a*b*cos(lon2 - lon1));
d = R*c;
sqlite3_result_double (context, d);
}
static void
function_sparql_haversine_distance (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
gdouble lat1;
gdouble lat2;
gdouble lon1;
gdouble lon2;
gdouble R;
gdouble dLat;
gdouble dLon;
gdouble a;
gdouble c;
gdouble d;
if (argc != 4) {
sqlite3_result_error (context, "Invalid argument count", -1);
return;
}
lat1 = sqlite3_value_double (argv[0])*M_PI/180;
lat2 = sqlite3_value_double (argv[1])*M_PI/180;
lon1 = sqlite3_value_double (argv[2])*M_PI/180;
lon2 = sqlite3_value_double (argv[3])*M_PI/180;
R = 6371000;
dLat = (lat2-lat1);
dLon = (lon2-lon1);
a = sin(dLat/2) * sin(dLat/2) + cos(lat1) * cos(lat2) * sin(dLon/2) * sin(dLon/2);
c = 2 * atan2(sqrt(a), sqrt(1-a));
d = R * c;
sqlite3_result_double (context, d);
}
static void
function_sparql_regex (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
gboolean ret;
const gchar *text, *pattern, *flags;
GRegexCompileFlags regex_flags;
GRegex *regex;
if (argc != 3) {
sqlite3_result_error (context, "Invalid argument count", -1);
return;
}
regex = sqlite3_get_auxdata (context, 1);
text = sqlite3_value_text (argv[0]);
flags = sqlite3_value_text (argv[2]);
if (regex == NULL) {
gchar *err_str;
GError *error = NULL;
pattern = sqlite3_value_text (argv[1]);
regex_flags = 0;
while (*flags) {
switch (*flags) {
case 's':
regex_flags |= G_REGEX_DOTALL;
break;
case 'm':
regex_flags |= G_REGEX_MULTILINE;
break;
case 'i':
regex_flags |= G_REGEX_CASELESS;
break;
case 'x':
regex_flags |= G_REGEX_EXTENDED;
break;
default:
err_str = g_strdup_printf ("Invalid SPARQL regex flag '%c'", *flags);
sqlite3_result_error (context, err_str, -1);
g_free (err_str);
return;
}
flags++;
}
regex = g_regex_new (pattern, regex_flags, 0, &error);
if (error) {
sqlite3_result_error (context, error->message, -1);
g_clear_error (&error);
return;
}
sqlite3_set_auxdata (context, 1, regex, (void (*) (void*)) g_regex_unref);
}
ret = g_regex_match (regex, text, 0, NULL);
sqlite3_result_int (context, ret);
}
#ifdef HAVE_LIBUNISTRING
static void
function_sparql_lower_case (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
const uint16_t *zInput;
uint16_t *zOutput;
size_t written = 0;
int nInput;
g_assert (argc == 1);
zInput = sqlite3_value_text16 (argv[0]);
if (!zInput) {
return;
}
nInput = sqlite3_value_bytes16 (argv[0]);
zOutput = u16_tolower (zInput, nInput/2, NULL, NULL, NULL, &written);
sqlite3_result_text16 (context, zOutput, written * 2, free);
}
static void
function_sparql_case_fold (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
const uint16_t *zInput;
uint16_t *zOutput;
size_t written = 0;
int nInput;
g_assert (argc == 1);
zInput = sqlite3_value_text16 (argv[0]);
if (!zInput) {
return;
}
nInput = sqlite3_value_bytes16 (argv[0]);
zOutput = u16_casefold (zInput, nInput/2, NULL, NULL, NULL, &written);
sqlite3_result_text16 (context, zOutput, written * 2, free);
}
static void
function_sparql_normalize (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
const gchar *nfstr;
const uint16_t *zInput;
uint16_t *zOutput;
size_t written = 0;
int nInput;
uninorm_t nf;
if (argc != 2) {
sqlite3_result_error (context, "Invalid argument count", -1);
return;
}
zInput = sqlite3_value_text16 (argv[0]);
if (!zInput) {
return;
}
nfstr = sqlite3_value_text (argv[1]);
if (g_ascii_strcasecmp (nfstr, "nfc") == 0)
nf = UNINORM_NFC;
else if (g_ascii_strcasecmp (nfstr, "nfd") == 0)
nf = UNINORM_NFD;
else if (g_ascii_strcasecmp (nfstr, "nfkc") == 0)
nf = UNINORM_NFKC;
else if (g_ascii_strcasecmp (nfstr, "nfkd") == 0)
nf = UNINORM_NFKD;
else {
sqlite3_result_error (context, "Invalid normalization specified, options are 'nfc', 'nfd', 'nfkc' or 'nfkd'", -1);
return;
}
nInput = sqlite3_value_bytes16 (argv[0]);
zOutput = u16_normalize (nf, zInput, nInput/2, NULL, &written);
sqlite3_result_text16 (context, zOutput, written * 2, free);
}
static void
function_sparql_unaccent (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
const gchar *zInput;
gchar *zOutput;
gsize written = 0;
int nInput;
g_assert (argc == 1);
zInput = sqlite3_value_text (argv[0]);
if (!zInput) {
return;
}
nInput = sqlite3_value_bytes (argv[0]);
zOutput = u8_normalize (UNINORM_NFKD, zInput, nInput, NULL, &written);
/* Unaccenting is done in place */
tracker_parser_unaccent_nfkd_string (zOutput, &written);
sqlite3_result_text (context, zOutput, written, free);
}
#elif HAVE_LIBICU
static void
function_sparql_lower_case (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
const UChar *zInput;
UChar *zOutput;
int nInput;
int nOutput;
UErrorCode status = U_ZERO_ERROR;
g_assert (argc == 1);
zInput = sqlite3_value_text16 (argv[0]);
if (!zInput) {
return;
}
nInput = sqlite3_value_bytes16 (argv[0]);
nOutput = nInput * 2 + 2;
zOutput = sqlite3_malloc (nOutput);
if (!zOutput) {
return;
}
u_strToLower (zOutput, nOutput/2, zInput, nInput/2, NULL, &status);
if (!U_SUCCESS (status)){
char zBuf[128];
sqlite3_snprintf (128, zBuf, "ICU error: u_strToLower(): %s", u_errorName (status));
zBuf[127] = '\0';
sqlite3_free (zOutput);
sqlite3_result_error (context, zBuf, -1);
return;
}
sqlite3_result_text16 (context, zOutput, -1, sqlite3_free);
}
static void
function_sparql_case_fold (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
const UChar *zInput;
UChar *zOutput;
int nInput;
int nOutput;
UErrorCode status = U_ZERO_ERROR;
g_assert (argc == 1);
zInput = sqlite3_value_text16 (argv[0]);
if (!zInput) {
return;
}
nInput = sqlite3_value_bytes16 (argv[0]);
nOutput = nInput * 2 + 2;
zOutput = sqlite3_malloc (nOutput);
if (!zOutput) {
return;
}
u_strFoldCase (zOutput, nOutput/2, zInput, nInput/2, U_FOLD_CASE_DEFAULT, &status);
if (!U_SUCCESS (status)){
char zBuf[128];
sqlite3_snprintf (128, zBuf, "ICU error: u_strFoldCase: %s", u_errorName (status));
zBuf[127] = '\0';
sqlite3_free (zOutput);
sqlite3_result_error (context, zBuf, -1);
return;
}
sqlite3_result_text16 (context, zOutput, -1, sqlite3_free);
}
static void
function_sparql_normalize (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
const gchar *nfstr;
const uint16_t *zInput;
uint16_t *zOutput;
int nInput;
int nOutput;
UNormalizationMode nf;
UErrorCode status = U_ZERO_ERROR;
if (argc != 2) {
sqlite3_result_error (context, "Invalid argument count", -1);
return;
}
zInput = sqlite3_value_text16 (argv[0]);
if (!zInput) {
return;
}
nfstr = sqlite3_value_text (argv[1]);
if (g_ascii_strcasecmp (nfstr, "nfc") == 0)
nf = UNORM_NFC;
else if (g_ascii_strcasecmp (nfstr, "nfd") == 0)
nf = UNORM_NFD;
else if (g_ascii_strcasecmp (nfstr, "nfkc") == 0)
nf = UNORM_NFKC;
else if (g_ascii_strcasecmp (nfstr, "nfkd") == 0)
nf = UNORM_NFKD;
else {
sqlite3_result_error (context, "Invalid normalization specified", -1);
return;
}
nInput = sqlite3_value_bytes16 (argv[0]);
nOutput = nInput * 2 + 2;
zOutput = sqlite3_malloc (nOutput);
if (!zOutput) {
return;
}
unorm_normalize (zInput, nInput/2, nf, 0, zOutput, nOutput/2, &status);
if (!U_SUCCESS (status)) {
char zBuf[128];
sqlite3_snprintf (128, zBuf, "ICU error: unorm_normalize: %s", u_errorName (status));
zBuf[127] = '\0';
sqlite3_free (zOutput);
sqlite3_result_error (context, zBuf, -1);
return;
}
sqlite3_result_text16 (context, zOutput, -1, sqlite3_free);
}
static void
function_sparql_unaccent (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
const gchar *nfstr;
const uint16_t *zInput;
uint16_t *zOutput;
int nInput;
gsize nOutput;
UErrorCode status = U_ZERO_ERROR;
g_assert (argc == 1);
zInput = sqlite3_value_text16 (argv[0]);
if (!zInput) {
return;
}
nInput = sqlite3_value_bytes16 (argv[0]);
nOutput = nInput * 2 + 2;
zOutput = sqlite3_malloc (nOutput);
if (!zOutput) {
return;
}
nOutput = unorm_normalize (zInput, nInput/2, UNORM_NFKD, 0, zOutput, nOutput/2, &status);
if (!U_SUCCESS (status)) {
char zBuf[128];
sqlite3_snprintf (128, zBuf, "ICU error: unorm_normalize: %s", u_errorName (status));
zBuf[127] = '\0';
sqlite3_free (zOutput);
sqlite3_result_error (context, zBuf, -1);
return;
}
/* Unaccenting is done in place */
tracker_parser_unaccent_nfkd_string (zOutput, &nOutput);
sqlite3_result_text16 (context, zOutput, -1, sqlite3_free);
}
#endif
static void
function_sparql_encode_for_uri (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
const gchar *str;
gchar *encoded;
if (argc != 1) {
sqlite3_result_error (context, "Invalid argument count", -1);
return;
}
str = sqlite3_value_text (argv[0]);
encoded = g_uri_escape_string (str, NULL, FALSE);
sqlite3_result_text (context, encoded, -1, g_free);
}
static void
function_sparql_string_before (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
const gchar *str, *substr, *loc;
gchar *encoded;
gint len;
if (argc != 2) {
sqlite3_result_error (context, "Invalid argument count", -1);
return;
}
if (sqlite3_value_type (argv[0]) != SQLITE_TEXT ||
sqlite3_value_type (argv[1]) != SQLITE_TEXT) {
sqlite3_result_error (context, "Invalid argument types", -1);
return;
}
str = sqlite3_value_text (argv[0]);
substr = sqlite3_value_text (argv[1]);
len = strlen (substr);
if (len == 0) {
sqlite3_result_text (context, "", -1, NULL);
return;
}
loc = strstr (str, substr);
if (!loc) {
sqlite3_result_text (context, "", -1, NULL);
return;
}
sqlite3_result_text (context, str, loc - str, NULL);
}
static void
function_sparql_string_after (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
const gchar *str, *substr, *loc;
gchar *encoded;
gint len;
if (argc != 2) {
sqlite3_result_error (context, "Invalid argument count", -1);
return;
}
if (sqlite3_value_type (argv[0]) != SQLITE_TEXT ||
sqlite3_value_type (argv[1]) != SQLITE_TEXT) {
sqlite3_result_error (context, "Invalid argument types", -1);
return;
}
str = sqlite3_value_text (argv[0]);
substr = sqlite3_value_text (argv[1]);
len = strlen (substr);
if (len == 0) {
sqlite3_result_text (context, g_strdup (str), -1, g_free);
return;
}
loc = strstr (str, substr);
if (!loc) {
sqlite3_result_text (context, "", -1, NULL);
return;
}
sqlite3_result_text (context, loc + len, -1, NULL);
}
static void
function_sparql_ceil (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
gdouble value;
if (argc != 1) {
sqlite3_result_error (context, "Invalid argument count", -1);
return;
}
value = sqlite3_value_double (argv[0]);
sqlite3_result_double (context, ceil (value));
}
static void
function_sparql_floor (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
gdouble value;
if (argc != 1) {
sqlite3_result_error (context, "Invalid argument count", -1);
return;
}
value = sqlite3_value_double (argv[0]);
sqlite3_result_double (context, floor (value));
}
static void
function_sparql_checksum (sqlite3_context *context,
int argc,
sqlite3_value *argv[])
{
const gchar *str, *checksumstr;
GChecksumType checksum;
gchar *result;
if (argc != 2) {
sqlite3_result_error (context, "Invalid argument count", -1);
return;
}
str = sqlite3_value_text (argv[0]);
checksumstr = sqlite3_value_text (argv[1]);
if (!str || !checksumstr) {
sqlite3_result_error (context, "Invalid arguments", -1);
return;
}
if (g_ascii_strcasecmp (checksumstr, "md5") == 0)
checksum = G_CHECKSUM_MD5;
else if (g_ascii_strcasecmp (checksumstr, "sha1") == 0)
checksum = G_CHECKSUM_SHA1;
else if (g_ascii_strcasecmp (checksumstr, "sha256") == 0)
checksum = G_CHECKSUM_SHA256;
else if (g_ascii_strcasecmp (checksumstr, "sha512") == 0)
checksum = G_CHECKSUM_SHA512;
else {
sqlite3_result_error (context, "Invalid checksum method specified", -1);
return;
}
result = g_compute_checksum_for_string (checksum, str, -1);
sqlite3_result_text (context, result, -1, g_free);
}
static inline int
stmt_step (sqlite3_stmt *stmt)
{
int result;
result = sqlite3_step (stmt);
/* If the statement expired between preparing it and executing
* sqlite3_step(), we are supposed to get SQLITE_SCHEMA error in
* sqlite3_errcode(), BUT there seems to be a bug in sqlite and
* SQLITE_ABORT is being returned instead for that case. So, the
* only way to see if a given statement was expired is to use
* sqlite3_expired(stmt), which is marked as DEPRECATED in sqlite.
* If found that the statement is expired, we need to reset it
* and retry the sqlite3_step().
* NOTE, that this expiration may only happen between preparing
* the statement and step-ing it, NOT between steps. */
if ((result == SQLITE_ABORT || result == SQLITE_SCHEMA) &&
sqlite3_expired (stmt)) {
sqlite3_reset (stmt);
result = sqlite3_step (stmt);
}
return result;
}
static int
check_interrupt (void *user_data)
{
TrackerDBInterface *db_interface = user_data;
if (db_interface->busy_callback) {
db_interface->busy_callback (db_interface->busy_status,
UNKNOWN_STATUS, /* No idea to get the status from SQLite */
db_interface->busy_user_data);
}
return g_cancellable_is_cancelled (db_interface->cancellable) ? 1 : 0;
}
static void
open_database (TrackerDBInterface *db_interface,
GError **error)
{
int mode;
int result;
g_assert (db_interface->filename != NULL);
if (!db_interface->ro) {
mode = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
} else {
mode = SQLITE_OPEN_READONLY;
}
result = sqlite3_open_v2 (db_interface->filename, &db_interface->db, mode | SQLITE_OPEN_NOMUTEX, NULL);
if (result != SQLITE_OK) {
const gchar *str;
str = sqlite3_errstr (result);
g_set_error (error,
TRACKER_DB_INTERFACE_ERROR,
TRACKER_DB_OPEN_ERROR,
"Could not open sqlite3 database:'%s': %s", db_interface->filename, str);
return;
} else {
g_message ("Opened sqlite3 database:'%s'", db_interface->filename);
}
/* Set our unicode collation function */
tracker_db_interface_sqlite_reset_collator (db_interface);
sqlite3_progress_handler (db_interface->db, 100,
check_interrupt, db_interface);
sqlite3_create_function (db_interface->db, "SparqlRegex", 3, SQLITE_ANY,
db_interface, &function_sparql_regex,
NULL, NULL);
sqlite3_create_function (db_interface->db, "SparqlHaversineDistance", 4, SQLITE_ANY,
db_interface, &function_sparql_haversine_distance,
NULL, NULL);
sqlite3_create_function (db_interface->db, "SparqlCartesianDistance", 4, SQLITE_ANY,
db_interface, &function_sparql_cartesian_distance,
NULL, NULL);
sqlite3_create_function (db_interface->db, "SparqlStringFromFilename", 1, SQLITE_ANY,
db_interface, &function_sparql_string_from_filename,
NULL, NULL);
sqlite3_create_function (db_interface->db, "SparqlStringJoin", -1, SQLITE_ANY,
db_interface, &function_sparql_string_join,
NULL, NULL);
sqlite3_create_function (db_interface->db, "SparqlUriIsParent", 2, SQLITE_ANY,
db_interface, &function_sparql_uri_is_parent,
NULL, NULL);
sqlite3_create_function (db_interface->db, "SparqlUriIsDescendant", -1, SQLITE_ANY,
db_interface, &function_sparql_uri_is_descendant,
NULL, NULL);
sqlite3_create_function (db_interface->db, "SparqlLowerCase", 1, SQLITE_ANY,
db_interface, &function_sparql_lower_case,
NULL, NULL);
sqlite3_create_function (db_interface->db, "SparqlCaseFold", 1, SQLITE_ANY,
db_interface, &function_sparql_case_fold,
NULL, NULL);
sqlite3_create_function (db_interface->db, "SparqlNormalize", 2, SQLITE_ANY,
db_interface, &function_sparql_normalize,
NULL, NULL);
sqlite3_create_function (db_interface->db, "SparqlUnaccent", 1, SQLITE_ANY,
db_interface, &function_sparql_unaccent,
NULL, NULL);
sqlite3_create_function (db_interface->db, "SparqlFormatTime", 1, SQLITE_ANY,
db_interface, &function_sparql_format_time,
NULL, NULL);
sqlite3_create_function (db_interface->db, "SparqlEncodeForUri", 1, SQLITE_ANY,
db_interface, &function_sparql_encode_for_uri,
NULL, NULL);
sqlite3_create_function (db_interface->db, "SparqlStringBefore", 2, SQLITE_ANY,
db_interface, &function_sparql_string_before,
NULL, NULL);
sqlite3_create_function (db_interface->db, "SparqlStringAfter", 2, SQLITE_ANY,
db_interface, &function_sparql_string_after,
NULL, NULL);
sqlite3_create_function (db_interface->db, "SparqlCeil", 1, SQLITE_ANY,
db_interface, &function_sparql_ceil,
NULL, NULL);
sqlite3_create_function (db_interface->db, "SparqlFloor", 1, SQLITE_ANY,
db_interface, &function_sparql_floor,
NULL, NULL);
sqlite3_create_function (db_interface->db, "SparqlChecksum", 2, SQLITE_ANY,
db_interface, &function_sparql_checksum,
NULL, NULL);
sqlite3_extended_result_codes (db_interface->db, 0);
sqlite3_busy_timeout (db_interface->db, 100000);
}
static gboolean
tracker_db_interface_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
TrackerDBInterface *db_iface;
GError *internal_error = NULL;
db_iface = TRACKER_DB_INTERFACE (initable);
open_database (db_iface, &internal_error);
if (internal_error) {
g_propagate_error (error, internal_error);
return FALSE;
}
return TRUE;
}
static void
tracker_db_interface_initable_iface_init (GInitableIface *iface)
{
iface->init = tracker_db_interface_initable_init;
}
static void
tracker_db_interface_sqlite_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
TrackerDBInterface *db_iface;
db_iface = TRACKER_DB_INTERFACE (object);
switch (prop_id) {
case PROP_RO:
db_iface->ro = g_value_get_boolean (value);
break;
case PROP_FILENAME:
db_iface->filename = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
tracker_db_interface_sqlite_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
TrackerDBInterface *db_iface;
db_iface = TRACKER_DB_INTERFACE (object);
switch (prop_id) {
case PROP_RO:
g_value_set_boolean (value, db_iface->ro);
break;
case PROP_FILENAME:
g_value_set_string (value, db_iface->filename);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
close_database (TrackerDBInterface *db_interface)
{
gint rc;
if (db_interface->dynamic_statements) {
g_hash_table_unref (db_interface->dynamic_statements);
db_interface->dynamic_statements = NULL;
}
if (db_interface->function_data) {
g_slist_foreach (db_interface->function_data, (GFunc) g_free, NULL);
g_slist_free (db_interface->function_data);
db_interface->function_data = NULL;
}
if (db_interface->db) {
rc = sqlite3_close (db_interface->db);
g_warn_if_fail (rc == SQLITE_OK);
}
}
static gchar **
_fts_create_properties (GHashTable *properties)
{
GHashTableIter iter;
GPtrArray *cols;
GList *columns;
gchar *table;
if (g_hash_table_size (properties) == 0) {
return NULL;
}
g_hash_table_iter_init (&iter, properties);
cols = g_ptr_array_new ();
while (g_hash_table_iter_next (&iter, (gpointer *) &table,
(gpointer *) &columns)) {
while (columns) {
g_ptr_array_add (cols, g_strdup (columns->data));
columns = columns->next;
}
}
g_ptr_array_add (cols, NULL);
return (gchar **) g_ptr_array_free (cols, FALSE);
}
void
tracker_db_interface_sqlite_fts_init (TrackerDBInterface *db_interface,
GHashTable *properties,
GHashTable *multivalued,
gboolean create)
{
#if HAVE_TRACKER_FTS
GStrv fts_columns;
tracker_fts_init_db (db_interface->db, properties);
if (create &&
!tracker_fts_create_table (db_interface->db, "fts",
properties, multivalued)) {
g_warning ("FTS tables creation failed");
}
fts_columns = _fts_create_properties (properties);
if (fts_columns) {
GString *insert, *select, *delete, cols;
gint i = 0;
insert = g_string_new ("INSERT INTO fts (docid");
select = g_string_new ("SELECT rowid");
delete = g_string_new ("UPDATE fts SET docid=?");
while (fts_columns[i]) {
g_string_append_printf (insert, ", \"%s\"",
fts_columns[i]);
g_string_append_printf (select, ", \"%s\"",
fts_columns[i]);
g_string_append_printf (delete, ", \"%s\"=\"\"",
fts_columns[i]);
i++;
}
g_string_append (select, " FROM fts_view WHERE rowid=?");
g_string_append (insert, ") ");
g_string_append (insert, select->str);
g_string_free (select, TRUE);
db_interface->fts_insert_str = g_string_free (insert, FALSE);
g_string_append (delete, " WHERE docid=?");
db_interface->fts_delete_str = g_string_free (delete, FALSE);
g_strfreev (fts_columns);
}
#endif
}
#if HAVE_TRACKER_FTS
void
tracker_db_interface_sqlite_fts_alter_table (TrackerDBInterface *db_interface,
GHashTable *properties,
GHashTable *multivalued)
{
if (!tracker_fts_alter_table (db_interface->db, "fts", properties, multivalued)) {
g_critical ("Failed to update FTS columns");
}
}
gboolean
tracker_db_interface_sqlite_fts_update_text (TrackerDBInterface *db_interface,
int id,
const gchar **properties,
const gchar **text,
gboolean create)
{
TrackerDBStatement *stmt;
GError *error = NULL;
if (!create) {
stmt = tracker_db_interface_create_statement (db_interface,
TRACKER_DB_STATEMENT_CACHE_TYPE_UPDATE,
&error,
"DELETE FROM fts WHERE docid=?");
if (!stmt || error) {
if (error) {
g_warning ("Could not create FTS update statement: %s",
error->message);
g_error_free (error);
}
return FALSE;
}
tracker_db_statement_bind_int (stmt, 0, id);
tracker_db_statement_execute (stmt, &error);
g_object_unref (stmt);
if (error) {
g_warning ("Could not update FTS text: %s", error->message);
g_error_free (error);
return FALSE;
}
}
stmt = tracker_db_interface_create_statement (db_interface,
TRACKER_DB_STATEMENT_CACHE_TYPE_UPDATE,
&error,
"%s",
db_interface->fts_insert_str);
if (!stmt || error) {
if (error) {
g_warning ("Could not create FTS insert statement: %s\n",
error->message);
g_error_free (error);
}
return FALSE;
}
tracker_db_statement_bind_int (stmt, 0, id);
tracker_db_statement_execute (stmt, &error);
g_object_unref (stmt);
if (error) {
g_warning ("Could not insert FTS text: %s", error->message);
g_error_free (error);
return FALSE;
}
return TRUE;
}
gboolean
tracker_db_interface_sqlite_fts_delete_text (TrackerDBInterface *db_interface,
int id,
const gchar *property)
{
TrackerDBStatement *stmt;
GError *error = NULL;
stmt = tracker_db_interface_create_statement (db_interface,
TRACKER_DB_STATEMENT_CACHE_TYPE_UPDATE,
&error,
"UPDATE fts SET \"%s\" = '' WHERE docid = ?",
property);
if (!stmt || error) {
if (error) {
g_warning ("Could not create FTS update statement: %s\n",
error->message);
g_error_free (error);
}
return FALSE;
}
tracker_db_statement_bind_int (stmt, 0, id);
tracker_db_statement_execute (stmt, &error);
g_object_unref (stmt);
if (error) {
g_warning ("Could not execute FTS update: %s", error->message);
g_error_free (error);
return FALSE;
}
return TRUE;
}
gboolean
tracker_db_interface_sqlite_fts_delete_id (TrackerDBInterface *db_interface,
int id)
{
TrackerDBStatement *stmt;
GError *error = NULL;
stmt = tracker_db_interface_create_statement (db_interface,
TRACKER_DB_STATEMENT_CACHE_TYPE_UPDATE,
&error,
"%s",
db_interface->fts_delete_str);
if (!stmt || error) {
if (error) {
g_warning ("Could not create FTS delete statement: %s",
error->message);
g_error_free (error);
}
return FALSE;
}
tracker_db_statement_bind_int (stmt, 0, id);
tracker_db_statement_bind_int (stmt, 1, id);
tracker_db_statement_execute (stmt, &error);
g_object_unref (stmt);
if (error) {
g_warning ("Could not delete FTS content: %s", error->message);
g_error_free (error);
return FALSE;
}
return TRUE;
}
void
tracker_db_interface_sqlite_fts_rebuild_tokens (TrackerDBInterface *interface)
{
tracker_fts_rebuild_tokens (interface->db, "fts");
}
#endif
void
tracker_db_interface_sqlite_reset_collator (TrackerDBInterface *db_interface)
{
g_debug ("Resetting collator in db interface %p", db_interface);
/* This will overwrite any other collation set before, if any */
if (sqlite3_create_collation_v2 (db_interface->db,
TRACKER_COLLATION_NAME,
SQLITE_UTF8,
tracker_collation_init (),
tracker_collation_utf8,
tracker_collation_shutdown) != SQLITE_OK)
{
g_critical ("Couldn't set collation function: %s",
sqlite3_errmsg (db_interface->db));
}
}
static gint
wal_hook (gpointer user_data,
sqlite3 *db,
const gchar *db_name,
gint n_pages)
{
((TrackerDBWalCallback) user_data) (n_pages);
return SQLITE_OK;
}
void
tracker_db_interface_sqlite_wal_hook (TrackerDBInterface *interface,
TrackerDBWalCallback callback)
{
sqlite3_wal_hook (interface->db, wal_hook, callback);
}
static void
tracker_db_interface_sqlite_finalize (GObject *object)
{
TrackerDBInterface *db_interface;
db_interface = TRACKER_DB_INTERFACE (object);
close_database (db_interface);
g_free (db_interface->fts_insert_str);
g_free (db_interface->fts_delete_str);
g_message ("Closed sqlite3 database:'%s'", db_interface->filename);
g_free (db_interface->filename);
g_free (db_interface->busy_status);
G_OBJECT_CLASS (tracker_db_interface_parent_class)->finalize (object);
}
static void
tracker_db_interface_class_init (TrackerDBInterfaceClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->set_property = tracker_db_interface_sqlite_set_property;
object_class->get_property = tracker_db_interface_sqlite_get_property;
object_class->finalize = tracker_db_interface_sqlite_finalize;
g_object_class_install_property (object_class,
PROP_FILENAME,
g_param_spec_string ("filename",
"DB filename",
"DB filename",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (object_class,
PROP_RO,
g_param_spec_boolean ("read-only",
"Read only",
"Whether the connection is read only",
FALSE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
}
static void
prepare_database (TrackerDBInterface *db_interface)
{
db_interface->dynamic_statements = g_hash_table_new_full (g_str_hash, g_str_equal,
NULL,
(GDestroyNotify) g_object_unref);
}
static void
tracker_db_interface_init (TrackerDBInterface *db_interface)
{
db_interface->ro = FALSE;
prepare_database (db_interface);
}
void
tracker_db_interface_set_max_stmt_cache_size (TrackerDBInterface *db_interface,
TrackerDBStatementCacheType cache_type,
guint max_size)
{
TrackerDBStatementLru *stmt_lru;
if (cache_type == TRACKER_DB_STATEMENT_CACHE_TYPE_UPDATE) {
stmt_lru = &db_interface->update_stmt_lru;
} else if (cache_type == TRACKER_DB_STATEMENT_CACHE_TYPE_SELECT) {
stmt_lru = &db_interface->select_stmt_lru;
} else {
return;
}
/* Must be larger than 2 to make sense (to have a tail and head) */
if (max_size > 2) {
stmt_lru->max = max_size;
} else {
stmt_lru->max = 3;
}
}
void
tracker_db_interface_set_busy_handler (TrackerDBInterface *db_interface,
TrackerBusyCallback busy_callback,
const gchar *busy_status,
gpointer busy_user_data)
{
g_return_if_fail (TRACKER_IS_DB_INTERFACE (db_interface));
db_interface->busy_callback = busy_callback;
db_interface->busy_user_data = busy_user_data;
g_free (db_interface->busy_status);
if (busy_status) {
db_interface->busy_status = g_strdup (busy_status);
} else {
db_interface->busy_status = NULL;
}
}
TrackerDBStatement *
tracker_db_interface_create_statement (TrackerDBInterface *db_interface,
TrackerDBStatementCacheType cache_type,
GError **error,
const gchar *query,
...)
{
TrackerDBStatementLru *stmt_lru = NULL;
TrackerDBStatement *stmt;
va_list args;
gchar *full_query;
g_return_val_if_fail (TRACKER_IS_DB_INTERFACE (db_interface), NULL);
va_start (args, query);
full_query = g_strdup_vprintf (query, args);
va_end (args);
/* There are three kinds of queries:
* a) Cached queries: SELECT and UPDATE ones (cache_type)
* b) Non-Cached queries: NONE ones (cache_type)
* c) Forced Non-Cached: in case of a stmt being already in use, we can't
* reuse it (you can't use two different loops on a sqlite3_stmt, of
* course). This happens with recursive uses of a cursor, for example */
if (cache_type != TRACKER_DB_STATEMENT_CACHE_TYPE_NONE) {
stmt = g_hash_table_lookup (db_interface->dynamic_statements, full_query);
if (stmt && stmt->stmt_is_sunk) {
/* c) Forced non-cached
* prepared statement is still in use, create new uncached one */
stmt = NULL;
/* Make sure to set cache_type here, to ensure the right flow in the
* LRU-cache below */
cache_type = TRACKER_DB_STATEMENT_CACHE_TYPE_NONE;
}
if (cache_type == TRACKER_DB_STATEMENT_CACHE_TYPE_UPDATE) {
/* a) Cached */
stmt_lru = &db_interface->update_stmt_lru;
} else {
/* a) Cached */
stmt_lru = &db_interface->select_stmt_lru;
}
} else {
/* b) Non-Cached */
stmt = NULL;
}
if (!stmt) {
sqlite3_stmt *sqlite_stmt;
int retval;
g_debug ("Preparing query: '%s'", full_query);
retval = sqlite3_prepare_v2 (db_interface->db, full_query, -1, &sqlite_stmt, NULL);
if (retval != SQLITE_OK) {
if (retval == SQLITE_INTERRUPT) {
g_set_error (error,
TRACKER_DB_INTERFACE_ERROR,
TRACKER_DB_INTERRUPTED,
"Interrupted");
} else {
g_set_error (error,
TRACKER_DB_INTERFACE_ERROR,
TRACKER_DB_QUERY_ERROR,
"%s",
sqlite3_errmsg (db_interface->db));
}
g_free (full_query);
return NULL;
}
stmt = tracker_db_statement_sqlite_new (db_interface, sqlite_stmt);
if (cache_type != TRACKER_DB_STATEMENT_CACHE_TYPE_NONE) {
/* use replace instead of insert to make sure we store the string that
belongs to the right sqlite statement to ensure the lifetime of the string
matches the statement */
g_hash_table_replace (db_interface->dynamic_statements,
(gpointer) sqlite3_sql (sqlite_stmt),
stmt);
/* So the ring looks a bit like this: *
* *
* .--tail .--head *
* | | *
* [p-n] -> [p-n] -> [p-n] -> [p-n] *
* ^ | *
* `- [n-p] <- [n-p] <--------' *
* */
if (stmt_lru->size >= stmt_lru->max) {
TrackerDBStatement *new_head;
/* We reached max-size of the LRU stmt cache. Destroy current
* least recently used (stmt_lru.head) and fix the ring. For
* that we take out the current head, and close the ring.
* Then we assign head->next as new head. */
new_head = stmt_lru->head->next;
g_hash_table_remove (db_interface->dynamic_statements,
(gpointer) sqlite3_sql (stmt_lru->head->stmt));
stmt_lru->size--;
stmt_lru->head = new_head;
} else {
if (stmt_lru->size == 0) {
stmt_lru->head = stmt;
stmt_lru->tail = stmt;
}
}
/* Set the current stmt (which is always new here) as the new tail
* (new most recent used). We insert current stmt between head and
* current tail, and we set tail to current stmt. */
stmt_lru->size++;
stmt->next = stmt_lru->head;
stmt_lru->head->prev = stmt;
stmt_lru->tail->next = stmt;
stmt->prev = stmt_lru->tail;
stmt_lru->tail = stmt;
}
} else {
tracker_db_statement_sqlite_reset (stmt);
if (cache_type != TRACKER_DB_STATEMENT_CACHE_TYPE_NONE) {
if (stmt == stmt_lru->head) {
/* Current stmt is least recently used, shift head and tail
* of the ring to efficiently make it most recently used. */
stmt_lru->head = stmt_lru->head->next;
stmt_lru->tail = stmt_lru->tail->next;
} else if (stmt != stmt_lru->tail) {
/* Current statement isn't most recently used, make it most
* recently used now (less efficient way than above). */
/* Take stmt out of the list and close the ring */
stmt->prev->next = stmt->next;
stmt->next->prev = stmt->prev;
/* Put stmt as tail (most recent used) */
stmt->next = stmt_lru->head;
stmt_lru->head->prev = stmt;
stmt->prev = stmt_lru->tail;
stmt_lru->tail->next = stmt;
stmt_lru->tail = stmt;
}
/* if (stmt == tail), it's already the most recently used in the
* ring, so in this case we do nothing of course */
}
}
g_free (full_query);
return (cache_type != TRACKER_DB_STATEMENT_CACHE_TYPE_NONE) ? g_object_ref (stmt) : stmt;
}
static void
execute_stmt (TrackerDBInterface *interface,
sqlite3_stmt *stmt,
GCancellable *cancellable,
GError **error)
{
gint result;
result = SQLITE_OK;
/* Statement is going to start, check if we got a request to reset the
* collator, and if so, do it. */
if (g_atomic_int_add (&interface->n_active_cursors, 1) == 0 &&
g_atomic_int_compare_and_exchange (&(interface->collator_reset_requested), TRUE, FALSE)) {
tracker_db_interface_sqlite_reset_collator (interface);
}
while (result == SQLITE_OK ||
result == SQLITE_ROW) {
if (g_cancellable_is_cancelled (cancellable)) {
result = SQLITE_INTERRUPT;
sqlite3_reset (stmt);
} else {
/* only one statement can be active at the same time per interface */
interface->cancellable = cancellable;
result = stmt_step (stmt);
interface->cancellable = NULL;
}
switch (result) {
case SQLITE_ERROR:
sqlite3_reset (stmt);
break;
case SQLITE_ROW:
break;
default:
break;
}
}
if (result == SQLITE_DONE) {
/* Statement finished, check if we got a request to reset the
* collator, and if so, do it.
*/
if (g_atomic_int_dec_and_test (&interface->n_active_cursors) &&
g_atomic_int_compare_and_exchange (&(interface->collator_reset_requested), TRUE, FALSE)) {
tracker_db_interface_sqlite_reset_collator (interface);
}
} else {
g_atomic_int_add (&interface->n_active_cursors, -1);
/* This is rather fatal */
if (errno != ENOSPC &&
(sqlite3_errcode (interface->db) == SQLITE_IOERR ||
sqlite3_errcode (interface->db) == SQLITE_CORRUPT ||
sqlite3_errcode (interface->db) == SQLITE_NOTADB)) {
g_critical ("SQLite error: %s (errno: %s)",
sqlite3_errmsg (interface->db),
g_strerror (errno));
#ifndef DISABLE_JOURNAL
g_unlink (interface->filename);
g_error ("SQLite experienced an error with file:'%s'. "
"It is either NOT a SQLite database or it is "
"corrupt or there was an IO error accessing the data. "
"This file has now been removed and will be recreated on the next start. "
"Shutting down now.",
interface->filename);
return;
#endif /* DISABLE_JOURNAL */
}
if (!error) {
g_warning ("Could not perform SQLite operation, error:%d->'%s'",
sqlite3_errcode (interface->db),
sqlite3_errmsg (interface->db));
} else {
if (result == SQLITE_INTERRUPT) {
g_set_error (error,
TRACKER_DB_INTERFACE_ERROR,
TRACKER_DB_INTERRUPTED,
"Interrupted");
} else {
g_set_error (error,
TRACKER_DB_INTERFACE_ERROR,
errno != ENOSPC ? TRACKER_DB_QUERY_ERROR : TRACKER_DB_NO_SPACE,
"%s%s%s%s",
sqlite3_errmsg (interface->db),
errno != 0 ? " (strerror of errno (not necessarily related): " : "",
errno != 0 ? g_strerror (errno) : "",
errno != 0 ? ")" : "");
}
}
}
}
void
tracker_db_interface_execute_vquery (TrackerDBInterface *db_interface,
GError **error,
const gchar *query,
va_list args)
{
gchar *full_query;
sqlite3_stmt *stmt;
int retval;
full_query = g_strdup_vprintf (query, args);
/* g_debug ("Running query: '%s'", full_query); */
retval = sqlite3_prepare_v2 (db_interface->db, full_query, -1, &stmt, NULL);
if (retval != SQLITE_OK) {
g_set_error (error,
TRACKER_DB_INTERFACE_ERROR,
TRACKER_DB_QUERY_ERROR,
"%s",
sqlite3_errmsg (db_interface->db));
g_free (full_query);
return;
} else if (stmt == NULL) {
g_set_error (error,
TRACKER_DB_INTERFACE_ERROR,
TRACKER_DB_QUERY_ERROR,
"Could not prepare SQL statement:'%s'",
full_query);
g_free (full_query);
return;
}
execute_stmt (db_interface, stmt, NULL, error);
sqlite3_finalize (stmt);
g_free (full_query);
}
TrackerDBInterface *
tracker_db_interface_sqlite_new (const gchar *filename,
GError **error)
{
TrackerDBInterface *object;
GError *internal_error = NULL;
object = g_initable_new (TRACKER_TYPE_DB_INTERFACE,
NULL,
&internal_error,
"filename", filename,
NULL);
if (internal_error) {
g_propagate_error (error, internal_error);
return NULL;
}
return object;
}
TrackerDBInterface *
tracker_db_interface_sqlite_new_ro (const gchar *filename,
GError **error)
{
TrackerDBInterface *object;
GError *internal_error = NULL;
object = g_initable_new (TRACKER_TYPE_DB_INTERFACE,
NULL,
&internal_error,
"filename", filename,
"read-only", TRUE,
NULL);
if (internal_error) {
g_propagate_error (error, internal_error);
return NULL;
}
return object;
}
gint64
tracker_db_interface_sqlite_get_last_insert_id (TrackerDBInterface *interface)
{
g_return_val_if_fail (TRACKER_IS_DB_INTERFACE (interface), 0);
return (gint64) sqlite3_last_insert_rowid (interface->db);
}
static void
tracker_db_statement_finalize (GObject *object)
{
TrackerDBStatement *stmt;
stmt = TRACKER_DB_STATEMENT (object);
/* A cursor was still open while we're being finalized, because a cursor
* holds its own reference, this means that somebody is unreffing a stmt
* too often. We mustn't sqlite3_finalize the priv->stmt in this case,
* though. It would crash&burn the cursor. */
g_assert (!stmt->stmt_is_sunk);
sqlite3_finalize (stmt->stmt);
G_OBJECT_CLASS (tracker_db_statement_parent_class)->finalize (object);
}
static void
tracker_db_statement_class_init (TrackerDBStatementClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->finalize = tracker_db_statement_finalize;
}
static TrackerDBStatement *
tracker_db_statement_sqlite_new (TrackerDBInterface *db_interface,
sqlite3_stmt *sqlite_stmt)
{
TrackerDBStatement *stmt;
stmt = g_object_new (TRACKER_TYPE_DB_STATEMENT, NULL);
stmt->db_interface = db_interface;
stmt->stmt = sqlite_stmt;
stmt->stmt_is_sunk = FALSE;
return stmt;
}
static void
tracker_db_cursor_close (TrackerDBCursor *cursor)
{
TrackerDBInterface *iface;
g_return_if_fail (TRACKER_IS_DB_CURSOR (cursor));
if (cursor->ref_stmt == NULL) {
/* already closed */
return;
}
/* As soon as we finalize the cursor, check if we need a collator reset
* and notify the iface about the removed cursor */
iface = cursor->ref_stmt->db_interface;
if (g_atomic_int_dec_and_test (&iface->n_active_cursors) &&
g_atomic_int_compare_and_exchange (&(iface->collator_reset_requested), TRUE, FALSE)) {
tracker_db_interface_sqlite_reset_collator (iface);
}
if (cursor->threadsafe) {
tracker_db_manager_lock ();
}
cursor->ref_stmt->stmt_is_sunk = FALSE;
tracker_db_statement_sqlite_reset (cursor->ref_stmt);
g_object_unref (cursor->ref_stmt);
cursor->ref_stmt = NULL;
if (cursor->threadsafe) {
tracker_db_manager_unlock ();
}
}
static void
tracker_db_cursor_finalize (GObject *object)
{
TrackerDBCursor *cursor;
int i;
cursor = TRACKER_DB_CURSOR (object);
tracker_db_cursor_close (cursor);
g_free (cursor->types);
for (i = 0; i < cursor->n_variable_names; i++) {
g_free (cursor->variable_names[i]);
}
g_free (cursor->variable_names);
G_OBJECT_CLASS (tracker_db_cursor_parent_class)->finalize (object);
}
static void
tracker_db_cursor_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
TrackerDBCursor *cursor = TRACKER_DB_CURSOR (object);
switch (prop_id) {
case TRACKER_DB_CURSOR_PROP_N_COLUMNS:
g_value_set_int (value, tracker_db_cursor_get_n_columns (cursor));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
tracker_db_cursor_iter_next_thread (GSimpleAsyncResult *res,
GObject *object,
GCancellable *cancellable)
{
/* run in thread */
TrackerDBCursor *cursor = TRACKER_DB_CURSOR (object);
GError *error = NULL;
gboolean result;
result = db_cursor_iter_next (cursor, cancellable, &error);
if (error) {
g_simple_async_result_set_from_error (res, error);
} else {
g_simple_async_result_set_op_res_gboolean (res, result);
}
}
static void
tracker_db_cursor_iter_next_async (TrackerDBCursor *cursor,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GSimpleAsyncResult *res;
res = g_simple_async_result_new (G_OBJECT (cursor), callback, user_data, tracker_db_cursor_iter_next_async);
g_simple_async_result_run_in_thread (res, tracker_db_cursor_iter_next_thread, 0, cancellable);
g_object_unref (res);
}
static gboolean
tracker_db_cursor_iter_next_finish (TrackerDBCursor *cursor,
GAsyncResult *res,
GError **error)
{
if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) {
return FALSE;
}
return g_simple_async_result_get_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (res));
}
static void
tracker_db_cursor_class_init (TrackerDBCursorClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
TrackerSparqlCursorClass *sparql_cursor_class = TRACKER_SPARQL_CURSOR_CLASS (class);
object_class->finalize = tracker_db_cursor_finalize;
object_class->get_property = tracker_db_cursor_get_property;
sparql_cursor_class->get_value_type = (TrackerSparqlValueType (*) (TrackerSparqlCursor *, gint)) tracker_db_cursor_get_value_type;
sparql_cursor_class->get_variable_name = (const gchar * (*) (TrackerSparqlCursor *, gint)) tracker_db_cursor_get_variable_name;
sparql_cursor_class->get_n_columns = (gint (*) (TrackerSparqlCursor *)) tracker_db_cursor_get_n_columns;
sparql_cursor_class->get_string = (const gchar * (*) (TrackerSparqlCursor *, gint, glong*)) tracker_db_cursor_get_string;
sparql_cursor_class->next = (gboolean (*) (TrackerSparqlCursor *, GCancellable *, GError **)) tracker_db_cursor_iter_next;
sparql_cursor_class->next_async = (void (*) (TrackerSparqlCursor *, GCancellable *, GAsyncReadyCallback, gpointer)) tracker_db_cursor_iter_next_async;
sparql_cursor_class->next_finish = (gboolean (*) (TrackerSparqlCursor *, GAsyncResult *, GError **)) tracker_db_cursor_iter_next_finish;
sparql_cursor_class->rewind = (void (*) (TrackerSparqlCursor *)) tracker_db_cursor_rewind;
sparql_cursor_class->close = (void (*) (TrackerSparqlCursor *)) tracker_db_cursor_close;
sparql_cursor_class->get_integer = (gint64 (*) (TrackerSparqlCursor *, gint)) tracker_db_cursor_get_int;
sparql_cursor_class->get_double = (gdouble (*) (TrackerSparqlCursor *, gint)) tracker_db_cursor_get_double;
sparql_cursor_class->get_boolean = (gboolean (*) (TrackerSparqlCursor *, gint)) tracker_db_cursor_get_boolean;
g_object_class_override_property (object_class, TRACKER_DB_CURSOR_PROP_N_COLUMNS, "n-columns");
}
static TrackerDBCursor *
tracker_db_cursor_sqlite_new (sqlite3_stmt *sqlite_stmt,
TrackerDBStatement *ref_stmt,
TrackerPropertyType *types,
gint n_types,
const gchar **variable_names,
gint n_variable_names,
gboolean threadsafe)
{
TrackerDBCursor *cursor;
TrackerDBInterface *iface;
/* As soon as we create a cursor, check if we need a collator reset
* and notify the iface about the new cursor */
iface = ref_stmt->db_interface;
if (g_atomic_int_add (&iface->n_active_cursors, 1) == 0 &&
g_atomic_int_compare_and_exchange (&(iface->collator_reset_requested), TRUE, FALSE)) {
tracker_db_interface_sqlite_reset_collator (iface);
}
cursor = g_object_new (TRACKER_TYPE_DB_CURSOR, NULL);
cursor->finished = FALSE;
/* used for direct access as libtracker-sparql is thread-safe and
uses a single shared connection with SQLite mutex disabled */
cursor->threadsafe = threadsafe;
cursor->stmt = sqlite_stmt;
ref_stmt->stmt_is_sunk = TRUE;
cursor->ref_stmt = g_object_ref (ref_stmt);
if (types) {
gint i;
cursor->types = g_new (TrackerPropertyType, n_types);
cursor->n_types = n_types;
for (i = 0; i < n_types; i++) {
cursor->types[i] = types[i];
}
}
if (variable_names) {
gint i;
cursor->variable_names = g_new (gchar *, n_variable_names);
cursor->n_variable_names = n_variable_names;
for (i = 0; i < n_variable_names; i++) {
cursor->variable_names[i] = g_strdup (variable_names[i]);
}
}
return cursor;
}
void
tracker_db_statement_bind_double (TrackerDBStatement *stmt,
int index,
double value)
{
g_return_if_fail (TRACKER_IS_DB_STATEMENT (stmt));
g_assert (!stmt->stmt_is_sunk);
sqlite3_bind_double (stmt->stmt, index + 1, value);
}
void
tracker_db_statement_bind_int (TrackerDBStatement *stmt,
int index,
gint64 value)
{
g_return_if_fail (TRACKER_IS_DB_STATEMENT (stmt));
g_assert (!stmt->stmt_is_sunk);
sqlite3_bind_int64 (stmt->stmt, index + 1, value);
}
void
tracker_db_statement_bind_null (TrackerDBStatement *stmt,
int index)
{
g_return_if_fail (TRACKER_IS_DB_STATEMENT (stmt));
g_assert (!stmt->stmt_is_sunk);
sqlite3_bind_null (stmt->stmt, index + 1);
}
void
tracker_db_statement_bind_text (TrackerDBStatement *stmt,
int index,
const gchar *value)
{
g_return_if_fail (TRACKER_IS_DB_STATEMENT (stmt));
g_assert (!stmt->stmt_is_sunk);
sqlite3_bind_text (stmt->stmt, index + 1, value, -1, SQLITE_TRANSIENT);
}
void
tracker_db_cursor_rewind (TrackerDBCursor *cursor)
{
g_return_if_fail (TRACKER_IS_DB_CURSOR (cursor));
if (cursor->threadsafe) {
tracker_db_manager_lock ();
}
sqlite3_reset (cursor->stmt);
cursor->finished = FALSE;
if (cursor->threadsafe) {
tracker_db_manager_unlock ();
}
}
gboolean
tracker_db_cursor_iter_next (TrackerDBCursor *cursor,
GCancellable *cancellable,
GError **error)
{
if (!cursor) {
return FALSE;
}
return db_cursor_iter_next (cursor, cancellable, error);
}
static gboolean
db_cursor_iter_next (TrackerDBCursor *cursor,
GCancellable *cancellable,
GError **error)
{
TrackerDBStatement *stmt = cursor->ref_stmt;
TrackerDBInterface *iface = stmt->db_interface;
if (!cursor->finished) {
guint result;
if (cursor->threadsafe) {
tracker_db_manager_lock ();
}
if (g_cancellable_is_cancelled (cancellable)) {
result = SQLITE_INTERRUPT;
sqlite3_reset (cursor->stmt);
} else {
/* only one statement can be active at the same time per interface */
iface->cancellable = cancellable;
result = stmt_step (cursor->stmt);
iface->cancellable = NULL;
}
if (result == SQLITE_INTERRUPT) {
g_set_error (error,
TRACKER_DB_INTERFACE_ERROR,
TRACKER_DB_INTERRUPTED,
"Interrupted");
} else if (result != SQLITE_ROW && result != SQLITE_DONE) {
g_set_error (error,
TRACKER_DB_INTERFACE_ERROR,
TRACKER_DB_QUERY_ERROR,
"%s", sqlite3_errmsg (iface->db));
}
cursor->finished = (result != SQLITE_ROW);
if (cursor->threadsafe) {
tracker_db_manager_unlock ();
}
}
return (!cursor->finished);
}
guint
tracker_db_cursor_get_n_columns (TrackerDBCursor *cursor)
{
return sqlite3_column_count (cursor->stmt);
}
void
tracker_db_cursor_get_value (TrackerDBCursor *cursor,
guint column,
GValue *value)
{
gint col_type;
col_type = sqlite3_column_type (cursor->stmt, column);
switch (col_type) {
case SQLITE_TEXT:
g_value_init (value, G_TYPE_STRING);
g_value_set_string (value, (gchar *) sqlite3_column_text (cursor->stmt, column));
break;
case SQLITE_INTEGER:
g_value_init (value, G_TYPE_INT64);
g_value_set_int64 (value, sqlite3_column_int64 (cursor->stmt, column));
break;
case SQLITE_FLOAT:
g_value_init (value, G_TYPE_DOUBLE);
g_value_set_double (value, sqlite3_column_double (cursor->stmt, column));
break;
case SQLITE_NULL:
/* just ignore NULLs */
break;
default:
g_critical ("Unknown sqlite3 database column type:%d", col_type);
}
}
gint64
tracker_db_cursor_get_int (TrackerDBCursor *cursor,
guint column)
{
gint64 result;
if (cursor->threadsafe) {
tracker_db_manager_lock ();
}
result = (gint64) sqlite3_column_int64 (cursor->stmt, column);
if (cursor->threadsafe) {
tracker_db_manager_unlock ();
}
return result;
}
gdouble
tracker_db_cursor_get_double (TrackerDBCursor *cursor,
guint column)
{
gdouble result;
if (cursor->threadsafe) {
tracker_db_manager_lock ();
}
result = (gdouble) sqlite3_column_double (cursor->stmt, column);
if (cursor->threadsafe) {
tracker_db_manager_unlock ();
}
return result;
}
static gboolean
tracker_db_cursor_get_boolean (TrackerSparqlCursor *sparql_cursor,
guint column)
{
TrackerDBCursor *cursor = (TrackerDBCursor *) sparql_cursor;
return (g_strcmp0 (tracker_db_cursor_get_string (cursor, column, NULL), "true") == 0);
}
TrackerSparqlValueType
tracker_db_cursor_get_value_type (TrackerDBCursor *cursor,
guint column)
{
gint column_type;
gint n_columns = sqlite3_column_count (cursor->stmt);
g_return_val_if_fail (column < n_columns, TRACKER_SPARQL_VALUE_TYPE_UNBOUND);
if (cursor->threadsafe) {
tracker_db_manager_lock ();
}
column_type = sqlite3_column_type (cursor->stmt, column);
if (cursor->threadsafe) {
tracker_db_manager_unlock ();
}
if (column_type == SQLITE_NULL) {
return TRACKER_SPARQL_VALUE_TYPE_UNBOUND;
} else if (column < cursor->n_types) {
switch (cursor->types[column]) {
case TRACKER_PROPERTY_TYPE_RESOURCE:
return TRACKER_SPARQL_VALUE_TYPE_URI;
case TRACKER_PROPERTY_TYPE_INTEGER:
return TRACKER_SPARQL_VALUE_TYPE_INTEGER;
case TRACKER_PROPERTY_TYPE_DOUBLE:
return TRACKER_SPARQL_VALUE_TYPE_DOUBLE;
case TRACKER_PROPERTY_TYPE_DATETIME:
return TRACKER_SPARQL_VALUE_TYPE_DATETIME;
case TRACKER_PROPERTY_TYPE_BOOLEAN:
return TRACKER_SPARQL_VALUE_TYPE_BOOLEAN;
default:
return TRACKER_SPARQL_VALUE_TYPE_STRING;
}
} else {
return TRACKER_SPARQL_VALUE_TYPE_STRING;
}
}
const gchar*
tracker_db_cursor_get_variable_name (TrackerDBCursor *cursor,
guint column)
{
const gchar *result;
if (cursor->threadsafe) {
tracker_db_manager_lock ();
}
if (column < cursor->n_variable_names) {
result = cursor->variable_names[column];
} else {
result = sqlite3_column_name (cursor->stmt, column);
}
if (cursor->threadsafe) {
tracker_db_manager_unlock ();
}
return result;
}
const gchar*
tracker_db_cursor_get_string (TrackerDBCursor *cursor,
guint column,
glong *length)
{
const gchar *result;
if (cursor->threadsafe) {
tracker_db_manager_lock ();
}
if (length) {
sqlite3_value *val = sqlite3_column_value (cursor->stmt, column);
*length = sqlite3_value_bytes (val);
result = (const gchar *) sqlite3_value_text (val);
} else {
result = (const gchar *) sqlite3_column_text (cursor->stmt, column);
}
if (cursor->threadsafe) {
tracker_db_manager_unlock ();
}
return result;
}
void
tracker_db_statement_execute (TrackerDBStatement *stmt,
GError **error)
{
g_return_if_fail (TRACKER_IS_DB_STATEMENT (stmt));
g_return_if_fail (!stmt->stmt_is_sunk);
execute_stmt (stmt->db_interface, stmt->stmt, NULL, error);
}
TrackerDBCursor *
tracker_db_statement_start_cursor (TrackerDBStatement *stmt,
GError **error)
{
g_return_val_if_fail (TRACKER_IS_DB_STATEMENT (stmt), NULL);
g_return_val_if_fail (!stmt->stmt_is_sunk, NULL);
return tracker_db_cursor_sqlite_new (stmt->stmt, stmt, NULL, 0, NULL, 0, FALSE);
}
TrackerDBCursor *
tracker_db_statement_start_sparql_cursor (TrackerDBStatement *stmt,
TrackerPropertyType *types,
gint n_types,
const gchar **variable_names,
gint n_variable_names,
gboolean threadsafe,
GError **error)
{
g_return_val_if_fail (TRACKER_IS_DB_STATEMENT (stmt), NULL);
g_return_val_if_fail (!stmt->stmt_is_sunk, NULL);
return tracker_db_cursor_sqlite_new (stmt->stmt, stmt, types, n_types, variable_names, n_variable_names, threadsafe);
}
static void
tracker_db_statement_init (TrackerDBStatement *stmt)
{
}
static void
tracker_db_cursor_init (TrackerDBCursor *cursor)
{
}
static void
tracker_db_statement_sqlite_reset (TrackerDBStatement *stmt)
{
g_assert (!stmt->stmt_is_sunk);
sqlite3_reset (stmt->stmt);
sqlite3_clear_bindings (stmt->stmt);
}