# HG changeset patch
# Node ID 85819f43f29a723c61b2f075853ba893fe14d508
# Parent  6d8ec5ef93790ba84b8bf2a85209c466881352ce
diff --git a/po/POTFILES b/po/POTFILES
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -1,2 +1,3 @@
 seagull/seagullmigrations.c
+seagull/seagulltransaction.c
 seagull/seagullversion.c
diff --git a/seagull/meson.build b/seagull/meson.build
--- a/seagull/meson.build
+++ b/seagull/meson.build
@@ -2,11 +2,13 @@
 
 SEAGULL_SOURCES = [
   'seagullmigrations.c',
+  'seagulltransaction.c',
   'seagullversion.c',
 ]
 
 SEAGULL_HEADERS = [
   'seagullmigrations.h',
+  'seagulltransaction.h',
   'seagulltypes.h',
   'seagullversion.h',
 ]
@@ -111,7 +113,7 @@
 # Library Dependency Object
 ###############################################################################
 seagull_dep = declare_dependency(
-  dependencies : [gio_dep, glib_dep, gobject_dep],
+  dependencies : [gio_dep, glib_dep, gobject_dep, sqlite3_dep],
   include_directories : [toplevel_inc, seagull_inc],
   link_with : seagull_lib,
   sources : [SEAGULL_BUILT_HEADERS, SEAGULL_GENERATED_TARGETS],
diff --git a/seagull/seagulltransaction.c b/seagull/seagulltransaction.c
new file mode 100644
--- /dev/null
+++ b/seagull/seagulltransaction.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022-2025 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 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <sqlite3.h>
+
+#include "seagulltransaction.h"
+
+G_DEFINE_QUARK(seagull-transaction-error, seagull_transaction_error)
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+gboolean
+seagull_transaction_begin(SeagullSqlite3 *db, GError **error) {
+	int result = 0;
+	char *error_message = NULL;
+	gboolean ret = TRUE;
+
+	g_return_val_if_fail(db != NULL, FALSE);
+
+	result = sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &error_message);
+	if(result != SQLITE_OK) {
+		ret = FALSE;
+
+		g_set_error(error,
+		            SEAGULL_TRANSACTION_ERROR,
+		            SEAGULL_TRANSACTION_ERROR_BEGIN_FAILED,
+		            "failed to begin transaction: %s",
+		            error_message);
+	}
+
+	g_clear_pointer(&error_message, sqlite3_free);
+
+	return ret;
+}
+
+gboolean
+seagull_transaction_commit(SeagullSqlite3 *db, GError **error) {
+	int result = 0;
+	char *error_message = NULL;
+	gboolean ret = TRUE;
+
+	g_return_val_if_fail(db != NULL, FALSE);
+
+	result = sqlite3_exec(db, "COMMIT", NULL, NULL, &error_message);
+	if(result != SQLITE_OK) {
+		ret = FALSE;
+
+		g_set_error(error,
+		            SEAGULL_TRANSACTION_ERROR,
+		            SEAGULL_TRANSACTION_ERROR_COMMIT_FAILED,
+		            "failed to commit transaction: %s",
+		            error_message);
+	}
+
+	g_clear_pointer(&error_message, sqlite3_free);
+
+	return ret;
+}
+
+gboolean
+seagull_transaction_rollback(SeagullSqlite3 *db, GError **error) {
+	int result = 0;
+	char *error_message = NULL;
+	gboolean ret = TRUE;
+
+	g_return_val_if_fail(db != NULL, FALSE);
+
+	result = sqlite3_exec(db, "ROLLBACK", NULL, NULL, &error_message);
+	if(result != SQLITE_OK) {
+		ret = FALSE;
+
+		g_set_error(error,
+		            SEAGULL_TRANSACTION_ERROR,
+		            SEAGULL_TRANSACTION_ERROR_ROLLBACK_FAILED,
+		            "failed to rollback transaction: %s",
+		            error_message);
+	}
+
+	g_clear_pointer(&error_message, sqlite3_free);
+
+	return ret;
+}
diff --git a/seagull/seagulltransaction.h b/seagull/seagulltransaction.h
new file mode 100644
--- /dev/null
+++ b/seagull/seagulltransaction.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022-2025 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 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#if !defined(SEAGULL_GLOBAL_HEADER_INSIDE) && !defined(SEAGULL_COMPILATION)
+# error "only <seagull.h> may be included directly"
+#endif
+
+#ifndef SEAGULL_TRANSACTION_H
+#define SEAGULL_TRANSACTION_H
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#include "seagulltypes.h"
+#include "seagullversion.h"
+
+G_BEGIN_DECLS
+
+#define SEAGULL_TRANSACTION_ERROR seagull_transaction_error_quark()
+
+/**
+ * SeagullTransactionError:
+ * @SEAGULL_TRANSACTION_ERROR_BEGIN_FAILED: the transaction failed to start
+ * @SEAGULL_TRANSACTION_ERROR_COMMIT_FAILED: the transaction failed to finish
+ * @SEAGULL_TRANSACTION_ERROR_ROLLBACK_FAILED: the transaction failed to roll
+ *  back.
+ *
+ * Error codes returned by the transaction functions.
+ *
+ * Since: 0.2
+ */
+typedef enum {
+	SEAGULL_TRANSACTION_ERROR_BEGIN_FAILED SEAGULL_AVAILABLE_ENUMERATOR_IN_0_2,
+	SEAGULL_TRANSACTION_ERROR_COMMIT_FAILED SEAGULL_AVAILABLE_ENUMERATOR_IN_0_2,
+	SEAGULL_TRANSACTION_ERROR_ROLLBACK_FAILED SEAGULL_AVAILABLE_ENUMERATOR_IN_0_2,
+} SeagullTransactionError;
+
+/**
+ * seagull_transaction_error_quark:
+ *
+ * The error domain to identify errors with transactions.
+ *
+ * Returns: The error domain.
+ *
+ * Since: 0.2
+ */
+SEAGULL_AVAILABLE_IN_0_2
+GQuark seagull_transaction_error_quark(void);
+
+/**
+ * seagull_transaction_begin:
+ * @db: the database instance
+ * @error: (out) (nullable): a return address for a #GError
+ *
+ * Begins a transaction.
+ *
+ * Return: true on success; otherwise false with @error set.
+ *
+ * Since: 0.2
+ */
+SEAGULL_AVAILABLE_IN_0_2
+gboolean seagull_transaction_begin(SeagullSqlite3 *db, GError **error);
+
+/**
+ * seagull_transaction_commit:
+ * @db: the database instance
+ * @error: (out) (nullable): a return address for a #GError
+ *
+ * Commits a transaction.
+ *
+ * Return: true on success; otherwise false with @error set.
+ *
+ * Since: 0.2
+ */
+SEAGULL_AVAILABLE_IN_0_2
+gboolean seagull_transaction_commit(SeagullSqlite3 *db, GError **error);
+
+/**
+ * seagull_transaction_rollback:
+ * @db: the database instance
+ * @error: (out) (nullable): a return address for a #GError
+ *
+ * Rolls back a transaction.
+ *
+ * Return: true on success; otherwise false with @error set.
+ *
+ * Since: 0.2
+ */
+SEAGULL_AVAILABLE_IN_0_2
+gboolean seagull_transaction_rollback(SeagullSqlite3 *db, GError **error);
+
+G_END_DECLS
+
+#endif /* SEAGULL_TRANSACTION_H */
diff --git a/seagull/tests/meson.build b/seagull/tests/meson.build
--- a/seagull/tests/meson.build
+++ b/seagull/tests/meson.build
@@ -1,4 +1,5 @@
 PROGRAMS = [
+  'transaction',
 ]
 
 foreach PROGRAM : PROGRAMS
diff --git a/seagull/tests/test_transaction.c b/seagull/tests/test_transaction.c
new file mode 100644
--- /dev/null
+++ b/seagull/tests/test_transaction.c
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2022-2025 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 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+
+#include <sqlite3.h>
+
+#include <seagull.h>
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+static void
+test_seagull_transaction_begin_commit(void) {
+	SeagullSqlite3 *db = NULL;
+	GError *error = NULL;
+	int rc = SQLITE_OK;
+	gboolean result = FALSE;
+
+	rc = sqlite3_open(":memory:", &db);
+	g_assert_nonnull(db);
+	g_assert_cmpint(rc, ==, SQLITE_OK);
+
+	result = seagull_transaction_begin(db, &error);
+	g_assert_no_error(error);
+	g_assert_true(result);
+
+	result = seagull_transaction_commit(db, &error);
+	g_assert_no_error(error);
+	g_assert_true(result);
+
+	rc = sqlite3_close(db);
+	g_assert_cmpint(rc, ==, SQLITE_OK);
+}
+
+static void
+test_seagull_transaction_begin_rollback(void) {
+	SeagullSqlite3 *db = NULL;
+	GError *error = NULL;
+	int rc = SQLITE_OK;
+	gboolean result = FALSE;
+
+	rc = sqlite3_open(":memory:", &db);
+	g_assert_nonnull(db);
+	g_assert_cmpint(rc, ==, SQLITE_OK);
+
+	result = seagull_transaction_begin(db, &error);
+	g_assert_no_error(error);
+	g_assert_true(result);
+
+	result = seagull_transaction_rollback(db, &error);
+	g_assert_no_error(error);
+	g_assert_true(result);
+
+	rc = sqlite3_close(db);
+	g_assert_cmpint(rc, ==, SQLITE_OK);
+}
+
+static void
+test_seagull_transaction_begin_begin(void) {
+	SeagullSqlite3 *db = NULL;
+	GError *error = NULL;
+	int rc = SQLITE_OK;
+	gboolean result = FALSE;
+
+	rc = sqlite3_open(":memory:", &db);
+	g_assert_nonnull(db);
+	g_assert_cmpint(rc, ==, SQLITE_OK);
+
+	result = seagull_transaction_begin(db, &error);
+	g_assert_no_error(error);
+	g_assert_true(result);
+
+	result = seagull_transaction_begin(db, &error);
+	g_assert_error(error,
+	               SEAGULL_TRANSACTION_ERROR,
+	               SEAGULL_TRANSACTION_ERROR_BEGIN_FAILED);
+	g_clear_error(&error);
+	g_assert_false(result);
+
+	rc = sqlite3_close(db);
+	g_assert_cmpint(rc, ==, SQLITE_OK);
+}
+
+static void
+test_seagull_transaction_commit_without_begin(void) {
+	SeagullSqlite3 *db = NULL;
+	GError *error = NULL;
+	int rc = SQLITE_OK;
+	gboolean result = FALSE;
+
+	rc = sqlite3_open(":memory:", &db);
+	g_assert_nonnull(db);
+	g_assert_cmpint(rc, ==, SQLITE_OK);
+
+	result = seagull_transaction_commit(db, &error);
+	g_assert_error(error,
+	               SEAGULL_TRANSACTION_ERROR,
+	               SEAGULL_TRANSACTION_ERROR_COMMIT_FAILED);
+	g_clear_error(&error);
+	g_assert_false(result);
+
+	rc = sqlite3_close(db);
+	g_assert_cmpint(rc, ==, SQLITE_OK);
+}
+
+static void
+test_seagull_transaction_rollback_without_begin(void) {
+	SeagullSqlite3 *db = NULL;
+	GError *error = NULL;
+	int rc = SQLITE_OK;
+	gboolean result = FALSE;
+
+	rc = sqlite3_open(":memory:", &db);
+	g_assert_nonnull(db);
+	g_assert_cmpint(rc, ==, SQLITE_OK);
+
+	result = seagull_transaction_rollback(db, &error);
+	g_assert_error(error,
+	               SEAGULL_TRANSACTION_ERROR,
+	               SEAGULL_TRANSACTION_ERROR_ROLLBACK_FAILED);
+	g_clear_error(&error);
+	g_assert_false(result);
+
+	rc = sqlite3_close(db);
+	g_assert_cmpint(rc, ==, SQLITE_OK);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+int
+main(int argc, char *argv[]) {
+	g_test_init(&argc, &argv, NULL);
+
+	g_test_add_func("/transaction/begin-commit",
+	                test_seagull_transaction_begin_commit);
+	g_test_add_func("/transaction/begin-rollback",
+	                test_seagull_transaction_begin_rollback);
+	g_test_add_func("/transaction/begin-begin",
+	                test_seagull_transaction_begin_begin);
+
+	g_test_add_func("/transaction/commit-without-begin",
+	                test_seagull_transaction_commit_without_begin);
+	g_test_add_func("/transaction/rollback-without-begin",
+	                test_seagull_transaction_rollback_without_begin);
+
+	return g_test_run();
+}
