<commit_before>/* adapters/sqlite/gom-adapter-sqlite.c
 *
 * Copyright (C) 2011 <NAME> <<EMAIL>>
 *
 * This file 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 file 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 General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <sqlite3.h>

#include "gom-adapter-sqlite.h"
#include "gom-condition.h"
#include "gom-enumerable-sqlite.h"
#include "gom-resource.h"
#include "gom-util.h"

G_DEFINE_TYPE(GomAdapterSqlite, gom_adapter_sqlite, GOM_TYPE_ADAPTER)

/*
 * Structures and enums.
 */

struct _GomAdapterSqlitePrivate
{
	sqlite3    *sqlite;
	GHashTable *created_tables;
};

enum
{
	OPENED,
	CLOSED,
	LAST_SIGNAL
};

/*
 * Forward declarations.
 */

static gboolean gom_adapter_sqlite_create          (GomAdapter        *adapter,
                                                    GomEnumerable     *enumerable,
                                                    GError           **error);
static gboolean gom_adapter_sqlite_create_read     (GomAdapterSqlite  *sqlite,
                                                    GomQuery          *query,
                                                    sqlite3_stmt     **stmt,
                                                    GError           **error);
static gboolean gom_adapter_sqlite_create_resource (GomAdapterSqlite  *sqlite,
                                                    GomResource       *resource,
                                                    GError           **error);
static gboolean gom_adapter_sqlite_delete          (GomAdapter        *adapter,
                                                    GomCollection     *collection,
                                                    GError           **error);
static gboolean gom_adapter_sqlite_ensure_table    (GomAdapterSqlite  *sqlite,
                                                    GomResource       *resource,
                                                    GError           **error);
static gboolean gom_adapter_sqlite_execute_sql     (GomAdapterSqlite  *sqlite,
                                                    const gchar       *sql,
                                                    GError           **error);
static void     gom_adapter_sqlite_finalize        (GObject           *object);
static gboolean gom_adapter_sqlite_read            (GomAdapter        *adapter,
                                                    GomQuery          *query,
                                                    GomEnumerable    **enumerable,
                                                    GError           **error);
static gboolean gom_adapter_sqlite_update          (GomAdapter        *adapter,
                                                    GomPropertySet    *properties,
                                                    GValueArray       *values,
                                                    GomCollection     *collection,
                                                    GError           **error);

/*
 * Globals.
 */

static guint    gSignals[LAST_SIGNAL];
static gboolean gLogSql;

static const gchar *
_get_table_name (GType type)
{
	GomResourceClass *resource_class;
	const gchar *table;

	resource_class = g_type_class_ref(type);
	table = resource_class->table;
	g_type_class_unref(resource_class);

	return table;
}

static GValue *
_g_value_dup (const GValue *value)
{
	GValue *dup_value;

	dup_value = g_new0(GValue, 1);
	g_value_init(dup_value, G_VALUE_TYPE(value));
	g_value_copy(value, dup_value);

	return dup_value;
}

static void
_g_value_free (gpointer data)
{
	GValue *value = (GValue *)data;

	if (value) {
		if (G_IS_VALUE(value)) {
			g_value_unset(value);
		}
		g_free(value);
	}
}

static void
_resource_mark_clean (GomResource *resource)
{
	GomPropertyValue **values;
	guint n_values = 0;
	gint i;

	values = gom_resource_get_properties(resource, &n_values);
	for (i = 0; i < n_values; i++) {
		values[i]->is_dirty = FALSE;
	}
	g_free(values);
}

/**
 * gtype_to_sqltype:
 * @type: (in): A #GType.
 *
 * Retrieves the name of the SQLite type that should be used to store
 * instances of @type.
 *
 * Returns: A string which should not be modified or freed.
 * Side effects: None.
 */
static const gchar *
gtype_to_sqltype (GType type)
{
	switch (type) {
	case G_TYPE_INT:
	case G_TYPE_UINT:
	case G_TYPE_INT64:
	case G_TYPE_UINT64:
	case G_TYPE_BOOLEAN:
		return "INTEGER";
	case G_TYPE_STRING:
		return "TEXT";
	case G_TYPE_DOUBLE:
	case G_TYPE_FLOAT:
		return "FLOAT";
	default:
		if (type == G_TYPE_DATE_TIME) {
			return "INTEGER";
		} else if (g_type_is_a(type, G_TYPE_ENUM)) {
			return "INTEGER";
		}
		g_assert_not_reached();
		return "TEXT";
	}
}

static void
gom_adapter_sqlite_append_condition (GomAdapterSqlite *sqlite,
                                     GomCondition     *condition,
                                     GHashTable       *hash,
                                     GString          *str)
{
	const gchar *table;
	const gchar *field;
	GValue *value;
	gchar *key;

	g_string_append(str, "(");

	if (gom_condition_is_a(condition, GOM_CONDITION_EQUAL)) {
		field = g_quark_to_string(condition->u.equality.property->name);
		table = _get_table_name(condition->u.equality.property->owner_type);
		key = g_strdup_printf("%s_%s", table, field);
		value = _g_value_dup(&condition->u.equality.value);
		g_string_append_printf(str, " '%s'.'%s' IS :%s ", table, field, key);
		g_hash_table_insert(hash, key, value);
	} else if (gom_condition_is_a(condition, GOM_CONDITION_AND)) {
		g_assert_not_reached(); /* TODO */
	} else if (gom_condition_is_a(condition, GOM_CONDITION_OR)) {
		g_assert_not_reached(); /* TODO */
	} else {
		g_assert_not_reached();
	}

	g_string_append(str, ") ");
}

void
gom_adapter_sqlite_begin (GomAdapterSqlite *sqlite)
{
	static const gchar sql[] = "BEGIN;";
	gom_adapter_sqlite_execute_sql(sqlite, sql, NULL);
}

/**
 * gom_adapter_sqlite_class_init:
 * @klass: (in): A #GomAdapterSqliteClass.
 *
 * Initializes the #GomAdapterSqliteClass and prepares the vtable.
 *
 * Returns: None.
 * Side effects: None.
 */
static void
gom_adapter_sqlite_class_init (GomAdapterSqliteClass *klass)
{
	GObjectClass *object_class;
	GomAdapterClass *adapter_class;

	adapter_class = GOM_ADAPTER_CLASS(klass);
	adapter_class->create = gom_adapter_sqlite_create;
	adapter_class->read = gom_adapter_sqlite_read;
	adapter_class->update = gom_adapter_sqlite_update;
	adapter_class->delete = gom_adapter_sqlite_delete;

	object_class = G_OBJECT_CLASS(klass);
	object_class->finalize = gom_adapter_sqlite_finalize;
	g_type_class_add_private(object_class, sizeof(GomAdapterSqlitePrivate));

	gSignals[OPENED] = g_signal_new("opened",
	                                GOM_TYPE_ADAPTER_SQLITE,
	                                G_SIGNAL_RUN_FIRST,
	                                0,
	                                NULL,
	                                NULL,
	                                g_cclosure_marshal_VOID__VOID,
	                                G_TYPE_NONE,
	                                0);

	gSignals[CLOSED] = g_signal_new("closed",
	                                GOM_TYPE_ADAPTER_SQLITE,
	                                G_SIGNAL_RUN_FIRST,
	                                0,
	                                NULL,
	                                NULL,
	                                g_cclosure_marshal_VOID__VOID,
	                                G_TYPE_NONE,
	                                0);

	gLogSql = !!g_getenv("GOM_ADAPTER_SQLITE_DEBUG");
}

gboolean
gom_adapter_sqlite_commit (GomAdapterSqlite  *sqlite,
                           GError           **error)
{
	static const gchar sql[] = "COMMIT;";
	return gom_adapter_sqlite_execute_sql(sqlite, sql, error);
}

/**
 * gom_adapter_sqlite_close:
 * @sqlite: (in): A #GomAdapterSqlite.
 *
 * Close the underlying sqlite storage for #GomAdapterSqlite.
 *
 * Returns: None.
 * Side effects: sqlite db is closed.
 */
void
gom_adapter_sqlite_close (GomAdapterSqlite *sqlite)
{
	GomAdapterSqlitePrivate *priv;

	g_return_if_fail(GOM_IS_ADAPTER_SQLITE(sqlite));

	priv = sqlite->priv;

	gom_clear_pointer(&priv->sqlite, sqlite3_close);

	g_signal_emit(sqlite, gSignals[CLOSED], 0);
}

/**
 * gom_adapter_sqlite_create:
 * @adapter: (in): A #GomAdapter.
 * @enumerable: (in): A #GomEnumerable.
 * @error: (error): A location for a #GError, or %NULL.
 *
 * Creates the enumerable of resources in the sqlite database. @enumerable is
 * expected to have a single column and contain GomResources.
 *
 * Returns: None.
 * Side effects: None.
 */
static gboolean
gom_adapter_sqlite_create (GomAdapter     *adapter,
                           GomEnumerable  *enumerable,
                           GError        **error)
{
	GomAdapterSqlite *sqlite = (GomAdapterSqlite *)adapter;
	GomAdapterSqlitePrivate *priv;
	GomEnumerableIter iter;
	GValue value = { 0 };

	g_return_val_if_fail(GOM_IS_ADAPTER_SQLITE(sqlite), FALSE);
	g_return_val_if_fail(GOM_IS_ENUMERABLE(enumerable), FALSE);
	g_return_val_if_fail(gom_enumerable_get_n_columns(enumerable) == 1, FALSE);

	priv = sqlite->priv;

	if (!priv->sqlite) {
		g_set_error(error, GOM_ADAPTER_SQLITE_ERROR,
		            GOM_ADAPTER_SQLITE_ERROR_NOT_OPEN,
		            "Must open a database before creating a resource.");
		return FALSE;
	}

	if (gom_enumerable_iter_init(&iter, enumerable)) {
		do {
			gom_enumerable_get_value(enumerable, &iter, 0, &value);
			if (!g_type_is_a(value.g_type, GOM_TYPE_RESOURCE)) {
				g_set_error(error, GOM_ADAPTER_SQLITE_ERROR,
				            GOM_ADAPTER_SQLITE_ERROR_INVALID_TYPE,
				            "The type %s is not a GomResource.",
				            g_type_name(value.g_type));
				g_value_unset(&value);
				return FALSE;
			}
			if (!gom_adapter_sqlite_ensure_table(sqlite,
			                                     g_value_get_object(&value),
			                                     error)) {
				g_value_unset(&value);
				return FALSE;
			}
			if (!gom_adapter_sqlite_create_resource(sqlite,
			                                        g_value_get_object(&value),
			                                        error)) {
				g_value_unset(&value);
				return FALSE;
			}
			_resource_mark_clean(g_value_get_object(&value));
			g_value_unset(&value);
		} while (gom_enumerable_iter_next(&iter));
	}

	return TRUE;
}

static void
_bind_parameter (sqlite3_stmt *stmt,
                 const gchar  *name,
                 const GValue *value)
{
	gchar *param_name;
	gint column;

	/*
	 * Get the index in the query for the named paramter.
	 */
	param_name = g_strdup_printf(":%s", name);
	column = sqlite3_bind_parameter_index(stmt, param_name);
	g_free(param_name);
	if (!column) {
		g_critical("Column \"%s\" not needed in query.", name);
		return;
	}

	switch (value->g_type) {
	case G_TYPE_INT:
	case G_TYPE_UINT:
	case G_TYPE_BOOLEAN:
		sqlite3_bind_int(stmt, column, value->data[0].v_int);
		break;
	case G_TYPE_INT64:
	case G_TYPE_UINT64:
		sqlite3_bind_int64(stmt, column, value->data[0].v_int64);
		break;
	case G_TYPE_FLOAT:
		sqlite3_bind_double(stmt, column, value->data[0].v_float);
		break;
	case G_TYPE_DOUBLE:
		sqlite3_bind_double(stmt, column, value->data[0].v_double);
		break;
	case G_TYPE_STRING:
		sqlite3_bind_text(stmt, column, value->data[0].v_pointer, -1, NULL);
		break;
	default:
		if (value->g_type == G_TYPE_DATE_TIME) {
			if (g_value_get_boxed(value)) {
				gint64 utc;
				utc = g_date_time_to_unix(g_value_get_boxed(value));
				sqlite3_bind_int(stmt, column, utc);
			}
		} else if (g_type_is_a(value->g_type, G_TYPE_ENUM)) {
			sqlite3_bind_int(stmt, column, value->data[0].v_int);
		}
	}
}

static GHashTable*
resource_to_hash (GomResource *resource)
{
	GomResourceClass *related_class;
	GomPropertyValue **values;
	GomProperty *prop;
	GomResource *related;
	GHashTable *hash;
	GValue *value;
	guint n_values;
	gchar *key;
	gint i;
	gint j;

	g_return_val_if_fail(GOM_IS_RESOURCE(resource), NULL);

	values = gom_resource_get_properties(resource, &n_values);
	hash = g_hash_table_new_full(g_str_hash, g_str_equal,
	                             g_free, _g_value_free);

	for (i = 0; i < n_values; i++) {
		if (g_type_is_a(values[i]->value.g_type, GOM_TYPE_RESOURCE)) {
			related_class = g_type_class_peek(values[i]->value.g_type);

			related = g_value_get_object(&values[i]->value);
			if (!related || !gom_resource_is_dirty(related)) {
				continue;<commit_msg>dont care about dirty status<commit_after>/* adapters/sqlite/gom-adapter-sqlite.c
 *
 * Copyright (C) 2011 <NAME> <<EMAIL>>
 *
 * This file 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 file 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 General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <sqlite3.h>

#include "gom-adapter-sqlite.h"
#include "gom-condition.h"
#include "gom-enumerable-sqlite.h"
#include "gom-resource.h"
#include "gom-util.h"

G_DEFINE_TYPE(GomAdapterSqlite, gom_adapter_sqlite, GOM_TYPE_ADAPTER)

/*
 * Structures and enums.
 */

struct _GomAdapterSqlitePrivate
{
	sqlite3    *sqlite;
	GHashTable *created_tables;
};

enum
{
	OPENED,
	CLOSED,
	LAST_SIGNAL
};

/*
 * Forward declarations.
 */

static gboolean gom_adapter_sqlite_create          (GomAdapter        *adapter,
                                                    GomEnumerable     *enumerable,
                                                    GError           **error);
static gboolean gom_adapter_sqlite_create_read     (GomAdapterSqlite  *sqlite,
                                                    GomQuery          *query,
                                                    sqlite3_stmt     **stmt,
                                                    GError           **error);
static gboolean gom_adapter_sqlite_create_resource (GomAdapterSqlite  *sqlite,
                                                    GomResource       *resource,
                                                    GError           **error);
static gboolean gom_adapter_sqlite_delete          (GomAdapter        *adapter,
                                                    GomCollection     *collection,
                                                    GError           **error);
static gboolean gom_adapter_sqlite_ensure_table    (GomAdapterSqlite  *sqlite,
                                                    GomResource       *resource,
                                                    GError           **error);
static gboolean gom_adapter_sqlite_execute_sql     (GomAdapterSqlite  *sqlite,
                                                    const gchar       *sql,
                                                    GError           **error);
static void     gom_adapter_sqlite_finalize        (GObject           *object);
static gboolean gom_adapter_sqlite_read            (GomAdapter        *adapter,
                                                    GomQuery          *query,
                                                    GomEnumerable    **enumerable,
                                                    GError           **error);
static gboolean gom_adapter_sqlite_update          (GomAdapter        *adapter,
                                                    GomPropertySet    *properties,
                                                    GValueArray       *values,
                                                    GomCollection     *collection,
                                                    GError           **error);

/*
 * Globals.
 */

static guint    gSignals[LAST_SIGNAL];
static gboolean gLogSql;

static const gchar *
_get_table_name (GType type)
{
	GomResourceClass *resource_class;
	const gchar *table;

	resource_class = g_type_class_ref(type);
	table = resource_class->table;
	g_type_class_unref(resource_class);

	return table;
}

static GValue *
_g_value_dup (const GValue *value)
{
	GValue *dup_value;

	dup_value = g_new0(GValue, 1);
	g_value_init(dup_value, G_VALUE_TYPE(value));
	g_value_copy(value, dup_value);

	return dup_value;
}

static void
_g_value_free (gpointer data)
{
	GValue *value = (GValue *)data;

	if (value) {
		if (G_IS_VALUE(value)) {
			g_value_unset(value);
		}
		g_free(value);
	}
}

static void
_resource_mark_clean (GomResource *resource)
{
	GomPropertyValue **values;
	guint n_values = 0;
	gint i;

	values = gom_resource_get_properties(resource, &n_values);
	for (i = 0; i < n_values; i++) {
		values[i]->is_dirty = FALSE;
	}
	g_free(values);
}

/**
 * gtype_to_sqltype:
 * @type: (in): A #GType.
 *
 * Retrieves the name of the SQLite type that should be used to store
 * instances of @type.
 *
 * Returns: A string which should not be modified or freed.
 * Side effects: None.
 */
static const gchar *
gtype_to_sqltype (GType type)
{
	switch (type) {
	case G_TYPE_INT:
	case G_TYPE_UINT:
	case G_TYPE_INT64:
	case G_TYPE_UINT64:
	case G_TYPE_BOOLEAN:
		return "INTEGER";
	case G_TYPE_STRING:
		return "TEXT";
	case G_TYPE_DOUBLE:
	case G_TYPE_FLOAT:
		return "FLOAT";
	default:
		if (type == G_TYPE_DATE_TIME) {
			return "INTEGER";
		} else if (g_type_is_a(type, G_TYPE_ENUM)) {
			return "INTEGER";
		}
		g_assert_not_reached();
		return "TEXT";
	}
}

static void
gom_adapter_sqlite_append_condition (GomAdapterSqlite *sqlite,
                                     GomCondition     *condition,
                                     GHashTable       *hash,
                                     GString          *str)
{
	const gchar *table;
	const gchar *field;
	GValue *value;
	gchar *key;

	g_string_append(str, "(");

	if (gom_condition_is_a(condition, GOM_CONDITION_EQUAL)) {
		field = g_quark_to_string(condition->u.equality.property->name);
		table = _get_table_name(condition->u.equality.property->owner_type);
		key = g_strdup_printf("%s_%s", table, field);
		value = _g_value_dup(&condition->u.equality.value);
		g_string_append_printf(str, " '%s'.'%s' IS :%s ", table, field, key);
		g_hash_table_insert(hash, key, value);
	} else if (gom_condition_is_a(condition, GOM_CONDITION_AND)) {
		g_assert_not_reached(); /* TODO */
	} else if (gom_condition_is_a(condition, GOM_CONDITION_OR)) {
		g_assert_not_reached(); /* TODO */
	} else {
		g_assert_not_reached();
	}

	g_string_append(str, ") ");
}

void
gom_adapter_sqlite_begin (GomAdapterSqlite *sqlite)
{
	static const gchar sql[] = "BEGIN;";
	gom_adapter_sqlite_execute_sql(sqlite, sql, NULL);
}

/**
 * gom_adapter_sqlite_class_init:
 * @klass: (in): A #GomAdapterSqliteClass.
 *
 * Initializes the #GomAdapterSqliteClass and prepares the vtable.
 *
 * Returns: None.
 * Side effects: None.
 */
static void
gom_adapter_sqlite_class_init (GomAdapterSqliteClass *klass)
{
	GObjectClass *object_class;
	GomAdapterClass *adapter_class;

	adapter_class = GOM_ADAPTER_CLASS(klass);
	adapter_class->create = gom_adapter_sqlite_create;
	adapter_class->read = gom_adapter_sqlite_read;
	adapter_class->update = gom_adapter_sqlite_update;
	adapter_class->delete = gom_adapter_sqlite_delete;

	object_class = G_OBJECT_CLASS(klass);
	object_class->finalize = gom_adapter_sqlite_finalize;
	g_type_class_add_private(object_class, sizeof(GomAdapterSqlitePrivate));

	gSignals[OPENED] = g_signal_new("opened",
	                                GOM_TYPE_ADAPTER_SQLITE,
	                                G_SIGNAL_RUN_FIRST,
	                                0,
	                                NULL,
	                                NULL,
	                                g_cclosure_marshal_VOID__VOID,
	                                G_TYPE_NONE,
	                                0);

	gSignals[CLOSED] = g_signal_new("closed",
	                                GOM_TYPE_ADAPTER_SQLITE,
	                                G_SIGNAL_RUN_FIRST,
	                                0,
	                                NULL,
	                                NULL,
	                                g_cclosure_marshal_VOID__VOID,
	                                G_TYPE_NONE,
	                                0);

	gLogSql = !!g_getenv("GOM_ADAPTER_SQLITE_DEBUG");
}

gboolean
gom_adapter_sqlite_commit (GomAdapterSqlite  *sqlite,
                           GError           **error)
{
	static const gchar sql[] = "COMMIT;";
	return gom_adapter_sqlite_execute_sql(sqlite, sql, error);
}

/**
 * gom_adapter_sqlite_close:
 * @sqlite: (in): A #GomAdapterSqlite.
 *
 * Close the underlying sqlite storage for #GomAdapterSqlite.
 *
 * Returns: None.
 * Side effects: sqlite db is closed.
 */
void
gom_adapter_sqlite_close (GomAdapterSqlite *sqlite)
{
	GomAdapterSqlitePrivate *priv;

	g_return_if_fail(GOM_IS_ADAPTER_SQLITE(sqlite));

	priv = sqlite->priv;

	gom_clear_pointer(&priv->sqlite, sqlite3_close);

	g_signal_emit(sqlite, gSignals[CLOSED], 0);
}

/**
 * gom_adapter_sqlite_create:
 * @adapter: (in): A #GomAdapter.
 * @enumerable: (in): A #GomEnumerable.
 * @error: (error): A location for a #GError, or %NULL.
 *
 * Creates the enumerable of resources in the sqlite database. @enumerable is
 * expected to have a single column and contain GomResources.
 *
 * Returns: None.
 * Side effects: None.
 */
static gboolean
gom_adapter_sqlite_create (GomAdapter     *adapter,
                           GomEnumerable  *enumerable,
                           GError        **error)
{
	GomAdapterSqlite *sqlite = (GomAdapterSqlite *)adapter;
	GomAdapterSqlitePrivate *priv;
	GomEnumerableIter iter;
	GValue value = { 0 };

	g_return_val_if_fail(GOM_IS_ADAPTER_SQLITE(sqlite), FALSE);
	g_return_val_if_fail(GOM_IS_ENUMERABLE(enumerable), FALSE);
	g_return_val_if_fail(gom_enumerable_get_n_columns(enumerable) == 1, FALSE);

	priv = sqlite->priv;

	if (!priv->sqlite) {
		g_set_error(error, GOM_ADAPTER_SQLITE_ERROR,
		            GOM_ADAPTER_SQLITE_ERROR_NOT_OPEN,
		            "Must open a database before creating a resource.");
		return FALSE;
	}

	if (gom_enumerable_iter_init(&iter, enumerable)) {
		do {
			gom_enumerable_get_value(enumerable, &iter, 0, &value);
			if (!g_type_is_a(value.g_type, GOM_TYPE_RESOURCE)) {
				g_set_error(error, GOM_ADAPTER_SQLITE_ERROR,
				            GOM_ADAPTER_SQLITE_ERROR_INVALID_TYPE,
				            "The type %s is not a GomResource.",
				            g_type_name(value.g_type));
				g_value_unset(&value);
				return FALSE;
			}
			if (!gom_adapter_sqlite_ensure_table(sqlite,
			                                     g_value_get_object(&value),
			                                     error)) {
				g_value_unset(&value);
				return FALSE;
			}
			if (!gom_adapter_sqlite_create_resource(sqlite,
			                                        g_value_get_object(&value),
			                                        error)) {
				g_value_unset(&value);
				return FALSE;
			}
			_resource_mark_clean(g_value_get_object(&value));
			g_value_unset(&value);
		} while (gom_enumerable_iter_next(&iter));
	}

	return TRUE;
}

static void
_bind_parameter (sqlite3_stmt *stmt,
                 const gchar  *name,
                 const GValue *value)
{
	gchar *param_name;
	gint column;

	/*
	 * Get the index in the query for the named paramter.
	 */
	param_name = g_strdup_printf(":%s", name);
	column = sqlite3_bind_parameter_index(stmt, param_name);
	g_free(param_name);
	if (!column) {
		g_critical("Column \"%s\" not needed in query.", name);
		return;
	}

	switch (value->g_type) {
	case G_TYPE_INT:
	case G_TYPE_UINT:
	case G_TYPE_BOOLEAN:
		sqlite3_bind_int(stmt, column, value->data[0].v_int);
		break;
	case G_TYPE_INT64:
	case G_TYPE_UINT64:
		sqlite3_bind_int64(stmt, column, value->data[0].v_int64);
		break;
	case G_TYPE_FLOAT:
		sqlite3_bind_double(stmt, column, value->data[0].v_float);
		break;
	case G_TYPE_DOUBLE:
		sqlite3_bind_double(stmt, column, value->data[0].v_double);
		break;
	case G_TYPE_STRING:
		sqlite3_bind_text(stmt, column, value->data[0].v_pointer, -1, NULL);
		break;
	default:
		if (value->g_type == G_TYPE_DATE_TIME) {
			if (g_value_get_boxed(value)) {
				gint64 utc;
				utc = g_date_time_to_unix(g_value_get_boxed(value));
				sqlite3_bind_int(stmt, column, utc);
			}
		} else if (g_type_is_a(value->g_type, G_TYPE_ENUM)) {
			sqlite3_bind_int(stmt, column, value->data[0].v_int);
		}
	}
}

static GHashTable*
resource_to_hash (GomResource *resource)
{
	GomResourceClass *related_class;
	GomPropertyValue **values;
	GomProperty *prop;
	GomResource *related;
	GHashTable *hash;
	GValue *value;
	guint n_values;
	gchar *key;
	gint i;
	gint j;

	g_return_val_if_fail(GOM_IS_RESOURCE(resource), NULL);

	values = gom_resource_get_properties(resource, &n_values);
	hash = g_hash_table_new_full(g_str_hash, g_str_equal,
	                             g_free, _g_value_free);

	for (i = 0; i < n_values; i++) {
		if (g_type_is_a(values[i]->value.g_type, GOM_TYPE_RESOURCE)) {
			related_class = g_type_class_peek(values[i]->value.g_type);

			related = g_value_get_object(&values[i]->value);
			if (!related) {
				continue;
{'fraction_non_alphanumeric': 0.0932676646242147, 'fraction_numerical': 0.004110757775537113, 'mean_word_length': 2.2604627639398154, 'pattern_counts': {'":': 2, '<': 17, '<?xml version=': 0, '>': 69, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 94, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6505355', 'n_tokens_mistral': 8697, 'n_tokens_neox': 8203, 'n_words': 1712}