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
2 changes: 1 addition & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
parameters:
level: 8
level: 10
treatPhpDocTypesAsCertain: false
paths:
- src
Expand Down
2 changes: 1 addition & 1 deletion src/Apply/DryRun.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public static function generate(
return "'" . str_replace(
['\\', "'", "\0", "\n", "\r"],
['\\\\', "''", '\\0', '\\n', '\\r'],
(string) $val,
is_bool($val) ? ($val ? '1' : '0') : (string) $val,
) . "'";
}, $sql);
$output[] = (string) $replaced;
Expand Down
8 changes: 4 additions & 4 deletions src/Apply/SqlGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ final class SqlGenerator
* Generate SQL statements for all operations.
*
* @param array<string, list<string>> $fkDependencies FK dependency map (child to parents).
* @return list<array{sql: string, params: list<mixed>}>
* @return list<array{sql: string, params: list<scalar|null>}>
*/
public static function generate(
MergeResult $result,
Expand Down Expand Up @@ -75,7 +75,7 @@ public static function generate(

/**
* @param \Closure(string): string $q Identifier quoter.
* @return array{sql: string, params: list<mixed>}
* @return array{sql: string, params: list<scalar|null>}
*/
private static function generateInsert(MergeOperation $op, \Closure $q): array
{
Expand All @@ -95,7 +95,7 @@ private static function generateInsert(MergeOperation $op, \Closure $q): array

/**
* @param \Closure(string): string $q Identifier quoter.
* @return array{sql: string, params: list<mixed>}|null
* @return array{sql: string, params: list<scalar|null>}|null
*/
private static function generateUpdate(MergeOperation $op, ?Snapshot $base, \Closure $q): ?array
{
Expand Down Expand Up @@ -135,7 +135,7 @@ private static function generateUpdate(MergeOperation $op, ?Snapshot $base, \Clo

/**
* @param \Closure(string): string $q Identifier quoter.
* @return array{sql: string, params: list<mixed>}
* @return array{sql: string, params: list<scalar|null>}
*/
private static function generateDelete(MergeOperation $op, ?Snapshot $base, \Closure $q): array
{
Expand Down
6 changes: 3 additions & 3 deletions src/CellMerge/CellMergeResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@
{
private function __construct(
public bool $clean,
public mixed $value,
public string|int|float|bool|null $value,
public int $conflicts,
) {
}

public static function resolved(mixed $value): self
public static function resolved(string|int|float|bool|null $value): self
{
return new self(true, $value, 0);
}

public static function conflict(mixed $oursValue, int $conflicts = 1): self
public static function conflict(string|int|float|bool|null $oursValue, int $conflicts = 1): self
{
return new self(false, $oursValue, $conflicts);
}
Expand Down
6 changes: 5 additions & 1 deletion src/CellMerge/CellMerger.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,9 @@ interface CellMerger
* Return CellMergeResult::resolved() if the merge is clean,
* or CellMergeResult::conflict() if it cannot be resolved.
*/
public function merge(mixed $base, mixed $ours, mixed $theirs): CellMergeResult;
public function merge(
string|int|float|bool|null $base,
string|int|float|bool|null $ours,
string|int|float|bool|null $theirs,
): CellMergeResult;
}
11 changes: 9 additions & 2 deletions src/CellMerge/JsonCellMerger.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
*/
final class JsonCellMerger implements CellMerger
{
public function merge(mixed $base, mixed $ours, mixed $theirs): CellMergeResult
{
public function merge(
string|int|float|bool|null $base,
string|int|float|bool|null $ours,
string|int|float|bool|null $theirs,
): CellMergeResult {
$baseObj = self::decode($base);
$oursObj = self::decode($ours);
$theirsObj = self::decode($theirs);
Expand Down Expand Up @@ -101,6 +104,10 @@ private static function decode(mixed $value): mixed
return null;
}

if (!is_scalar($value)) {
return null;
}

try {
return json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException) {
Expand Down
7 changes: 5 additions & 2 deletions src/CellMerge/OpaqueCellMerger.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
*/
final class OpaqueCellMerger implements CellMerger
{
public function merge(mixed $base, mixed $ours, mixed $theirs): CellMergeResult
{
public function merge(
string|int|float|bool|null $base,
string|int|float|bool|null $ours,
string|int|float|bool|null $theirs,
): CellMergeResult {
return CellMergeResult::conflict($ours);
}
}
7 changes: 5 additions & 2 deletions src/CellMerge/TextCellMerger.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
*/
final class TextCellMerger implements CellMerger
{
public function merge(mixed $base, mixed $ours, mixed $theirs): CellMergeResult
{
public function merge(
string|int|float|bool|null $base,
string|int|float|bool|null $ours,
string|int|float|bool|null $theirs,
): CellMergeResult {
$result = ThreeWayMerge::merge(
(string) ($base ?? ''),
(string) ($ours ?? ''),
Expand Down
4 changes: 2 additions & 2 deletions src/Diff/ColumnDiff.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
{
public function __construct(
public string $column,
public mixed $oldValue,
public mixed $newValue,
public string|int|float|bool|null $oldValue,
public string|int|float|bool|null $newValue,
) {
}
}
6 changes: 3 additions & 3 deletions src/Diff/Differ.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ private function diffTable(
}

/**
* @param array<string, mixed> $baseRow
* @param array<string, mixed> $currentRow
* @param array<string, scalar|null> $baseRow
* @param array<string, scalar|null> $currentRow
* @return list<ColumnDiff>
*/
private function diffColumns(array $baseRow, array $currentRow): array
Expand All @@ -116,7 +116,7 @@ private function diffColumns(array $baseRow, array $currentRow): array
/**
* Compare two values, treating NULL as a distinct value.
*/
public static function valuesEqual(mixed $a, mixed $b): bool
public static function valuesEqual(string|int|float|bool|null $a, string|int|float|bool|null $b): bool
{
if ($a === null && $b === null) {
return true;
Expand Down
2 changes: 1 addition & 1 deletion src/Diff/RowDelete.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
/**
* @param string $table Table name.
* @param string $rowKey Row identity key.
* @param array<string, mixed> $oldValues Column values before deletion.
* @param array<string, scalar|null> $oldValues Column values before deletion.
*/
public function __construct(
public string $table,
Expand Down
2 changes: 1 addition & 1 deletion src/Diff/RowInsert.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
/**
* @param string $table Table name.
* @param string $rowKey Row identity key.
* @param array<string, mixed> $values All column values.
* @param array<string, scalar|null> $values All column values.
*/
public function __construct(
public string $table,
Expand Down
2 changes: 1 addition & 1 deletion src/Diff/RowUpdate.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* @param string $table Table name.
* @param string $rowKey Row identity key.
* @param list<ColumnDiff> $columnDiffs Per-column changes.
* @param array<string, mixed> $fullRow Complete current row data.
* @param array<string, scalar|null> $fullRow Complete current row data.
*/
public function __construct(
public string $table,
Expand Down
4 changes: 4 additions & 0 deletions src/Driver/DriverFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ public static function create(PDO $pdo): Driver
{
$pdoDriver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);

if (!is_string($pdoDriver)) {
throw new \RuntimeException('Unable to determine PDO driver name.');
}

$class = self::$drivers[$pdoDriver] ?? null;
if ($class === null) {
throw new \RuntimeException("Unsupported database driver: {$pdoDriver}");
Expand Down
30 changes: 27 additions & 3 deletions src/Driver/MysqlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,15 @@ public function readForeignKeys(PDO $pdo): array

$deps = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$deps[$row['TABLE_NAME']][] = $row['REFERENCED_TABLE_NAME'];
if (!is_array($row)) {
continue;
}
$tableName = $row['TABLE_NAME'] ?? null;
$referenced = $row['REFERENCED_TABLE_NAME'] ?? null;
if (!is_string($tableName) || !is_string($referenced)) {
continue;
}
$deps[$tableName][] = $referenced;
}

return $deps;
Expand All @@ -79,7 +87,15 @@ private function readColumns(PDO $pdo, string $db, string $table): array

$columns = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$columns[$row['COLUMN_NAME']] = $row['COLUMN_TYPE'];
if (!is_array($row)) {
continue;
}
$name = $row['COLUMN_NAME'] ?? null;
$type = $row['COLUMN_TYPE'] ?? null;
if (!is_string($name) || !is_string($type)) {
continue;
}
$columns[$name] = $type;
}

return $columns;
Expand Down Expand Up @@ -125,7 +141,15 @@ private function readUniqueKeys(PDO $pdo, string $db, string $table): array

$keys = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$keys[$row['CONSTRAINT_NAME']][] = $row['COLUMN_NAME'];
if (!is_array($row)) {
continue;
}
$constraint = $row['CONSTRAINT_NAME'] ?? null;
$column = $row['COLUMN_NAME'] ?? null;
if (!is_string($constraint) || !is_string($column)) {
continue;
}
$keys[$constraint][] = $column;
}

return array_values(array_map('array_values', $keys));
Expand Down
58 changes: 49 additions & 9 deletions src/Driver/SqliteDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,19 @@ public function readForeignKeys(PDO $pdo): array

$parents = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$parents[] = $row['table'];
if (!is_array($row)) {
continue;
}
$parent = $row['table'] ?? null;
if (!is_string($parent)) {
continue;
}
$parents[] = $parent;
}

$parents = array_unique($parents);
$parents = array_values(array_unique($parents));
if ($parents !== []) {
$deps[$table] = array_values($parents);
$deps[$table] = $parents;
}
}

Expand All @@ -80,7 +87,15 @@ private function readColumns(PDO $pdo, string $table): array

$columns = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$columns[$row['name']] = strtolower($row['type'] ?: 'text');
if (!is_array($row)) {
continue;
}
$name = $row['name'] ?? null;
$type = $row['type'] ?? null;
if (!is_string($name)) {
continue;
}
$columns[$name] = strtolower(is_string($type) && $type !== '' ? $type : 'text');
}

return $columns;
Expand All @@ -98,8 +113,17 @@ private function readPrimaryKey(PDO $pdo, string $table): array

$pkColumns = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
if ((int) $row['pk'] > 0) {
$pkColumns[(int) $row['pk']] = $row['name'];
if (!is_array($row)) {
continue;
}
$pk = $row['pk'] ?? null;
$name = $row['name'] ?? null;
if (!is_numeric($pk) || !is_string($name)) {
continue;
}
$pkOrder = (int) $pk;
if ($pkOrder > 0) {
$pkColumns[$pkOrder] = $name;
}
}

Expand All @@ -120,22 +144,38 @@ private function readUniqueKeys(PDO $pdo, string $table): array

$uniqueKeys = [];
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $index) {
if ((int) $index['unique'] !== 1) {
if (!is_array($index)) {
continue;
}
$unique = $index['unique'] ?? null;
if (!is_numeric($unique) || (int) $unique !== 1) {
continue;
}

if (($index['origin'] ?? null) === 'pk') {
continue;
}

$colStmt = $pdo->query("PRAGMA index_info(" . $this->quoteIdentifier($index['name']) . ")");
$indexName = $index['name'] ?? null;
if (!is_string($indexName)) {
continue;
}

$colStmt = $pdo->query("PRAGMA index_info(" . $this->quoteIdentifier($indexName) . ")");
if ($colStmt === false) {
continue;
}

$cols = [];
foreach ($colStmt->fetchAll(PDO::FETCH_ASSOC) as $col) {
$cols[] = $col['name'];
if (!is_array($col)) {
continue;
}
$colName = $col['name'] ?? null;
if (!is_string($colName)) {
continue;
}
$cols[] = $colName;
}

if ($cols !== []) {
Expand Down
4 changes: 2 additions & 2 deletions src/Filter/ColumnFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ public static function ignore(array $columns): self
/**
* Remove ignored columns from a row.
*
* @param array<string, mixed> $row
* @return array<string, mixed>
* @param array<string, scalar|null> $row
* @return array<string, scalar|null>
*/
public function applyToRow(array $row): array
{
Expand Down
6 changes: 3 additions & 3 deletions src/Filter/RowFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
*/
final class RowFilter
{
/** @var \Closure(string, array<string, mixed>): bool */
/** @var \Closure(string, array<string, scalar|null>): bool */
private \Closure $predicate;

private function __construct(\Closure $predicate)
Expand All @@ -20,7 +20,7 @@ private function __construct(\Closure $predicate)
/**
* Create a row filter from a predicate.
*
* @param callable(string, array<string, mixed>): bool $predicate
* @param callable(string, array<string, scalar|null>): bool $predicate
* Receives table name and row data, returns true to include the row.
*/
public static function create(callable $predicate): self
Expand All @@ -29,7 +29,7 @@ public static function create(callable $predicate): self
}

/**
* @param array<string, mixed> $row
* @param array<string, scalar|null> $row
*/
public function shouldInclude(string $table, array $row): bool
{
Expand Down
Loading