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
6 changes: 0 additions & 6 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -798,12 +798,6 @@ parameters:
count: 1
path: src/Phaseolies/Database/Entity/Model.php

-
message: '#^Unsafe usage of new static\(\)\.$#'
identifier: new.static
count: 11
path: src/Phaseolies/Database/Entity/Model.php

-
message: '#^Call to an undefined static method Phaseolies\\Support\\Facades\\URL\:\:current\(\)\.$#'
identifier: staticMethod.notFound
Expand Down
102 changes: 96 additions & 6 deletions src/Phaseolies/Console/Commands/Migrations/MigrateTemporalCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,36 @@ public function handle(): int
);

if (!$dryRun && $this->tableExists($pdo, $driver, $historyTable)) {
// Table exists — check if we need to add the actor column
// Table exists — check if we need to add the actor and/or branch columns
$addedColumns = [];
$addedIndexes = [];

if ($attr?->trackActor && !$this->columnExists($pdo, $driver, $historyTable, 'actor')) {
$pdo->exec($this->addActorColumnSql($driver, $historyTable));
$row('✓', 'green', 'actor column added');
$addedColumns[] = 'actor';
}

if ($attr !== null && $attr->branches && !$this->columnExists($pdo, $driver, $historyTable, 'branch')) {
$pdo->exec($this->addBranchColumnSql($driver, $historyTable));
$addedColumns[] = 'branch';
}

$branchIndex = $driver === 'mysql' ? 'idx_branch' : 'idx_' . $historyTable . '_branch';
if ($attr !== null && $attr->branches && !$this->indexExists($pdo, $driver, $historyTable, $branchIndex)) {
$pdo->exec($this->createBranchIndexSql($driver, $historyTable));
$addedIndexes[] = 'branch';
}

if (!empty($addedColumns) || !empty($addedIndexes)) {
TemporalManager::resetCache();
$status = [];
if (!empty($addedColumns)) {
$status[] = implode(', ', $addedColumns) . ' column' . (count($addedColumns) > 1 ? 's' : '') . ' added';
}
if (!empty($addedIndexes)) {
$status[] = implode(', ', $addedIndexes) . ' index added';
}
$row('✓', 'green', implode('; ', $status));
$created++;
} else {
$row('•', 'yellow', 'already exists');
Expand All @@ -109,9 +135,10 @@ public function handle(): int
}

$statements = TemporalManager::createHistoryTableSql(
historyTable: $historyTable,
driver: $driver,
trackActor: $attr?->trackActor ?? false,
historyTable: $historyTable,
driver: $driver,
trackActor: $attr?->trackActor ?? false,
trackBranches: $attr !== null && $attr->branches,
);

if ($dryRun) {
Expand Down Expand Up @@ -268,7 +295,7 @@ private function columnExists(PDO $pdo, string $driver, string $table, string $c
if ($driver === 'pgsql') {
$stmt = $pdo->prepare(
"SELECT COUNT(*) FROM information_schema.columns
WHERE table_name = ? AND column_name = ?"
WHERE table_schema = current_schema() AND table_name = ? AND column_name = ?"
);
$stmt->execute([$table, $column]);
return (bool) $stmt->fetchColumn();
Expand Down Expand Up @@ -308,4 +335,67 @@ private function addActorColumnSql(string $driver, string $table): string
default => "ALTER TABLE `{$table}` ADD COLUMN `actor` VARCHAR(255) NULL",
};
}

/**
* Generate the ALTER TABLE SQL to add the branch column.
*
* @param string $driver
* @param string $table
* @return string
*/
private function addBranchColumnSql(string $driver, string $table): string
{
return match ($driver) {
'pgsql' => "ALTER TABLE \"{$table}\" ADD COLUMN branch VARCHAR(50) NOT NULL DEFAULT 'main'",
'sqlite' => "ALTER TABLE \"{$table}\" ADD COLUMN branch TEXT NOT NULL DEFAULT 'main'",
default => "ALTER TABLE `{$table}` ADD COLUMN `branch` VARCHAR(50) NOT NULL DEFAULT 'main'",
};
}

/**
* Check whether the given index exists on the given table.
*
* @param PDO $pdo
* @param string $driver
* @param string $table
* @param string $index
* @return bool
*/
private function indexExists(PDO $pdo, string $driver, string $table, string $index): bool
{
try {
if ($driver === 'sqlite') {
foreach ($pdo->query("PRAGMA index_list(\"{$table}\")")->fetchAll(PDO::FETCH_ASSOC) as $row) {
if ($row['name'] === $index) return true;
}
return false;
}
if ($driver === 'pgsql') {
$stmt = $pdo->prepare('SELECT 1 FROM pg_indexes WHERE schemaname = current_schema() AND tablename = ? AND indexname = ?');
$stmt->execute([$table, $index]);
return $stmt->fetchColumn() !== false;
}
$stmt = $pdo->prepare("SHOW INDEX FROM `{$table}` WHERE Key_name = ?");
$stmt->execute([$index]);
return $stmt->fetch() !== false;
} catch (\Throwable) {
return false;
}
}

/**
* Generate the CREATE INDEX SQL for the branch lookup index.
*
* @param string $driver
* @param string $table
* @return string
*/
private function createBranchIndexSql(string $driver, string $table): string

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
$name = $driver === 'mysql' ? 'idx_branch' : 'idx_' . $table . '_branch';
return match ($driver) {
'pgsql', 'sqlite' => "CREATE INDEX \"{$name}\" ON \"{$table}\" (record_id, branch, valid_from)",
default => "CREATE INDEX `{$name}` ON `{$table}` (record_id, branch, valid_from)",
};
}
}
2 changes: 2 additions & 0 deletions src/Phaseolies/Database/Entity/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
use Phaseolies\Database\Entity\Attributes\Hook;

/**
* @phpstan-consistent-constructor
* @property string $__branch Temporal history metadata.
* @method static \Phaseolies\Database\Entity\Builder withoutHook()
*/
abstract class Model implements Jsonable, \ArrayAccess, \JsonSerializable, \Stringable
Expand Down
2 changes: 2 additions & 0 deletions src/Phaseolies/Database/Temporal/Attributes/Temporal.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ final class Temporal
/**
* @param string $suffix
* @param bool $trackActor
* @param bool $branches
*/
public function __construct(
public readonly string $suffix = '_history',
public readonly bool $trackActor = false,
public readonly bool $branches = false,
) {}
}
Loading
Loading