From 0f6196e5d79e2bb72806a4bd427c08ee946abae6 Mon Sep 17 00:00:00 2001 From: Richan Fongdasen Date: Mon, 22 Apr 2024 22:13:52 +0700 Subject: [PATCH] Enable database transactions feature --- README.md | 7 -- src/Database/TursoPDO.php | 26 +++++-- tests/Feature/DatabaseTransactionsTest.php | 89 ++++++++++++++++++++++ tests/Feature/TursoPDOTest.php | 36 +++++++++ tests/Unit/Database/TursoPDOTest.php | 17 ----- 5 files changed, 145 insertions(+), 30 deletions(-) create mode 100644 tests/Feature/DatabaseTransactionsTest.php diff --git a/README.md b/README.md index a5a829c..fefee12 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,6 @@ This package provides a Turso database driver for Laravel, allowing you to use T You can find a demo application that uses this Turso database driver in the [richan-fongdasen/pingcrm](https://github.com/richan-fongdasen/pingcrm) repository. -## Unsupported Features - -Some features are not yet supported by this package: - -- Creating and dropping database -- [Database Transactions](https://turso.tech/blog/bring-your-own-sdk-with-tursos-http-api-ff4ccbed) - ## Requirements - PHP 8.2 or higher diff --git a/src/Database/TursoPDO.php b/src/Database/TursoPDO.php index 9e2f524..914d21f 100644 --- a/src/Database/TursoPDO.php +++ b/src/Database/TursoPDO.php @@ -5,7 +5,6 @@ namespace RichanFongdasen\Turso\Database; use PDO; -use RichanFongdasen\Turso\Exceptions\FeatureNotSupportedException; /** * Turso PDO Database Connection. @@ -14,9 +13,14 @@ * that is used by the Turso database driver. * * see: https://www.php.net/manual/en/class.pdo.php + * + * Turso database transactions & interactive queries reference: + * https://docs.turso.tech/sdk/http/reference#interactive-query */ class TursoPDO extends PDO { + protected bool $inTransaction = false; + protected array $lastInsertIds = []; public function __construct( @@ -30,12 +34,18 @@ public function __construct( public function beginTransaction(): bool { - throw new FeatureNotSupportedException('Database transaction is not supported by the current Turso database driver.'); + $this->inTransaction = $this->prepare('BEGIN')->execute(); + + return $this->inTransaction; } public function commit(): bool { - throw new FeatureNotSupportedException('Database transaction is not supported by the current Turso database driver.'); + $result = $this->prepare('COMMIT')->execute(); + + $this->inTransaction = false; + + return $result; } public function exec(string $queryStatement): int @@ -43,12 +53,12 @@ public function exec(string $queryStatement): int $statement = $this->prepare($queryStatement); $statement->execute(); - return $statement->getAffectedRows(); + return $statement->rowCount(); } public function inTransaction(): bool { - return false; + return $this->inTransaction; } public function lastInsertId(?string $name = null): string|false @@ -69,7 +79,11 @@ public function prepare(string $query, array $options = []): TursoPDOStatement public function rollBack(): bool { - throw new FeatureNotSupportedException('Database transaction is not supported by the current Turso database driver.'); + $result = $this->prepare('ROLLBACK')->execute(); + + $this->inTransaction = false; + + return $result; } public function setLastInsertId(?string $name = null, ?int $value = null): void diff --git a/tests/Feature/DatabaseTransactionsTest.php b/tests/Feature/DatabaseTransactionsTest.php new file mode 100644 index 0000000..bc06b78 --- /dev/null +++ b/tests/Feature/DatabaseTransactionsTest.php @@ -0,0 +1,89 @@ +user = User::factory()->create(); +}); + +afterEach(function () { + Schema::dropAllTables(); +}); + +test('it can rollback the transaction', function () { + $this->user->name = 'John Doe'; + $this->user->save(); + + expect(User::count())->toBe(1); + + DB::transaction(function () { + $this->user->name = 'Jane Doe'; + $this->user->save(); + + expect(User::first()->name)->toBe('Jane Doe'); + + DB::rollBack(); + }); + + expect(User::count())->toBe(1); + expect(User::first()->name)->toBe('John Doe'); +})->group('DatabaseTransactionsTest', 'FeatureTest'); + +test('it can rollback the transaction by manually using the transactions', function () { + $this->user->name = 'John Doe'; + $this->user->save(); + + expect(User::count())->toBe(1); + + DB::beginTransaction(); + + $this->user->name = 'Jane Doe'; + $this->user->save(); + + expect(User::first()->name)->toBe('Jane Doe'); + + DB::rollBack(); + + expect(User::count())->toBe(1); + expect(User::first()->name)->toBe('John Doe'); +})->group('DatabaseTransactionsTest', 'FeatureTest'); + +test('it can commit the transaction', function () { + $this->user->name = 'John Doe'; + $this->user->save(); + + expect(User::count())->toBe(1); + + DB::transaction(function () { + $this->user->name = 'Jane Doe'; + $this->user->save(); + + expect(User::first()->name)->toBe('Jane Doe'); + }); + + expect(User::count())->toBe(1); + expect(User::first()->name)->toBe('Jane Doe'); +})->group('DatabaseTransactionsTest', 'FeatureTest'); + +test('it can commit the transaction by manually using the transactions', function () { + $this->user->name = 'John Doe'; + $this->user->save(); + + expect(User::count())->toBe(1); + + DB::beginTransaction(); + + $this->user->name = 'Jane Doe'; + $this->user->save(); + + expect(User::first()->name)->toBe('Jane Doe'); + + DB::commit(); + + expect(User::count())->toBe(1); + expect(User::first()->name)->toBe('Jane Doe'); +})->group('DatabaseTransactionsTest', 'FeatureTest'); diff --git a/tests/Feature/TursoPDOTest.php b/tests/Feature/TursoPDOTest.php index 723d05b..0168683 100644 --- a/tests/Feature/TursoPDOTest.php +++ b/tests/Feature/TursoPDOTest.php @@ -4,8 +4,44 @@ beforeEach(function () { $this->pdo = DB::connection()->getPdo(); + + $this->pdo->exec('CREATE TABLE "projects" ("id" INTEGER PRIMARY KEY, "name" TEXT);'); +}); + +afterEach(function () { + $this->pdo->exec('DROP TABLE IF EXISTS "projects";'); }); test('it can execute SQL command', function () { expect($this->pdo->exec('PRAGMA foreign_keys = ON;'))->toBe(0); })->group('TursoPDOTest', 'FeatureTest'); + +test('it can begin the database transaction, and rollback the changes.', function () { + $this->pdo->beginTransaction(); + + $this->pdo->exec('INSERT INTO "projects" ("name") VALUES (\'Project 1\');'); + $this->pdo->exec('INSERT INTO "projects" ("name") VALUES (\'Project 2\');'); + + expect($this->pdo->inTransaction())->toBeTrue() + ->and($this->pdo->exec('SELECT * FROM "projects";'))->toBe(2); + + $this->pdo->rollBack(); + + expect($this->pdo->inTransaction())->toBeFalse() + ->and($this->pdo->exec('SELECT * FROM "projects";'))->toBe(0); +})->group('TursoPDOTest', 'FeatureTest'); + +test('it can begin the database transaction, and commit the changes.', function () { + $this->pdo->beginTransaction(); + + $this->pdo->exec('INSERT INTO "projects" ("name") VALUES (\'Project 1\');'); + $this->pdo->exec('INSERT INTO "projects" ("name") VALUES (\'Project 2\');'); + + expect($this->pdo->inTransaction())->toBeTrue() + ->and($this->pdo->exec('SELECT * FROM "projects";'))->toBe(2); + + $this->pdo->commit(); + + expect($this->pdo->inTransaction())->toBeFalse() + ->and($this->pdo->exec('SELECT * FROM "projects";'))->toBe(2); +})->group('TursoPDOTest', 'FeatureTest'); diff --git a/tests/Unit/Database/TursoPDOTest.php b/tests/Unit/Database/TursoPDOTest.php index 2c7aaef..6fe60c6 100644 --- a/tests/Unit/Database/TursoPDOTest.php +++ b/tests/Unit/Database/TursoPDOTest.php @@ -2,7 +2,6 @@ use Illuminate\Support\Facades\DB; use RichanFongdasen\Turso\Database\TursoPDO; -use RichanFongdasen\Turso\Exceptions\FeatureNotSupportedException; beforeEach(function () { $this->pdo = DB::connection()->getPdo(); @@ -12,22 +11,6 @@ expect($this->pdo)->toBeInstanceOf(TursoPDO::class); })->group('TursoPDOTest', 'UnitTest'); -test('it raises exception on beginning a database transaction', function () { - $this->pdo->beginTransaction(); -})->throws(FeatureNotSupportedException::class)->group('TursoPDOTest', 'UnitTest'); - -test('it raises exception on committing a database transaction', function () { - $this->pdo->commit(); -})->throws(FeatureNotSupportedException::class)->group('TursoPDOTest', 'UnitTest'); - -test('it raises exception on rolling back a database transaction', function () { - $this->pdo->rollBack(); -})->throws(FeatureNotSupportedException::class)->group('TursoPDOTest', 'UnitTest'); - -test('database transaction status should always be false', function () { - expect($this->pdo->inTransaction())->toBeFalse(); -})->group('TursoPDOTest', 'UnitTest'); - test('it can manage the last insert id value', function () { $this->pdo->setLastInsertId(value: 123);