Passed
Push — dbal ( 18ae5a...530d2d )
by Greg
05:47
created

Connection::diffSchema()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2022 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\DB;
21
22
use DomainException;
23
use Fisharebest\Webtrees\DB\Drivers\DriverInterface;
24
use Fisharebest\Webtrees\DB\Drivers\MySQLDriver;
25
use Fisharebest\Webtrees\DB\Drivers\PostgreSQLDriver;
26
use Fisharebest\Webtrees\DB\Drivers\SQLiteDriver;
27
use Fisharebest\Webtrees\DB\Drivers\SQLServerDriver;
28
use Fisharebest\Webtrees\DB\Exceptions\SchemaException;
29
use Fisharebest\Webtrees\DB\Schema\Schema;
30
use Fisharebest\Webtrees\DB\Schema\Table;
31
use PDO;
32
33
use function array_diff_key;
34
use function array_intersect_key;
35
use function array_keys;
36
use function array_map;
37
use function implode;
38
use function in_array;
39
40
/**
41
 * Extend the PDO database connection to support prefixes and introspection.
42
 */
43
class Connection
44
{
45
    private DriverInterface $driver;
46
47
    /**
48
     * @param PDO    $pdo
49
     * @param string $prefix
50
     */
51
    public function __construct(private readonly PDO $pdo, private readonly string $prefix = '')
52
    {
53
        $driver_name = $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
54
55
        $this->driver = match ($driver_name) {
56
            'mysql'    => new MySQLDriver($pdo, $prefix),
57
            'postgres' => new PostgreSQLDriver($pdo, $prefix),
58
            'sqlite'   => new SQLiteDriver($pdo, $prefix),
59
            'sqlsrv'   => new SQLServerDriver($pdo, $prefix),
60
            default    => throw new DomainException('No driver available for ' . $driver_name),
61
        };
62
    }
63
64
    /**
65
     * @param Schema $target
66
     *
67
     * @return array<string>
68
     */
69
    public function diffSchema(Schema $target): array
70
    {
71
        $source = $this->driver->introspectSchema();
72
73
        // Collect SQL statements into three groups
74
        $drop_foreign_key_sql       = [];
75
        $create_and_alter_table_sql = [];
76
        $create_foreign_key_sql     = [];
77
78
        foreach ($target->getTables() as $target_table) {
79
            $src_table = $all_tables[$this->prefix . $target_table->getName()] ?? null;
80
81
            if (!in_array($src_table, $dst_tables, true)) {
82
                // Table does not currently exist
83
                $create_and_alter_table_sql = [...$create_and_alter_table_sql, ...$this->driver->generateTableSql($target_table)];
84
            } else {
85
                // Table exists - check columns
86
                $src_columns     = $this->driver->listColumns($target_table->getName());
87
                $src_columns_sql = array_map($this->driver->introspectColumn(...), $src_columns);
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected ')' on line 87 at column 80
Loading history...
88
                $dst_columns_sql = array_map($this->driver->columnSql(...), $target_table->getColumns());
89
90
                $drop_columns  = array_map($this->driver->dropColumnSQL(...), array_keys(array_diff_key($src_columns, $target_table->getColumns())));
91
                $add_columns   = array_map($this->driver->addColumnSQL(...), array_diff_key($target_table->getColumns(), $src_columns));
92
                $alter_columns = array_map($this->driver->alterColumnSQL(...), array_intersect_key($target_table->getColumns(), $src_columns));
93
94
                $changes = [...$drop_columns, ...$alter_columns, ...$add_columns];
95
96
                if ($changes !== []) {
97
                    $alter_table                = 'ALTER TABLE ' . $this->driver->quoteIdentifier($this->prefix . $target_table->getName()) . implode(', ', $changes);
98
                    $create_and_alter_table_sql = [...$create_and_alter_table_sql, $alter_table];
99
                }
100
            }
101
        }
102
103
        // Results need to be in this order
104
        return [...$drop_foreign_key_sql, ...$create_and_alter_table_sql, ...$create_foreign_key_sql];
105
    }
106
107
    /**
108
     * @param string $table_name
109
     *
110
     * @return bool
111
     */
112
    public function tableExists(string $table_name): bool
113
    {
114
        return in_array($this->prefix . $table_name, $this->driver->listTables(), true);
115
    }
116
117
    /**
118
     * @param string $table_name
119
     * @param string $column_name
120
     *
121
     * @return bool
122
     */
123
    public function columnExists(string $table_name, string $column_name): bool
124
    {
125
        return in_array($column_name, $this->driver->listColumns($this->prefix . $table_name), true);
126
    }
127
}
128