Passed
Push — 2.x ( d36d3c...04f7c3 )
by Aleksei
19:00
created

SQLServerHandler   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 152
Duplicated Lines 0 %

Test Coverage

Coverage 93.33%

Importance

Changes 0
Metric Value
eloc 50
dl 0
loc 152
ccs 56
cts 60
cp 0.9333
rs 10
c 0
b 0
f 0
wmc 27

11 Methods

Rating   Name   Duplication   Size   Complexity  
A hasTable() 0 6 1
A createColumn() 0 4 1
C alterColumn() 0 52 12
A getTableNames() 0 14 4
A getSchema() 0 3 1
A renameTable() 0 5 1
A eraseTable() 0 4 1
A dropIndex() 0 3 1
A disableForeignKeyConstraints() 0 4 2
A enableForeignKeyConstraints() 0 4 2
A renameColumn() 0 10 1
1
<?php
2
3
/**
4
 * This file is part of Cycle ORM package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Cycle\Database\Driver\SQLServer;
13
14
use PDO;
15
use Cycle\Database\Driver\Handler;
16
use Cycle\Database\Driver\SQLServer\Schema\SQLServerColumn;
17
use Cycle\Database\Driver\SQLServer\Schema\SQLServerTable;
18
use Cycle\Database\Exception\SchemaException;
19
use Cycle\Database\Schema\AbstractColumn;
0 ignored issues
show
Bug introduced by
The type Cycle\Database\Schema\AbstractColumn was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
20
use Cycle\Database\Schema\AbstractIndex;
21
use Cycle\Database\Schema\AbstractTable;
22
23
class SQLServerHandler extends Handler
24
{
25
    /**
26
     * @psalm-param non-empty-string $table
27
     */
28 486
    public function getSchema(string $table, string $prefix = null): AbstractTable
29
    {
30 486
        return new SQLServerTable($this->driver, $table, $prefix ?? '');
0 ignored issues
show
Bug introduced by
It seems like $this->driver can also be of type null; however, parameter $driver of Cycle\Database\Driver\SQ...verTable::__construct() does only seem to accept Cycle\Database\Driver\DriverInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

30
        return new SQLServerTable(/** @scrutinizer ignore-type */ $this->driver, $table, $prefix ?? '');
Loading history...
31
    }
32
33 906
    public function getTableNames(string $prefix = ''): array
34
    {
35 906
        $query = "SELECT [table_name] FROM [information_schema].[tables] WHERE [table_type] = 'BASE TABLE'";
36
37 906
        $tables = [];
38 906
        foreach ($this->driver->query($query)->fetchAll(PDO::FETCH_NUM) as $name) {
0 ignored issues
show
Bug introduced by
The method query() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

38
        foreach ($this->driver->/** @scrutinizer ignore-call */ query($query)->fetchAll(PDO::FETCH_NUM) as $name) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
39 454
            if ($prefix !== '' && !str_starts_with($name[0], $prefix)) {
40 2
                continue;
41
            }
42
43 454
            $tables[] = $name[0];
44
        }
45
46 906
        return $tables;
47
    }
48
49
    /**
50
     * @psalm-param non-empty-string $table
51
     */
52 486
    public function hasTable(string $table): bool
53
    {
54 486
        $query = "SELECT COUNT(*) FROM [information_schema].[tables]
55
            WHERE [table_type] = 'BASE TABLE' AND [table_name] = ?";
56
57 486
        return (bool)$this->driver->query($query, [$table])->fetchColumn();
58
    }
59
60 4
    public function eraseTable(AbstractTable $table): void
61
    {
62 4
        $this->driver->execute(
63 4
            "TRUNCATE TABLE {$this->driver->identifier($table->getFullName())}"
0 ignored issues
show
Bug introduced by
The method identifier() does not exist on Cycle\Database\Driver\DriverInterface. It seems like you code against a sub-type of Cycle\Database\Driver\DriverInterface such as Cycle\Database\Driver\Driver. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

63
            "TRUNCATE TABLE {$this->driver->/** @scrutinizer ignore-call */ identifier($table->getFullName())}"
Loading history...
64
        );
65 2
    }
66
67
    /**
68
     * @psalm-param non-empty-string $table
69
     * @psalm-param non-empty-string $name
70
     */
71 2
    public function renameTable(string $table, string $name): void
72
    {
73 2
        $this->run(
74 2
            'sp_rename @objname = ?, @newname = ?',
75 2
            [$table, $name]
76
        );
77 2
    }
78
79 40
    public function createColumn(AbstractTable $table, AbstractColumn $column): void
80
    {
81 40
        $this->run(
82 40
            "ALTER TABLE {$this->identify($table)} ADD {$column->sqlStatement($this->driver)}"
83
        );
84 40
    }
85
86
    /**
87
     * Driver specific column alter command.
88
     *
89
     * @throws SchemaException
90
     */
91 38
    public function alterColumn(
92
        AbstractTable $table,
93
        AbstractColumn $initial,
94
        AbstractColumn $column
95
    ): void {
96 38
        if (!$initial instanceof SQLServerColumn || !$column instanceof SQLServerColumn) {
97
            throw new SchemaException('SQlServer handler can work only with SQLServer columns');
98
        }
99
100
        //In SQLServer we have to drop ALL related indexes and foreign keys while
101
        //applying type change... yeah...
102
103 38
        $indexesBackup = [];
104 38
        $foreignBackup = [];
105 38
        foreach ($table->getIndexes() as $index) {
106 6
            if (in_array($column->getName(), $index->getColumns(), true)) {
107 4
                $indexesBackup[] = $index;
108 4
                $this->dropIndex($table, $index);
109
            }
110
        }
111
112 38
        foreach ($table->getForeignKeys() as $foreign) {
113 2
            if ($column->getName() === $foreign->getColumns()) {
114
                $foreignBackup[] = $foreign;
115
                $this->dropForeignKey($table, $foreign);
116
            }
117
        }
118
119
        //Column will recreate needed constraints
120 38
        foreach ($column->getConstraints() as $constraint) {
121 18
            $this->dropConstrain($table, $constraint);
122
        }
123
124
        //Rename is separate operation
125 38
        if ($column->getName() !== $initial->getName()) {
126 18
            $this->renameColumn($table, $initial, $column);
127
128
            //This call is required to correctly built set of alter operations
129 18
            $initial->setName($column->getName());
130
        }
131
132 38
        foreach ($column->alterOperations($this->driver, $initial) as $operation) {
0 ignored issues
show
Bug introduced by
It seems like $this->driver can also be of type null; however, parameter $driver of Cycle\Database\Driver\SQ...lumn::alterOperations() does only seem to accept Cycle\Database\Driver\DriverInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

132
        foreach ($column->alterOperations(/** @scrutinizer ignore-type */ $this->driver, $initial) as $operation) {
Loading history...
133 26
            $this->run("ALTER TABLE {$this->identify($table)} {$operation}");
134
        }
135
136
        //Restoring indexes and foreign keys
137 38
        foreach ($indexesBackup as $index) {
138 4
            $this->createIndex($table, $index);
139
        }
140
141 38
        foreach ($foreignBackup as $foreign) {
142
            $this->createForeignKey($table, $foreign);
143
        }
144 38
    }
145
146 16
    public function dropIndex(AbstractTable $table, AbstractIndex $index): void
147
    {
148 16
        $this->run("DROP INDEX {$this->identify($index)} ON {$this->identify($table)}");
149 16
    }
150
151 18
    public function enableForeignKeyConstraints(): void
152
    {
153
        foreach ($this->getTableNames() as $table) {
154
            $this->run("ALTER TABLE {$this->identify($table)} WITH CHECK CHECK CONSTRAINT ALL");
155
        }
156 18
    }
157 18
158
    public function disableForeignKeyConstraints(): void
159 18
    {
160 18
        foreach ($this->getTableNames() as $table) {
161
            $this->run("ALTER TABLE {$this->identify($table)} NOCHECK CONSTRAINT ALL");
162
        }
163 18
    }
164
165
    private function renameColumn(
166
        AbstractTable $table,
167
        AbstractColumn $initial,
168
        AbstractColumn $column
169
    ): void {
170
        $this->run(
171
            "sp_rename ?, ?, 'COLUMN'",
172
            [
173
                $table->getFullName() . '.' . $initial->getName(),
174
                $column->getName(),
175
            ]
176
        );
177
    }
178
}
179