/*
 * Seagull: A SQLite helper library
 * Copyright (C) Seagull Developers
 *
 * Seagull is the legal property of its developers, whose names are too
 * numerous to list here. Please refer to the AUTHORS file distributed with
 * this source distribution
 *
 * This program 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 program 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, see <https://www.gnu.org/licenses/>.
 */

#include <sqlite3.h>

#include <seagull.h>

/******************************************************************************
 * Tests
 *****************************************************************************/
static void
test_seagull_statement_new_db_null(void) {
	SeagullStatement *statement = NULL;
	GError *error = NULL;

	statement = seagull_statement_new(NULL, NULL, &error);
	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_NO_DB_PROVIDED);
	g_clear_error(&error);
	g_assert_null(statement);
}

static void
test_seagull_statement_new_sql_null(void) {
	SeagullSqlite3 *db = NULL;
	SeagullStatement *statement = NULL;
	GError *error = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, NULL, &error);
	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_NO_SQL_PROVIDED);
	g_clear_error(&error);
	g_assert_null(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_new_sql_invalid(void) {
	SeagullSqlite3 *db = NULL;
	SeagullStatement *statement = NULL;
	GError *error = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT * from;", &error);
	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_PREPARATION_FAILED);
	g_clear_error(&error);
	g_assert_null(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_new_sql_multiple(void) {
	SeagullSqlite3 *db = NULL;
	SeagullStatement *statement = NULL;
	GError *error = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT 1; SELECT2;", &error);
	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_MORE_THAN_ONE_STATEMENT);
	g_clear_error(&error);
	g_assert_null(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_new_normal(void) {
	SeagullSqlite3 *db = NULL;
	SeagullStatement *statement = NULL;
	GError *error = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT 1", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_double_init(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT 1", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = g_initable_init(G_INITABLE(statement), NULL, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_properties(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	SeagullSqlite3 *db1 = NULL;
	SeagullSqlite3Stmt *stmt = NULL;
	GError *error = NULL;
	char *sql = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = g_initable_new(
		SEAGULL_TYPE_STATEMENT,
		NULL,
		&error,
		"db", db,
		"sql", "SELECT 1",
		NULL);

	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	g_object_get(
		statement,
		"db", &db1,
		"sql", &sql,
		"stmt", &stmt,
		NULL);

	g_assert_true(db == db1);

	g_assert_cmpstr(sql, ==, "SELECT 1");
	g_clear_pointer(&sql, g_free);

	g_assert_nonnull(stmt);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_trailing_semi_colon(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT 1;", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_expanded_no_parameters(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT 1", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT 1");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_expanded_at_sign_not_bound(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT @foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT NULL");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_expanded_colon_not_bound(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT NULL");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_expanded_dollar_sign_not_bound(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT $foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT NULL");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_boolean(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_boolean(statement, ":foo", TRUE, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT 1");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_bytes_null(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_bytes(statement, ":foo", NULL, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT NULL");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_bytes_empty(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GBytes *bytes = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	bytes = g_bytes_new(NULL, 0);
	result = seagull_statement_bind_bytes(statement, ":foo", bytes, &error);
	g_clear_pointer(&bytes, g_bytes_unref);
	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT NULL");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_bytes_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GBytes *bytes = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	bytes = g_bytes_new("seagull", 7);
	result = seagull_statement_bind_bytes(statement, ":foo", bytes, &error);
	g_clear_pointer(&bytes, g_bytes_unref);
	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT x'73656167756c6c'");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_date_time_null(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_date_time(statement, ":foo", NULL, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT NULL");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_date_time_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GDateTime *dt = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	dt = g_date_time_new_from_iso8601("2025-06-27T02:22:19Z", NULL);
	result = seagull_statement_bind_date_time(statement, ":foo", dt, &error);
	g_date_time_unref(dt);

	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT '2025-06-27T02:22:19Z'");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_double_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_double(statement, ":foo", 6.28, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT 6.28");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_double_not_found(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_double(statement, ":bar", 6.28, &error);
	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_BIND_PARAMETER_NOT_FOUND);
	g_clear_error(&error);
	g_assert_false(result);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_error_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	GError *value = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	value = g_error_new(SEAGULL_STATEMENT_ERROR, -1337, "test value");
	result = seagull_statement_bind_error(statement, ":foo", value, &error);
	g_clear_error(&value);

	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==,
	                "SELECT x'73656167756c6c2d73746174656d656e742d6572726f7200c7faffff746573742076616c75650018'");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_error_not_found(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	GError *value = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	value = g_error_new(SEAGULL_STATEMENT_ERROR, -1337, "test value");
	result = seagull_statement_bind_error(statement, ":bar", value, &error);
	g_clear_error(&value);

	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_BIND_PARAMETER_NOT_FOUND);
	g_clear_error(&error);
	g_assert_false(result);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_error_null(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_error(statement, ":foo", NULL, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT NULL");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_int_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_int(statement, ":foo", 1337, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT 1337");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_int_not_found(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_int(statement, ":bar", 1337, &error);
	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_BIND_PARAMETER_NOT_FOUND);
	g_clear_error(&error);
	g_assert_false(result);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_int64_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_int64(statement, ":foo",
	                                      G_GINT64_CONSTANT(0x1cedc0ffee),
	                                      &error);
	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT 124247932910");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_int64_not_found(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_int64(statement, ":bar",
	                                      G_GINT64_CONSTANT(0x1cedc0ffee),
	                                      &error);
	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_BIND_PARAMETER_NOT_FOUND);
	g_clear_error(&error);
	g_assert_false(result);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_mixed(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo, $bar, @baz, :qux",
	                                  &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_text(statement, ":foo", g_strdup("bar"),
	                                     -1, g_free, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	result = seagull_statement_bind_int(statement, "$bar", 1337, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	result = seagull_statement_bind_null(statement, "@baz", &error);
	g_assert_no_error(error);
	g_assert_true(result);

	result = seagull_statement_bind_double(statement, ":qux", 6.28, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT 'bar', 1337, NULL, 6.28");
	seagull_free(expanded);

	/* Clear the bindings and verify it worked. */
	result = seagull_statement_clear_bindings(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT NULL, NULL, NULL, NULL");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_null_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_null(statement, ":foo", &error);
	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT NULL");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_null_not_found(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_null(statement, ":bar", &error);
	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_BIND_PARAMETER_NOT_FOUND);
	g_clear_error(&error);
	g_assert_false(result);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_text_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_text(statement, ":foo", "bar", -1, NULL,
	                                     &error);
	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT 'bar'");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_text_with_destroy(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_text(statement, ":foo", g_strdup("bar"),
	                                     -1, g_free, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT 'bar'");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_text_not_found(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_text(statement, ":bar", "baz", -1, NULL,
	                                     &error);
	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_BIND_PARAMETER_NOT_FOUND);
	g_clear_error(&error);
	g_assert_false(result);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_text_null(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_text(statement, ":foo", NULL, -1, NULL,
	                                     &error);
	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT NULL");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_time_zone_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	GTimeZone *time_zone = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	time_zone = g_time_zone_new_utc();

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_time_zone(statement, ":foo", time_zone,
	                                          &error);
	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT 'UTC'");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	g_clear_pointer(&time_zone, g_time_zone_unref);
}

static void
test_seagull_statement_bind_time_zone_null(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_time_zone(statement, ":foo", NULL, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT NULL");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_uint_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	/* Make sure the 32nd bit is set. */
	result = seagull_statement_bind_uint(statement, ":foo", 1 << 31, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	/* SQLite only stores signed integers, so we have to detect the sign. */
	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT -2147483648");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_uint_not_found(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_int(statement, ":bar", 1 << 31, &error);
	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_BIND_PARAMETER_NOT_FOUND);
	g_clear_error(&error);
	g_assert_false(result);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_uint64_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	char *expanded = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	/* Make sure the 64th bit is set. */
	result = seagull_statement_bind_uint64(statement, ":foo",
	                                       G_GUINT64_CONSTANT(0x8000000000000000),
	                                       &error);
	g_assert_no_error(error);
	g_assert_true(result);

	/* SQLite only stores signed integers so we have to check it that way. */
	expanded = seagull_statement_get_expanded_sql(statement);
	g_assert_cmpstr(expanded, ==, "SELECT -9223372036854775808");
	seagull_free(expanded);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_bind_uint64_not_found(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT :foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_bind_int64(statement, ":bar",
	                                      G_GUINT64_CONSTANT(9223372036854775808),
	                                      &error);
	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_BIND_PARAMETER_NOT_FOUND);
	g_clear_error(&error);
	g_assert_false(result);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_step_reset(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	statement = seagull_statement_new(db, "SELECT 1", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	/* Make sure reset works on new statements. */
	result = seagull_statement_reset(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	/* Step the statement. */
	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	/* Step the statement again which should return false with no error. */
	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_false(result);

	/* Reset the statement again. */
	result = seagull_statement_reset(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	/* Reset the statement again one more time. */
	result = seagull_statement_reset(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_column_normal_asterisk(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	const char *col_text = NULL;
	const char *migrations[] = {
		"CREATE TABLE foo(a TEXT, b INTEGER, c DOUBLE, d INTEGER);",
		"INSERT INTO foo(a, b, c, d) VALUES('xyz', 42, 3.14, 1);",
		NULL
	};
	gint col_int = 0;
	double col_double = 0;
	gboolean col_boolean = FALSE;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	statement = seagull_statement_new(db, "SELECT * from foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	col_text = seagull_statement_column_text(statement, "a", &error);
	g_assert_no_error(error);
	g_assert_cmpstr(col_text, ==, "xyz");

	col_int = seagull_statement_column_int(statement, "b", &error);
	g_assert_no_error(error);
	g_assert_cmpint(col_int, ==, 42);

	col_double = seagull_statement_column_double(statement, "c", &error);
	g_assert_no_error(error);
	g_assert_cmpfloat(col_double, ==, 3.14);

	col_boolean = seagull_statement_column_boolean(statement, "d", &error);
	g_assert_no_error(error);
	g_assert_true(col_boolean);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_column_normal_names(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	const char *col_text = NULL;
	const char *migrations[] = {
		"CREATE TABLE foo(a TEXT, b INTEGER, c DOUBLE);",
		"INSERT INTO foo(a, b, c) VALUES('xyz', 42, 3.14);",
		NULL
	};
	gint col_int = 0;
	double col_double = 0;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	statement = seagull_statement_new(db, "SELECT a, b, c from foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	col_text = seagull_statement_column_text(statement, "a", &error);
	g_assert_no_error(error);
	g_assert_cmpstr(col_text, ==, "xyz");

	col_int = seagull_statement_column_int(statement, "b", &error);
	g_assert_no_error(error);
	g_assert_cmpint(col_int, ==, 42);

	col_double = seagull_statement_column_double(statement, "c", &error);
	g_assert_no_error(error);
	g_assert_cmpfloat(col_double, ==, 3.14);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_column_normal_ases(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	const char *col_text = NULL;
	const char *migrations[] = {
		"CREATE TABLE foo(a TEXT, b INTEGER, c DOUBLE);",
		"INSERT INTO foo(a, b, c) VALUES('xyz', 42, 3.14);",
		NULL
	};
	gint col_int = 0;
	double col_double = 0;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	statement = seagull_statement_new(db,
	                                  "SELECT a AS x, b AS Y, c AS Z1 from foo",
	                                  &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	col_text = seagull_statement_column_text(statement, "a", &error);
	g_assert_no_error(error);
	g_assert_cmpstr(col_text, ==, "xyz");

	col_text = seagull_statement_column_text(statement, "x", &error);
	g_assert_no_error(error);
	g_assert_cmpstr(col_text, ==, "xyz");

	col_int = seagull_statement_column_int(statement, "b", &error);
	g_assert_no_error(error);
	g_assert_cmpint(col_int, ==, 42);

	col_int = seagull_statement_column_int(statement, "Y", &error);
	g_assert_no_error(error);
	g_assert_cmpint(col_int, ==, 42);

	col_double = seagull_statement_column_double(statement, "c", &error);
	g_assert_no_error(error);
	g_assert_cmpfloat(col_double, ==, 3.14);

	col_double = seagull_statement_column_double(statement, "Z1", &error);
	g_assert_no_error(error);
	g_assert_cmpfloat(col_double, ==, 3.14);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_column_normal_table(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	const char *col_text = NULL;
	const char *migrations[] = {
		"CREATE TABLE foo(a TEXT, b INTEGER, c DOUBLE);",
		"INSERT INTO foo(a, b, c) VALUES('xyz', 42, 3.14);",
		NULL
	};
	gint col_int = 0;
	double col_double = 0;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	statement = seagull_statement_new(db,
	                                  "SELECT foo.a, foo.b, foo.c from foo",
	                                  &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	col_text = seagull_statement_column_text(statement, "a", &error);
	g_assert_no_error(error);
	g_assert_cmpstr(col_text, ==, "xyz");

	col_text = seagull_statement_column_text(statement, "foo.a", &error);
	g_assert_no_error(error);
	g_assert_cmpstr(col_text, ==, "xyz");

	col_int = seagull_statement_column_int(statement, "b", &error);
	g_assert_no_error(error);
	g_assert_cmpint(col_int, ==, 42);

	col_int = seagull_statement_column_int(statement, "foo.b", &error);
	g_assert_no_error(error);
	g_assert_cmpint(col_int, ==, 42);

	col_double = seagull_statement_column_double(statement, "c", &error);
	g_assert_no_error(error);
	g_assert_cmpfloat(col_double, ==, 3.14);

	col_double = seagull_statement_column_double(statement, "foo.c", &error);
	g_assert_no_error(error);
	g_assert_cmpfloat(col_double, ==, 3.14);

	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_column_wrong_type(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	const char *col_text = NULL;
	const char *migrations[] = {
		"CREATE TABLE foo(a TEXT, b INTEGER, c DOUBLE);",
		"INSERT INTO foo(a, b, c) VALUES('xyz', 42, 3.14);",
		NULL
	};
	double col_double = 0;
	int col_int = 0;

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	statement = seagull_statement_new(db, "SELECT * from foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	/* Test double */
	col_double = seagull_statement_column_double(statement, "a", &error);
	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_WRONG_COLUMN_TYPE);
	g_clear_error(&error);
	g_assert_cmpfloat(col_double, ==, 0);

	col_double = seagull_statement_column_double(statement, "b", &error);
	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_WRONG_COLUMN_TYPE);
	g_clear_error(&error);
	g_assert_cmpfloat(col_double, ==, 0);

	col_double = seagull_statement_column_double(statement, "c", &error);
	g_assert_no_error(error);
	g_assert_cmpfloat(col_double, ==, 3.14);

	/* Test int */
	col_int = seagull_statement_column_int(statement, "a", &error);
	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_WRONG_COLUMN_TYPE);
	g_clear_error(&error);
	g_assert_cmpint(col_int, ==, 0);

	col_int = seagull_statement_column_int(statement, "b", &error);
	g_assert_no_error(error);
	g_assert_cmpint(col_int, ==, 42);

	col_int = seagull_statement_column_int(statement, "c", &error);
	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_WRONG_COLUMN_TYPE);
	g_clear_error(&error);
	g_assert_cmpint(col_int, ==, 0);

	/* Test text */
	col_text = seagull_statement_column_text(statement, "a", &error);
	g_assert_no_error(error);
	g_assert_cmpstr(col_text, ==, "xyz");

	col_text = seagull_statement_column_text(statement, "b", &error);
	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_WRONG_COLUMN_TYPE);
	g_clear_error(&error);
	g_assert_cmpstr(col_text, ==, NULL);

	col_text = seagull_statement_column_text(statement, "c", &error);
	g_assert_error(error,
	               SEAGULL_STATEMENT_ERROR,
	               SEAGULL_STATEMENT_ERROR_WRONG_COLUMN_TYPE);
	g_clear_error(&error);
	g_assert_cmpstr(col_text, ==, NULL);

	/* Clean up */
	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_column_bytes_null(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GBytes *bytes = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	const char *migrations[] = {
		"CREATE TABLE foo(bar BLOB) STRICT;",
		"INSERT INTO foo(bar) VALUES(null);",
		NULL
	};

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	statement = seagull_statement_new(db, "SELECT * from foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	bytes = seagull_statement_column_bytes(statement, "bar", &error);
	g_assert_no_error(error);
	g_assert_null(bytes);

	/* Clean up */
	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_column_bytes_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GBytes *bytes = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	gconstpointer data = NULL;
	gsize data_size = 0;
	const char *migrations[] = {
		"CREATE TABLE foo(bar BLOB) STRICT;",
		"INSERT INTO foo(bar) VALUES(x'73656167756c6c');",
		NULL
	};

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	statement = seagull_statement_new(db, "SELECT * from foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	bytes = seagull_statement_column_bytes(statement, "bar", &error);
	g_assert_no_error(error);
	g_assert_nonnull(bytes);

	data = g_bytes_get_data(bytes, &data_size);
	g_assert_cmpmem(data, data_size, "seagull", 7);
	g_clear_pointer(&bytes, g_bytes_unref);

	/* Clean up */
	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_column_date_time_null(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GDateTime *dt = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	const char *migrations[] = {
		"CREATE TABLE foo(bar TEXT);",
		"INSERT INTO foo(bar) VALUES(null);",
		NULL
	};

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	statement = seagull_statement_new(db, "SELECT * from foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	dt = seagull_statement_column_date_time(statement, "bar", &error);
	g_assert_no_error(error);
	g_assert_null(dt);

	/* Clean up */
	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_column_date_time_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GDateTime *dt = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	char *iso8601 = NULL;
	const char *migrations[] = {
		"CREATE TABLE foo(bar TEXT);",
		"INSERT INTO foo(bar) VALUES('2025-02-04T21:54:18Z');",
		NULL
	};

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	statement = seagull_statement_new(db, "SELECT * from foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	dt = seagull_statement_column_date_time(statement, "bar", &error);
	g_assert_no_error(error);
	g_assert_nonnull(dt);

	iso8601 = g_date_time_format_iso8601(dt);
	g_assert_cmpstr(iso8601, ==, "2025-02-04T21:54:18Z");
	g_clear_pointer(&iso8601, g_free);

	g_clear_pointer(&dt, g_date_time_unref);

	/* Clean up */
	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_column_error_null(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	GError *value = NULL;
	gboolean result = FALSE;
	const char *migrations[] = {
		"CREATE TABLE foo(bar BYTES);",
		"INSERT INTO foo(bar) VALUES(null);",
		NULL
	};

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	statement = seagull_statement_new(db, "SELECT * from foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	value = seagull_statement_column_error(statement, "bar", &error);
	g_assert_no_error(error);
	g_assert_null(value);

	/* Clean up */
	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_column_error_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	GError *value = NULL;
	gboolean result = FALSE;
	const char *migrations[] = {
		"CREATE TABLE foo(bar BYTES);",
		"INSERT INTO foo(bar) VALUES(x'73656167756c6c2d73746174656d656e742d6572726f7200c7faffff746573742076616c75650018');",
		NULL
	};

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	statement = seagull_statement_new(db, "SELECT * from foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	value = seagull_statement_column_error(statement, "bar", &error);
	g_assert_no_error(error);
	g_assert_nonnull(value);

	g_assert_error(value, SEAGULL_STATEMENT_ERROR, -1337);
	g_assert_cmpstr(value->message, ==, "test value");

	g_clear_error(&value);

	/* Clean up */
	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_column_int64_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	gint64 value = 0;
	const char *migrations[] = {
		"CREATE TABLE foo(bar INTEGER);",
		"INSERT INTO foo(bar) VALUES(0x1cedc0ffee);",
		NULL
	};

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	statement = seagull_statement_new(db, "SELECT * from foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	value = seagull_statement_column_int64(statement, "bar", &error);
	g_assert_no_error(error);

	g_assert_cmpuint(value, ==, G_GINT64_CONSTANT(0x1cedc0ffee));

	/* Clean up */
	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_column_time_zone_null(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	GTimeZone *tz = NULL;
	gboolean result = FALSE;
	const char *migrations[] = {
		"CREATE TABLE foo(tz TEXT);",
		"INSERT INTO foo(tz) VALUES(null);",
		NULL
	};

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	statement = seagull_statement_new(db, "SELECT * from foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	tz = seagull_statement_column_time_zone(statement, "tz", &error);
	g_assert_no_error(error);
	g_assert_null(tz);

	/* Clean up */
	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_column_time_zone_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	GTimeZone *tz = NULL;
	GTimeZone *utc = NULL;
	gboolean result = FALSE;
	const char *migrations[] = {
		"CREATE TABLE foo(tz TEXT);",
		"INSERT INTO foo(tz) VALUES('Z');",
		NULL
	};

	utc = g_time_zone_new_utc();

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	statement = seagull_statement_new(db, "SELECT * from foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	tz = seagull_statement_column_time_zone(statement, "tz", &error);
	g_assert_no_error(error);
	g_assert_nonnull(tz);

	g_assert_cmpint(g_time_zone_get_offset(tz, 0), ==,
	                g_time_zone_get_offset(utc, 0));

	g_clear_pointer(&tz, g_time_zone_unref);

	/* Clean up */
	g_assert_finalize_object(statement);
	g_time_zone_unref(utc);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_column_uint_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	guint64 value = 0;
	const char *migrations[] = {
		"CREATE TABLE foo(bar INTEGER);",
		"INSERT INTO foo(bar) VALUES(0x80000000);",
		NULL
	};

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	statement = seagull_statement_new(db, "SELECT * from foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	value = seagull_statement_column_uint(statement, "bar", &error);
	g_assert_no_error(error);

	g_assert_cmpuint(value, ==, 0x80000000);

	/* Clean up */
	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

static void
test_seagull_statement_column_uint64_normal(void) {
	SeagullStatement *statement = NULL;
	SeagullSqlite3 *db = NULL;
	GError *error = NULL;
	gboolean result = FALSE;
	guint64 value = 0;
	const char *migrations[] = {
		"CREATE TABLE foo(bar int64);",
		"INSERT INTO foo(bar) VALUES(0x8000000000000000);",
		NULL
	};

	db = seagull_db_new_in_memory(&error);
	g_assert_no_error(error);
	g_assert_nonnull(db);

	result = seagull_migrations_run_from_strings(db, migrations, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	statement = seagull_statement_new(db, "SELECT * from foo", &error);
	g_assert_no_error(error);
	g_assert_true(SEAGULL_IS_STATEMENT(statement));

	result = seagull_statement_step(statement, &error);
	g_assert_no_error(error);
	g_assert_true(result);

	value = seagull_statement_column_uint64(statement, "bar", &error);
	g_assert_no_error(error);

	g_assert_cmpuint(value, ==, G_GINT64_CONSTANT(0x8000000000000000));

	/* Clean up */
	g_assert_finalize_object(statement);

	result = seagull_db_close(db, &error);
	g_assert_no_error(error);
	g_assert_true(result);
}

/******************************************************************************
 * Main
 *****************************************************************************/
int
main(int argc, char *argv[]) {
	g_test_init(&argc, &argv, NULL);

	g_test_add_func("/seagull/statement/new/db-null",
	                test_seagull_statement_new_db_null);
	g_test_add_func("/seagull/statement/new/sql-null",
	                test_seagull_statement_new_sql_null);
	g_test_add_func("/seagull/statement/new/sql-invalid",
	                test_seagull_statement_new_sql_invalid);
	g_test_add_func("/seagull/statement/new/sql-multiple",
	                test_seagull_statement_new_sql_multiple);
	g_test_add_func("/seagull/statement/new/normal",
	                test_seagull_statement_new_normal);

	g_test_add_func("/seagull/statement/double-init",
	                test_seagull_statement_double_init);
	g_test_add_func("/seagull/statement/properties",
	                test_seagull_statement_properties);

	g_test_add_func("/seagull/statement/trailing-semi-colon",
	                test_seagull_statement_trailing_semi_colon);

	g_test_add_func("/seagull/statement/expanded/no-parameters",
	                test_seagull_statement_expanded_no_parameters);
	g_test_add_func("/seagull/statement/expanded/at-sign-not-bound",
	                test_seagull_statement_expanded_at_sign_not_bound);
	g_test_add_func("/seagull/statement/expanded/colon-not-bound",
	                test_seagull_statement_expanded_colon_not_bound);
	g_test_add_func("/seagull/statement/expanded/dollar-sign-not-bound",
	                test_seagull_statement_expanded_dollar_sign_not_bound);

	g_test_add_func("/seagull/statement/bind/boolean",
	                test_seagull_statement_bind_boolean);
	g_test_add_func("/seagull/statement/bind/bytes/null",
	                test_seagull_statement_bind_bytes_null);
	g_test_add_func("/seagull/statement/bind/bytes/empty",
	                test_seagull_statement_bind_bytes_empty);
	g_test_add_func("/seagull/statement/bind/bytes/normal",
	                test_seagull_statement_bind_bytes_normal);
	g_test_add_func("/seagull/statement/bind/date-time/null",
	                test_seagull_statement_bind_date_time_null);
	g_test_add_func("/seagull/statement/bind/date-time/normal",
	                test_seagull_statement_bind_date_time_normal);
	g_test_add_func("/seagull/statement/bind/double/normal",
	                test_seagull_statement_bind_double_normal);
	g_test_add_func("/seagull/statement/bind/double/not-found",
	                test_seagull_statement_bind_double_not_found);
	g_test_add_func("/seagull/statement/bind/error/normal",
	                test_seagull_statement_bind_error_normal);
	g_test_add_func("/seagull/statement/bind/error/not-found",
	                test_seagull_statement_bind_error_not_found);
	g_test_add_func("/seagull/statement/bind/error/null",
	                test_seagull_statement_bind_error_null);
	g_test_add_func("/seagull/statement/bind/int/normal",
	                test_seagull_statement_bind_int_normal);
	g_test_add_func("/seagull/statement/bind/int/not-found",
	                test_seagull_statement_bind_int_not_found);
	g_test_add_func("/seagull/statement/bind/int64/normal",
	                test_seagull_statement_bind_int64_normal);
	g_test_add_func("/seagull/statement/bind/int64/not-found",
	                test_seagull_statement_bind_int64_not_found);
	g_test_add_func("/seagull/statement/bind/mixed",
	                test_seagull_statement_bind_mixed);
	g_test_add_func("/seagull/statement/bind/null/normal",
	                test_seagull_statement_bind_null_normal);
	g_test_add_func("/seagull/statement/bind/null/not-found",
	                test_seagull_statement_bind_null_not_found);
	g_test_add_func("/seagull/statement/bind/text/normal",
	                test_seagull_statement_bind_text_normal);
	g_test_add_func("/seagull/statement/bind/text/with-destroy",
	                test_seagull_statement_bind_text_with_destroy);
	g_test_add_func("/seagull/statement/bind/text/not-found",
	                test_seagull_statement_bind_text_not_found);
	g_test_add_func("/seagull/statement/bind/text/null",
	                test_seagull_statement_bind_text_null);
	g_test_add_func("/seagull/statement/bind/time-zone/normal",
	                test_seagull_statement_bind_time_zone_normal);
	g_test_add_func("/seagull/statement/bind/time-zone/null",
	                test_seagull_statement_bind_time_zone_null);
	g_test_add_func("/seagull/statement/bind/uint/normal",
	                test_seagull_statement_bind_uint_normal);
	g_test_add_func("/seagull/statement/bind/uint/not-found",
	                test_seagull_statement_bind_uint_not_found);
	g_test_add_func("/seagull/statement/bind/uint64/normal",
	                test_seagull_statement_bind_uint64_normal);
	g_test_add_func("/seagull/statement/bind/uint64/not-found",
	                test_seagull_statement_bind_uint64_not_found);

	g_test_add_func("/seagull/statement/step-reset",
	                test_seagull_statement_step_reset);

	g_test_add_func("/seagull/statement/column/normal/asterisk",
	                test_seagull_statement_column_normal_asterisk);
	g_test_add_func("/seagull/statement/column/normal/names",
	                test_seagull_statement_column_normal_names);
	g_test_add_func("/seagull/statement/column/normal/ases",
	                test_seagull_statement_column_normal_ases);
	g_test_add_func("/seagull/statement/column/normal/table",
	                test_seagull_statement_column_normal_table);
	g_test_add_func("/seagull/statement/column/wrong-type",
	                test_seagull_statement_column_wrong_type);

	g_test_add_func("/seagull/statement/column/bytes/null",
	                test_seagull_statement_column_bytes_null);
	g_test_add_func("/seagull/statement/column/bytes/normal",
	                test_seagull_statement_column_bytes_normal);
	g_test_add_func("/seagull/statement/column/date-time/null",
	                test_seagull_statement_column_date_time_null);
	g_test_add_func("/seagull/statement/column/date-time/normal",
	                test_seagull_statement_column_date_time_normal);
	g_test_add_func("/seagull/statement/column/error/null",
	                test_seagull_statement_column_error_null);
	g_test_add_func("/seagull/statement/column/error/normal",
	                test_seagull_statement_column_error_normal);
	g_test_add_func("/seagull/statement/column/int64/normal",
	                test_seagull_statement_column_int64_normal);
	g_test_add_func("/seagull/statement/column/time-zone/null",
	                test_seagull_statement_column_time_zone_null);
	g_test_add_func("/seagull/statement/column/time-zone/normal",
	                test_seagull_statement_column_time_zone_normal);
	g_test_add_func("/seagull/statement/column/uint/normal",
	                test_seagull_statement_column_uint_normal);
	g_test_add_func("/seagull/statement/column/uint64/normal",
	                test_seagull_statement_column_uint64_normal);

	return g_test_run();
}
