Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
env:
POSTGRES_USER: runner
POSTGRES_PASSWORD: ''
POSTGRES_DB: postgres

jobs:
vitest:
Expand All @@ -28,7 +32,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, pcntl, fileinfo, pdo, sqlite, pdo_sqlite, pdo_mysql, intl, ftp, zip
extensions: dom, curl, libxml, mbstring, pcntl, fileinfo, pdo, sqlite, pdo_sqlite, pdo_mysql, pdo_pgsql intl, ftp, zip
coverage: pcov

- name: Setup Bun
Expand Down Expand Up @@ -60,6 +64,9 @@ jobs:
stability:
- prefer-stable
- prefer-lowest
exclude:
- os: windows-latest
database: postgres

name: "Run tests: PHP ${{ matrix.php }} - ${{ matrix.database }} - ${{ matrix.stability }} - ${{ matrix.os }}"

Expand Down Expand Up @@ -94,7 +101,10 @@ jobs:
uses: ankane/setup-postgres@v1

- name: Set database config - ${{ matrix.database }}
run: php -r "file_exists('tests/Fixtures/Config/database.config.php') || copy('tests/Fixtures/Config/database.${{ matrix.database }}.php', 'tests/Fixtures/Config/database.config.php');"
run: php -r "copy('tests/Fixtures/Config/database.${{ matrix.database }}.php', 'tests/Fixtures/Config/database.config.php');"

- name: Tempest about
run: php ./tempest about

- name: List discovered locations
run: php ./tempest discovery:status
Expand Down
2 changes: 1 addition & 1 deletion packages/container/src/GenericContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function __construct(
private ArrayIterator $singletons = new ArrayIterator(),

/** @var ArrayIterator<array-key, class-string> $initializers */
private ArrayIterator $initializers = new ArrayIterator(),
public ArrayIterator $initializers = new ArrayIterator(),

/** @var ArrayIterator<array-key, class-string> $dynamicInitializers */
private ArrayIterator $dynamicInitializers = new ArrayIterator(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function __construct(
);
}

public function execute(mixed ...$bindings): Id
public function execute(mixed ...$bindings): ?Id
{
return $this->build()->execute(...$bindings);
}
Expand Down
11 changes: 11 additions & 0 deletions packages/database/src/Config/DatabaseDialect.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Tempest\Database\Config;

use PDOException;

enum DatabaseDialect: string
{
case SQLITE = 'sqlite';
Expand All @@ -18,4 +20,13 @@ public function tableNotFoundCode(): string
self::SQLITE => 'HY000',
};
}

public function isTableNotFoundError(PDOException $exception): bool
{
return match ($this) {
self::MYSQL => $exception->getCode() === '42S02' && str_contains($exception->getMessage(), 'table'),
self::SQLITE => $exception->getCode() === 'HY000' && str_contains($exception->getMessage(), 'table'),
self::POSTGRESQL => $exception->getCode() === '42P01' && str_contains($exception->getMessage(), 'relation'),
};
}
}
2 changes: 1 addition & 1 deletion packages/database/src/Config/PostgresConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public function __construct(
#[SensitiveParameter]
public string $port = '5432',
#[SensitiveParameter]
public string $username = '',
public string $username = 'postgres',
#[SensitiveParameter]
public string $password = '',
#[SensitiveParameter]
Expand Down
35 changes: 0 additions & 35 deletions packages/database/src/Connection/CachedConnectionInitializer.php

This file was deleted.

2 changes: 1 addition & 1 deletion packages/database/src/Connection/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public function rollback(): bool;

public function lastInsertId(): false|string;

public function prepare(string $sql): false|PDOStatement;
public function prepare(string $sql): PDOStatement;

public function close(): void;

Expand Down
35 changes: 30 additions & 5 deletions packages/database/src/Connection/PDOConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PDOStatement;
use Tempest\Database\Config\DatabaseConfig;
use Tempest\Database\Exceptions\ConnectionClosed;
use Throwable;

final class PDOConnection implements Connection
{
Expand Down Expand Up @@ -53,13 +54,37 @@ public function lastInsertId(): false|string
return $this->pdo->lastInsertId();
}

public function prepare(string $sql): false|PDOStatement
public function prepare(string $sql): PDOStatement
{
if ($this->pdo === null) {
throw new ConnectionClosed();
}

return $this->pdo->prepare($sql);
$statement = $this->pdo->prepare($sql);

if ($statement === false) {
throw new ConnectionClosed();
}

return $statement;
}

public function ping(): bool
{
try {
$statement = $this->prepare('SELECT 1');
$statement->execute();

return true;
} catch (Throwable) {
return false;
}
}

public function reconnect(): void
{
$this->close();
$this->connect();
}

public function close(): void
Expand All @@ -74,9 +99,9 @@ public function connect(): void
}

$this->pdo = new PDO(
$this->config->dsn,
$this->config->username,
$this->config->password,
dsn: $this->config->dsn,
username: $this->config->username,
password: $this->config->password,
);
}
}
2 changes: 1 addition & 1 deletion packages/database/src/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface Database
{
public function execute(Query $query): void;

public function getLastInsertId(): Id;
public function getLastInsertId(): ?Id;

public function fetch(Query $query): array;

Expand Down
16 changes: 16 additions & 0 deletions packages/database/src/DatabaseDialectInitializer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Tempest\Database;

use Tempest\Container\Container;
use Tempest\Container\Initializer;
use Tempest\Database\Config\DatabaseConfig;
use Tempest\Database\Config\DatabaseDialect;

final class DatabaseDialectInitializer implements Initializer
{
public function initialize(Container $container): DatabaseDialect
{
return $container->get(DatabaseConfig::class)->dialect;
}
}
1 change: 1 addition & 0 deletions packages/database/src/DatabaseInitializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public function initialize(ClassReflector $class, ?string $tag, Container $conta
return new GenericDatabase(
$connection,
new GenericTransactionManager($connection),
$container->get(DatabaseConfig::class)->dialect,
);
}
}
13 changes: 13 additions & 0 deletions packages/database/src/Exceptions/NoLastInsertIdAvailable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Tempest\Database\Exceptions;

use Exception;

final class NoLastInsertIdAvailable extends Exception
{
public function __construct()
{
parent::__construct('No last insert id available.');
}
}
51 changes: 40 additions & 11 deletions packages/database/src/GenericDatabase.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,72 @@
use DateTimeInterface;
use PDO;
use PDOException;
use PDOStatement;
use Tempest\Database\Config\DatabaseDialect;
use Tempest\Database\Connection\Connection;
use Tempest\Database\Exceptions\QueryException;
use Tempest\Database\Transactions\TransactionManager;
use Throwable;

final readonly class GenericDatabase implements Database
final class GenericDatabase implements Database
{
private ?PDOStatement $lastStatement = null;
private ?Query $lastQuery = null;

public function __construct(
private(set) Connection $connection,
private(set) TransactionManager $transactionManager,
private(set) readonly Connection $connection,
private(set) readonly TransactionManager $transactionManager,
private(set) readonly DatabaseDialect $dialect,
) {}

public function execute(Query $query): void
{
$bindings = $this->resolveBindings($query);

try {
$this->connection
->prepare($query->toSql())
->execute($bindings);
foreach (explode(';', $query->toSql()) as $sql) {
if (! trim($sql)) {
continue;
}

$statement = $this->connection->prepare($sql . ';');

$statement->execute($bindings);

$this->lastStatement = $statement;
$this->lastQuery = $query;
}
} catch (PDOException $pdoException) {
throw new QueryException($query, $bindings, $pdoException);
}
}

public function getLastInsertId(): Id
public function getLastInsertId(): ?Id
{
return new Id($this->connection->lastInsertId());
$sql = $this->lastQuery->toSql();

// TODO: properly determine whether a query is an insert or not
if (! str_starts_with($sql, 'INSERT')) {
return null;
}

if ($this->dialect === DatabaseDialect::POSTGRESQL) {
$data = $this->lastStatement->fetch(PDO::FETCH_ASSOC);
$lastInsertId = $data['id'] ?? null;
} else {
$lastInsertId = $this->connection->lastInsertId();
}

return Id::tryFrom($lastInsertId);
}

public function fetch(Query $query): array
{
$bindings = $this->resolveBindings($query);

$pdoQuery = $this->connection->prepare($query->toSql());

$pdoQuery->execute($this->resolveBindings($query));
$pdoQuery->execute($bindings);

return $pdoQuery->fetchAll(PDO::FETCH_NAMED);
}
Expand All @@ -60,8 +91,6 @@ public function withinTransaction(callable $callback): bool
$callback();

$this->transactionManager->commit();
} catch (PDOException) {
return false;
} catch (Throwable) {
$this->transactionManager->rollback();

Expand Down
9 changes: 9 additions & 0 deletions packages/database/src/Id.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
{
public string|int $id;

public static function tryFrom(string|int|self|null $id): ?self
{
if ($id === null) {
return null;
}

return new self($id);
}

public function __construct(string|int|self $id)
{
$id = ($id instanceof self) ? $id->id : $id;
Expand Down
6 changes: 5 additions & 1 deletion packages/database/src/IsDatabaseModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,15 @@ public static function create(mixed ...$params): self

$model = self::new(...$params);

$model->id = query(self::class)
$id = query(self::class)
->insert($model)
->build()
->execute();

if ($id !== null) {
$model->id = new Id($id);
}

return $model;
}

Expand Down
Loading