Test Failed
Pull Request — master (#242)
by Wilmer
15:25 queued 10:51
created

DDLQueryBuilder   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 282
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 37
eloc 99
c 3
b 0
f 0
dl 0
loc 282
ccs 116
cts 116
cp 1
rs 9.44

14 Methods

Rating   Name   Duplication   Size   Complexity  
B alterColumn() 0 39 8
A addCommentOnTable() 0 3 1
A dropCommentFromTable() 0 3 1
A dropCommentFromColumn() 0 3 1
A dropColumn() 0 7 1
A addCommentOnColumn() 0 3 1
A addDefaultValue() 0 8 2
A dropConstraintsForColumn() 0 20 2
A renameColumn() 0 6 1
A buildRemoveCommentSql() 0 28 6
A checkIntegrity() 0 20 5
A dropDefaultValue() 0 6 1
A renameTable() 0 5 1
A buildAddCommentSql() 0 33 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Mssql;
6
7
use Exception;
8
use Throwable;
9
use Yiisoft\Db\Exception\InvalidArgumentException;
10
use Yiisoft\Db\Exception\NotSupportedException;
11
use Yiisoft\Db\Expression\Expression;
12
use Yiisoft\Db\QueryBuilder\AbstractDDLQueryBuilder;
13
use Yiisoft\Db\Schema\Builder\ColumnInterface;
14
15
use function array_diff;
16
17
/**
18
 * Implements a (Data Definition Language) SQL statements for MSSQL Server.
19
 */
20
final class DDLQueryBuilder extends AbstractDDLQueryBuilder
21
{
22
    /**
23
     * @throws InvalidArgumentException
24
     */
25 879
    public function addCommentOnColumn(string $table, string $column, string $comment): string
26
    {
27
        return $this->buildAddCommentSql($comment, $table, $column);
28
    }
29
30 879
    /**
31
     * @throws InvalidArgumentException
32
     */
33
    public function addCommentOnTable(string $table, string $comment): string
34
    {
35
        return $this->buildAddCommentSql($comment, $table);
36 5
    }
37
38 5
    /**
39
     * @throws Exception
40
     */
41
    public function addDefaultValue(string $table, string $name, string $column, mixed $value): string
42
    {
43
        return 'ALTER TABLE '
44 4
            . $this->quoter->quoteTableName($table)
0 ignored issues
show
Bug introduced by
The property quoter is declared private in Yiisoft\Db\QueryBuilder\AbstractDDLQueryBuilder and cannot be accessed from this context.
Loading history...
45
            . ' ADD CONSTRAINT '
46 4
            . $this->quoter->quoteColumnName($name)
47
            . ' DEFAULT ' . ($value === null ? 'NULL' : (string) $this->quoter->quoteValue($value))
48
            . ' FOR ' . $this->quoter->quoteColumnName($column);
49
    }
50
51
    /**
52 11
     * @throws Exception
53
     */
54 11
    public function alterColumn(string $table, string $column, ColumnInterface|string $type): string
55 11
    {
56 11
        $sqlAfter = [$this->dropConstraintsForColumn($table, $column, 'D')];
57 11
58 11
        $columnName = $this->quoter->quoteColumnName($column);
0 ignored issues
show
Bug introduced by
The property quoter is declared private in Yiisoft\Db\QueryBuilder\AbstractDDLQueryBuilder and cannot be accessed from this context.
Loading history...
59 11
        $tableName = $this->quoter->quoteTableName($table);
60
        $constraintBase = preg_replace('/[^a-z0-9_]/i', '', $table . '_' . $column);
61
62
        if ($type instanceof ColumnInterface) {
63
            $type->setFormat('{type}{length}{notnull}{append}');
64
65 9
            /** @psalm-var mixed $defaultValue */
66
            $defaultValue = $type->getDefault();
67 9
            if ($defaultValue !== null || $type->isNotNull() === false) {
68
                $sqlAfter[] = $this->addDefaultValue(
69 9
                    $table,
70 9
                    "DF_{$constraintBase}",
71 9
                    $column,
72
                    $defaultValue instanceof Expression ? $defaultValue : new Expression((string)$defaultValue)
73 9
                );
74 8
            }
75
76
            $checkValue = $type->getCheck();
77 8
            if ($checkValue !== null) {
78 8
                $sqlAfter[] = "ALTER TABLE {$tableName} ADD CONSTRAINT " .
79 6
                    $this->quoter->quoteColumnName("CK_{$constraintBase}") .
80 6
                    ' CHECK (' . ($defaultValue instanceof Expression ? $checkValue : new Expression($checkValue)) . ')';
81 6
            }
82 6
83 6
            if ($type->isUnique()) {
84 6
                $sqlAfter[] = "ALTER TABLE {$tableName} ADD CONSTRAINT " . $this->quoter->quoteColumnName("UQ_{$constraintBase}") . " UNIQUE ({$columnName})";
85
            }
86
        }
87 8
88 8
        return 'ALTER TABLE ' . $tableName
89 4
            . ' ALTER COLUMN '
90 4
            . $columnName . ' '
91 4
            . $this->queryBuilder->getColumnType($type) . "\n"
0 ignored issues
show
Bug introduced by
The property queryBuilder is declared private in Yiisoft\Db\QueryBuilder\AbstractDDLQueryBuilder and cannot be accessed from this context.
Loading history...
92
            . implode("\n", $sqlAfter);
93
    }
94 8
95 3
    /**
96
     * @throws NotSupportedException
97
     * @throws Throwable
98
     * @throws \Yiisoft\Db\Exception\Exception
99 9
     */
100 9
    public function checkIntegrity(string $schema = '', string $table = '', bool $check = true): string
101 9
    {
102 9
        $enable = $check ? 'CHECK' : 'NOCHECK';
103 9
104
        /** @psalm-var Schema $schemaInstance */
105
        $schemaInstance = $this->schema;
0 ignored issues
show
Bug introduced by
The property schema is declared private in Yiisoft\Db\QueryBuilder\AbstractDDLQueryBuilder and cannot be accessed from this context.
Loading history...
106
        $defaultSchema = $schema ?: $schemaInstance->getDefaultSchema() ?? '';
107
        /** @psalm-var string[] $tableNames */
108
        $tableNames = $schemaInstance->getTableSchema($table)
109
             ? [$table] : $schemaInstance->getTableNames($defaultSchema);
110
        $viewNames = $schemaInstance->getViewNames($defaultSchema);
111 3
        $tableNames = array_diff($tableNames, $viewNames);
112
        $command = '';
113 3
114
        foreach ($tableNames as $tableName) {
115
            $tableName = $this->quoter->quoteTableName("$defaultSchema.$tableName");
0 ignored issues
show
Bug introduced by
The property quoter is declared private in Yiisoft\Db\QueryBuilder\AbstractDDLQueryBuilder and cannot be accessed from this context.
Loading history...
116 3
            $command .= "ALTER TABLE $tableName $enable CONSTRAINT ALL; ";
117 3
        }
118
119 3
        return $command;
120 3
    }
121 3
122 3
    /**
123 3
     * @throws InvalidArgumentException
124
     */
125 3
    public function dropCommentFromColumn(string $table, string $column): string
126 3
    {
127 3
        return $this->buildRemoveCommentSql($table, $column);
128
    }
129
130 3
    /**
131
     * @throws InvalidArgumentException
132
     */
133
    public function dropCommentFromTable(string $table): string
134
    {
135
        return $this->buildRemoveCommentSql($table);
136 3
    }
137
138 3
    public function dropDefaultValue(string $table, string $name): string
139
    {
140
        return 'ALTER TABLE '
141
            . $this->quoter->quoteTableName($table)
0 ignored issues
show
Bug introduced by
The property quoter is declared private in Yiisoft\Db\QueryBuilder\AbstractDDLQueryBuilder and cannot be accessed from this context.
Loading history...
142
            . ' DROP CONSTRAINT '
143
            . $this->quoter->quoteColumnName($name);
144 3
    }
145
146 3
    public function dropColumn(string $table, string $column): string
147
    {
148
        return $this->dropConstraintsForColumn($table, $column)
149 4
            . "\nALTER TABLE "
150
            . $this->quoter->quoteTableName($table)
0 ignored issues
show
Bug introduced by
The property quoter is declared private in Yiisoft\Db\QueryBuilder\AbstractDDLQueryBuilder and cannot be accessed from this context.
Loading history...
151 4
            . ' DROP COLUMN '
152 4
            . $this->quoter->quoteColumnName($column);
153 4
    }
154 4
155
    public function renameTable(string $oldName, string $newName): string
156
    {
157 3
        return 'sp_rename '
158
            . $this->quoter->quoteTableName($oldName) . ', '
0 ignored issues
show
Bug introduced by
The property quoter is declared private in Yiisoft\Db\QueryBuilder\AbstractDDLQueryBuilder and cannot be accessed from this context.
Loading history...
159 3
            . $this->quoter->quoteTableName($newName);
160 3
    }
161 3
162 3
    public function renameColumn(string $table, string $oldName, string $newName): string
163 3
    {
164
        return 'sp_rename '
165
            . "'" . $this->quoter->quoteTableName($table) . '.' . $this->quoter->quoteColumnName($oldName) . "'" . ', '
0 ignored issues
show
Bug introduced by
The property quoter is declared private in Yiisoft\Db\QueryBuilder\AbstractDDLQueryBuilder and cannot be accessed from this context.
Loading history...
166 3
            . $this->quoter->quoteColumnName($newName) . ', '
167
            . "'COLUMN'";
168 3
    }
169 3
170 3
    /**
171
     * Builds an SQL command for adding or updating a comment to a table or a column.
172
     *
173 2
     * The command built will check if a comment already exists. If so, it will be updated, otherwise, it will be added.
174
     *
175 2
     * @param string $comment The text of the comment to add.
176 2
     * @param string $table The table to comment or whose column is to comment.
177 2
     * @param string|null $column Optional, the name of the column to comment.
178 2
     * If empty, the command will add the comment to the table instead.
179
     *
180
     * @throws Exception
181
     * @throws InvalidArgumentException If the table doesn't exist.
182
     *
183
     * @return string The SQL statement for adding a comment.
184
     *
185
     * Note: The method will quote the `comment`, `table`, `column` parameter before using it in the generated SQL.
186
     */
187
    private function buildAddCommentSql(string $comment, string $table, string $column = null): string
188
    {
189
        $tableSchema = $this->schema->getTableSchema($table);
0 ignored issues
show
Bug introduced by
The property schema is declared private in Yiisoft\Db\QueryBuilder\AbstractDDLQueryBuilder and cannot be accessed from this context.
Loading history...
190
191
        if ($tableSchema === null) {
192
            throw new InvalidArgumentException("Table not found: $table");
193
        }
194
195
        $schemaName = $tableSchema->getSchemaName()
196
            ? "N'" . (string) $tableSchema->getSchemaName() . "'" : 'SCHEMA_NAME()';
197
        $tableName = 'N' . (string) $this->quoter->quoteValue($tableSchema->getName());
0 ignored issues
show
Bug introduced by
The property quoter is declared private in Yiisoft\Db\QueryBuilder\AbstractDDLQueryBuilder and cannot be accessed from this context.
Loading history...
198 9
        $columnName = $column ? 'N' . (string) $this->quoter->quoteValue($column) : null;
199
        $comment = 'N' . (string) $this->quoter->quoteValue($comment);
200 9
        $functionParams = "
201
            @name = N'MS_description',
202 9
            @value = $comment,
203 2
            @level0type = N'SCHEMA', @level0name = $schemaName,
204
            @level1type = N'TABLE', @level1name = $tableName"
205
            . ($column ? ", @level2type = N'COLUMN', @level2name = $columnName" : '') . ';';
206 7
207 7
        return "
208 7
            IF NOT EXISTS (
209 7
                    SELECT 1
210 7
                    FROM fn_listextendedproperty (
211 7
                        N'MS_description',
212
                        'SCHEMA', $schemaName,
213 7
                        'TABLE', $tableName,
214 7
                        " . ($column ? "'COLUMN', $columnName " : ' DEFAULT, DEFAULT ') . "
215 7
                    )
216 7
            )
217
                EXEC sys.sp_addextendedproperty $functionParams
218 7
            ELSE
219
                EXEC sys.sp_updateextendedproperty $functionParams
220
        ";
221
    }
222
223 7
    /**
224 7
     * Builds an SQL command for removing a comment from a table or a column. The command built will check if a comment
225 7
     * already exists before trying to perform the removal.
226
     *
227
     * @param string $table The table that will have the comment removed or whose column will have the comment removed.
228 7
     * @param string|null $column Optional, the name of the column whose comment will be removed. If empty, the command
229
     * will remove the comment from the table instead.
230 7
     *
231 7
     * @throws Exception
232
     * @throws InvalidArgumentException If the table doesn't exist.
233
     *
234
     * @return string The SQL statement for removing the comment.
235
     *
236
     * Note: The method will quote the `table`, `column` parameter before using it in the generated SQL.
237
     */
238
    private function buildRemoveCommentSql(string $table, string $column = null): string
239
    {
240
        $tableSchema = $this->schema->getTableSchema($table);
0 ignored issues
show
Bug introduced by
The property schema is declared private in Yiisoft\Db\QueryBuilder\AbstractDDLQueryBuilder and cannot be accessed from this context.
Loading history...
241
242
        if ($tableSchema === null) {
243
            throw new InvalidArgumentException("Table not found: $table");
244
        }
245
246
        $schemaName = $tableSchema->getSchemaName()
247
            ? "N'" . (string) $tableSchema->getSchemaName() . "'" : 'SCHEMA_NAME()';
248
        $tableName = 'N' . (string) $this->quoter->quoteValue($tableSchema->getName());
0 ignored issues
show
Bug introduced by
The property quoter is declared private in Yiisoft\Db\QueryBuilder\AbstractDDLQueryBuilder and cannot be accessed from this context.
Loading history...
249 6
        $columnName = $column ? 'N' . (string) $this->quoter->quoteValue($column) : null;
250
251 6
        return "
252
            IF EXISTS (
253 6
                    SELECT 1
254 2
                    FROM fn_listextendedproperty (
255
                        N'MS_description',
256
                        'SCHEMA', $schemaName,
257 4
                        'TABLE', $tableName,
258 4
                        " . ($column ? "'COLUMN', $columnName " : ' DEFAULT, DEFAULT ') . "
259 4
                    )
260 4
            )
261
                EXEC sys.sp_dropextendedproperty
262 4
                    @name = N'MS_description',
263
                    @level0type = N'SCHEMA', @level0name = $schemaName,
264
                    @level1type = N'TABLE', @level1name = $tableName"
265
                    . ($column ? ", @level2type = N'COLUMN', @level2name = $columnName" : '') . ';';
266
    }
267 4
268 4
    /**
269 4
     * Builds an SQL statement for dropping constraints for column of table.
270
     *
271
     * @param string $table The table whose constraint is to be dropped.
272
     * @param string $column the column whose constraint is to be dropped.
273
     * @param string $type type of constraint, leave empty for all types of constraints(for example: D - default,
274 4
     * 'UQ' - unique, 'C' - check)
275 4
     *
276 4
     * @return string the DROP CONSTRAINTS SQL
277
     *
278
     * @link https://docs.microsoft.com/sql/relational-databases/system-catalog-views/sys-objects-transact-sql
279
     *
280
     * Note: The method will quote the `table`, `column` parameter before using it in the generated SQL.
281
     */
282
    private function dropConstraintsForColumn(string $table, string $column, string $type = ''): string
283
    {
284
        return "DECLARE @tableName VARCHAR(MAX) = '" . $this->quoter->quoteTableName($table) . "'
0 ignored issues
show
Bug introduced by
The property quoter is declared private in Yiisoft\Db\QueryBuilder\AbstractDDLQueryBuilder and cannot be accessed from this context.
Loading history...
285
DECLARE @columnName VARCHAR(MAX) = '{$column}'
286
WHILE 1=1 BEGIN
287
    DECLARE @constraintName NVARCHAR(128)
288
    SET @constraintName = (SELECT TOP 1 OBJECT_NAME(cons.[object_id])
289
        FROM (
290
            SELECT sc.[constid] object_id
291
            FROM [sys].[sysconstraints] sc
292
            JOIN [sys].[columns] c ON c.[object_id]=sc.[id] AND c.[column_id]=sc.[colid] AND c.[name]=@columnName
293 11
            WHERE sc.[id] = OBJECT_ID(@tableName)
294
            UNION
295 11
            SELECT object_id(i.[name]) FROM [sys].[indexes] i
296 11
            JOIN [sys].[columns] c ON c.[object_id]=i.[object_id] AND c.[name]=@columnName
297
            JOIN [sys].[index_columns] ic ON ic.[object_id]=i.[object_id] AND i.[index_id]=ic.[index_id] AND c.[column_id]=ic.[column_id]
298
            WHERE i.[is_unique_constraint]=1 and i.[object_id]=OBJECT_ID(@tableName)
299
        ) cons
300
        JOIN [sys].[objects] so ON so.[object_id]=cons.[object_id]
301
        " . (!empty($type) ? " WHERE so.[type]='{$type}'" : '') . ")
302
    IF @constraintName IS NULL BREAK
303
    EXEC (N'ALTER TABLE ' + @tableName + ' DROP CONSTRAINT [' + @constraintName + ']')
304
END";
305
    }
306
}
307