Skip to content

Commit

Permalink
Add support for multi Turso database connections
Browse files Browse the repository at this point in the history
  • Loading branch information
richan-fongdasen committed Nov 6, 2024
1 parent 3eff744 commit 3b446e3
Show file tree
Hide file tree
Showing 27 changed files with 359 additions and 149 deletions.
30 changes: 23 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ You can find a demo application that uses this Turso database driver in the [ric

- PHP 8.2 or higher
- Laravel 11.0 or higher
- Node.js 16 or higher
- Node.js 18 or higher

## Installation

Expand Down Expand Up @@ -131,7 +131,7 @@ The driver supports the embedded replica feature. If you're unfamiliar with this
Run the sync script manually using the following Artisan command:

```bash
php artisan turso:sync
php artisan turso:sync <connectionName?>
```

> You may encounter an error if the path to the replica database does not exist. This is expected when the replica database has not been created yet.
Expand All @@ -146,24 +146,40 @@ use RichanFongdasen\Turso\Facades\Turso;

if ( DB::hasModifiedRecords() ) {
// Run the sync script immediately
Turso::sync();
DB::sync();

// Run the sync script in the background
Turso::backgroundSync();
DB::backgroundSync();
}

// Sync on the specific connection
DB::connection('turso')->sync();
DB::connection('turso')->backgroundSync();

// Sync on all of the turso database connections
Turso::sync();
Turso::backgroundSync();
```

## Debugging

To debug the HTTP requests and responses sent and received by the Turso database client, enable the debugging feature as follows:

```php
Turso::enableQueryLog();
// Enabling query log on default database connection
DB::enableQueryLog();

// Enabling query log on specific connection
DB::connection('turso')->enableQueryLog();

// Perform some queries
DB::table('users')->get();

// Get the query log
$logs = Turso::getQueryLog();
// Get the query log for default database connection
DB::getQueryLog();

// Get the query log for specific connection
DB::connection('turso')->getQueryLog();
```

## Changelog
Expand Down
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@
"laravel/pint": "^1.14",
"nunomaduro/collision": "^8.1.1",
"orchestra/testbench": "^9.0.0",
"pestphp/pest": "^2.34",
"pestphp/pest-plugin-arch": "^2.7",
"pestphp/pest-plugin-laravel": "^2.3",
"pestphp/pest": "^3.5",
"pestphp/pest-plugin-arch": "^3.0",
"pestphp/pest-plugin-laravel": "^3.0",
"phpstan/extension-installer": "^1.3",
"phpstan/phpstan-deprecation-rules": "^1.1",
"phpstan/phpstan-phpunit": "^1.3",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"sync": "node turso-sync.mjs"
},
"devDependencies": {
"@libsql/client": "^0.6.0"
"@libsql/client": "^0.14.0"
}
}
1 change: 0 additions & 1 deletion phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@ parameters:
ignoreErrors:
- identifier: missingType.iterableValue
- identifier: missingType.generics
- '#Variable method call on RichanFongdasen\\Turso\\TursoClient.#'
31 changes: 25 additions & 6 deletions src/Commands/TursoSyncCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,25 @@
namespace RichanFongdasen\Turso\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Process;
use RuntimeException;

class TursoSyncCommand extends Command
{
public $signature = 'turso:sync';
public $signature = 'turso:sync {connectionName?}';

public $description = 'Sync changes from the remote database to the local replica manually.';

protected function compileRunProcess(): string
protected function compileRunProcess(array $config): string
{
return sprintf(
'%s %s "%s" "%s" "%s"',
$this->getNodePath(),
config('turso-laravel.sync_command.script_filename'),
config('database.connections.turso.db_url'),
config('database.connections.turso.access_token'),
config('database.connections.turso.db_replica'),
data_get($config, 'db_url'),
data_get($config, 'access_token'),
data_get($config, 'db_replica'),
);
}

Expand All @@ -41,16 +42,34 @@ public function handle(): int
{
$timeout = (int) config('turso-laravel.sync_command.timeout');

$connectionName = (string) $this->argument('connectionName');

if (DB::connection($connectionName)->getConfig('driver') !== 'turso') {
$this->error('The specified connection is not a Turso connection.');

return self::FAILURE;
}

if ((string) DB::connection($connectionName)->getConfig('db_replica') === '') {
$this->error('The specified connection does not have a read replica.');

return self::FAILURE;
}

$result = Process::timeout($timeout)
->path(config('turso-laravel.sync_command.script_path') ?? base_path())
->run($this->compileRunProcess());
->run($this->compileRunProcess(
DB::connection($connectionName)->getConfig()
));

if ($result->failed()) {
throw new RuntimeException('Turso sync command failed: ' . $result->errorOutput());
}

$this->info($result->output());

DB::connection($connectionName)->forgetRecordModificationState();

return self::SUCCESS;
}
}
48 changes: 48 additions & 0 deletions src/Database/TursoConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
use Exception;
use Illuminate\Database\Connection;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Facades\Artisan;
use PDO;
use RichanFongdasen\Turso\Jobs\TursoSyncJob;

class TursoConnection extends Connection
{
Expand Down Expand Up @@ -83,4 +85,50 @@ protected function isUniqueConstraintError(Exception $exception): bool
{
return boolval(preg_match('#(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)#i', $exception->getMessage()));
}

public function sync(): void
{
Artisan::call('turso:sync', ['connectionName' => $this->getName()]);
}

public function backgroundSync(): void
{
TursoSyncJob::dispatch((string) $this->getName());
$this->enableQueryLog();
}

public function disableQueryLog(): void
{
parent::disableQueryLog();

$this->tursoPdo()->getClient()->disableQueryLog();
}

public function enableQueryLog(): void
{
parent::enableQueryLog();

$this->tursoPdo()->getClient()->enableQueryLog();
}

public function flushQueryLog(): void
{
parent::flushQueryLog();

$this->tursoPdo()->getClient()->flushQueryLog();
}

public function getQueryLog()
{
return $this->tursoPdo()->getClient()->getQueryLog()->toArray();
}

public function tursoPdo(): TursoPDO
{
if (! $this->pdo instanceof TursoPDO) {
throw new Exception('The current PDO instance is not an instance of TursoPDO.');
}

return $this->pdo;
}
}
2 changes: 1 addition & 1 deletion src/Database/TursoConnector.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ public function connect(array $config)
{
$options = $this->getOptions($config);

return new TursoPDO('sqlite::memory:', null, null, $options);
return new TursoPDO($config, $options);
}
}
19 changes: 15 additions & 4 deletions src/Database/TursoPDO.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace RichanFongdasen\Turso\Database;

use PDO;
use RichanFongdasen\Turso\TursoClient;

/**
* Turso PDO Database Connection.
Expand All @@ -19,17 +20,22 @@
*/
class TursoPDO extends PDO
{
protected TursoClient $client;

protected array $config = [];

protected bool $inTransaction = false;

protected array $lastInsertIds = [];

public function __construct(
string $dsn = 'sqlite::memory:',
?string $username = null,
?string $password = null,
array $config,
?array $options = null
) {
parent::__construct($dsn, $username, $password, $options);
parent::__construct('sqlite::memory:', null, null, $options);

$this->config = $config;
$this->client = new TursoClient($config);
}

public function beginTransaction(): bool
Expand All @@ -56,6 +62,11 @@ public function exec(string $queryStatement): int
return $statement->rowCount();
}

public function getClient(): TursoClient
{
return $this->client;
}

public function inTransaction(): bool
{
return $this->inTransaction;
Expand Down
6 changes: 2 additions & 4 deletions src/Database/TursoPDOStatement.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
use PDOStatement;
use RichanFongdasen\Turso\Enums\PdoParam;
use RichanFongdasen\Turso\Enums\TursoType;
use RichanFongdasen\Turso\Facades\Turso;
use RichanFongdasen\Turso\Http\QueryResponse;

/**
Expand All @@ -33,8 +32,7 @@ public function __construct(
protected TursoPDO $pdo,
protected string $query,
protected array $options = [],
) {
}
) {}

public function setFetchMode(int $mode, mixed ...$args): bool
{
Expand All @@ -61,7 +59,7 @@ public function execute(?array $params = null): bool
$this->bindValue($key, $value, $type->value);
});

$this->response = Turso::query($this->query, array_values($this->bindings));
$this->response = $this->pdo->getClient()->query($this->query, array_values($this->bindings));

$lastId = (int) $this->response->getLastInsertId();
if ($lastId > 0) {
Expand Down
4 changes: 1 addition & 3 deletions src/Database/TursoQueryGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,4 @@

use Illuminate\Database\Query\Grammars\SQLiteGrammar;

class TursoQueryGrammar extends SQLiteGrammar
{
}
class TursoQueryGrammar extends SQLiteGrammar {}
4 changes: 1 addition & 3 deletions src/Database/TursoQueryProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,4 @@

use Illuminate\Database\Query\Processors\SQLiteProcessor;

class TursoQueryProcessor extends SQLiteProcessor
{
}
class TursoQueryProcessor extends SQLiteProcessor {}
3 changes: 2 additions & 1 deletion src/Database/TursoSchemaGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ public function compileDropAllViews(): string
}

#[Override]
public function wrap($value, $prefixAlias = false): string
public function wrap(mixed $value, mixed $prefixAlias = false): string
{
/** @phpstan-ignore arguments.count */
return str_replace('"', '\'', parent::wrap($value, $prefixAlias));
}
}
4 changes: 1 addition & 3 deletions src/Database/TursoSchemaState.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,4 @@

use Illuminate\Database\Schema\SqliteSchemaState;

class TursoSchemaState extends SqliteSchemaState
{
}
class TursoSchemaState extends SqliteSchemaState {}
4 changes: 1 addition & 3 deletions src/Exceptions/FeatureNotSupportedException.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,4 @@

use LogicException;

class FeatureNotSupportedException extends LogicException
{
}
class FeatureNotSupportedException extends LogicException {}
12 changes: 3 additions & 9 deletions src/Http/RequestBody.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@

namespace RichanFongdasen\Turso\Http;

use ErrorException;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Collection;
use InvalidArgumentException;
use RichanFongdasen\Turso\Contracts\TursoQuery;
use RichanFongdasen\Turso\Database\TursoConnection;
use RichanFongdasen\Turso\Database\TursoSchemaGrammar;
use RichanFongdasen\Turso\Queries\CloseQuery;
use RichanFongdasen\Turso\Queries\ExecuteQuery;
Expand Down Expand Up @@ -72,21 +70,17 @@ public function withoutCloseRequest(): self
return $this;
}

public function withForeignKeyConstraints(): self
public function withForeignKeyConstraints(bool $constraintsEnabled): self
{
// Make sure that the foreign key constraints statement
// is getting executed only once.
if ((string) $this->baton !== '') {
return $this;
}

$grammar = app(TursoConnection::class)->getSchemaGrammar();
$grammar = app(TursoSchemaGrammar::class);

if (! ($grammar instanceof TursoSchemaGrammar)) {
throw new ErrorException('The registered schema grammar is not an instance of TursoSchemaGrammar.');
}

$statement = (bool) config('database.connections.turso.foreign_key_constraints')
$statement = $constraintsEnabled
? $grammar->compileEnableForeignKeyConstraints()
: $grammar->compileDisableForeignKeyConstraints();

Expand Down
Loading

0 comments on commit 3b446e3

Please sign in to comment.