Completed
Push — master ( ae0d5c...733570 )
by Edgaras
03:20 queued 18s
created

SqlServerAdapter::getDropTableInstructions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 7
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 7
loc 7
ccs 0
cts 7
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
/**
3
 * Phinx
4
 *
5
 * (The MIT license)
6
 * Copyright (c) 2015 Rob Morgan
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated * documentation files (the "Software"), to
10
 * deal in the Software without restriction, including without limitation the
11
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
 * sell copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
 * IN THE SOFTWARE.
25
 *
26
 * @package    Phinx
27
 * @subpackage Phinx\Db\Adapter
28
 */
29
namespace Phinx\Db\Adapter;
30
31
use Cake\Database\Connection;
32
use Cake\Database\Driver\Sqlserver as SqlServerDriver;
33
use Phinx\Db\Table\Column;
34
use Phinx\Db\Table\ForeignKey;
35
use Phinx\Db\Table\Index;
36
use Phinx\Db\Table\Table;
37
use Phinx\Db\Util\AlterInstructions;
38
use Phinx\Util\Literal;
39
40
/**
41
 * Phinx SqlServer Adapter.
42
 *
43
 * @author Rob Morgan <[email protected]>
44
 */
45
class SqlServerAdapter extends PdoAdapter implements AdapterInterface
46
{
47
    protected $schema = 'dbo';
48
49
    protected $signedColumnTypes = ['integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true];
50
51
    /**
52
     * {@inheritdoc}
53
     */
54
    public function connect()
55
    {
56
        if ($this->connection === null) {
57
            if (!class_exists('PDO') || !in_array('sqlsrv', \PDO::getAvailableDrivers(), true)) {
58
                // try our connection via freetds (Mac/Linux)
59
                $this->connectDblib();
60
61
                return;
62
            }
63
64
            $db = null;
65
            $options = $this->getOptions();
66
67
            // if port is specified use it, otherwise use the SqlServer default
68 View Code Duplication
            if (empty($options['port'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
69
                $dsn = 'sqlsrv:server=' . $options['host'] . ';database=' . $options['name'];
70
            } else {
71
                $dsn = 'sqlsrv:server=' . $options['host'] . ',' . $options['port'] . ';database=' . $options['name'];
72
            }
73
            $dsn .= ';MultipleActiveResultSets=false';
74
75
            $driverOptions = [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION];
76
77
            // charset support
78
            if (isset($options['charset'])) {
79
                $driverOptions[\PDO::SQLSRV_ATTR_ENCODING] = $options['charset'];
80
            }
81
82
            // support arbitrary \PDO::SQLSRV_ATTR_* driver options and pass them to PDO
83
            // http://php.net/manual/en/ref.pdo-sqlsrv.php#pdo-sqlsrv.constants
84 View Code Duplication
            foreach ($options as $key => $option) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
85
                if (strpos($key, 'sqlsrv_attr_') === 0) {
86
                    $driverOptions[constant('\PDO::' . strtoupper($key))] = $option;
87
                }
88
            }
89
90
            try {
91
                $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
92
            } catch (\PDOException $exception) {
93
                throw new \InvalidArgumentException(sprintf(
94
                    'There was a problem connecting to the database: %s',
95
                    $exception->getMessage()
96
                ));
97
            }
98
99
            $this->setConnection($db);
100
        }
101
    }
102
103
    /**
104
     * Connect to MSSQL using dblib/freetds.
105
     *
106
     * The "sqlsrv" driver is not available on Unix machines.
107
     *
108
     * @throws \InvalidArgumentException
109
     */
110
    protected function connectDblib()
111
    {
112
        if (!class_exists('PDO') || !in_array('dblib', \PDO::getAvailableDrivers(), true)) {
113
            // @codeCoverageIgnoreStart
114
            throw new \RuntimeException('You need to enable the PDO_Dblib extension for Phinx to run properly.');
115
            // @codeCoverageIgnoreEnd
116
        }
117
118
        $options = $this->getOptions();
119
120
        // if port is specified use it, otherwise use the SqlServer default
121 View Code Duplication
        if (empty($options['port'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
122
            $dsn = 'dblib:host=' . $options['host'] . ';dbname=' . $options['name'];
123
        } else {
124
            $dsn = 'dblib:host=' . $options['host'] . ':' . $options['port'] . ';dbname=' . $options['name'];
125
        }
126
127
        $driverOptions = [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION];
128
129
        try {
130
            $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
131
        } catch (\PDOException $exception) {
132
            throw new \InvalidArgumentException(sprintf(
133
                'There was a problem connecting to the database: %s',
134
                $exception->getMessage()
135
            ));
136
        }
137
138
        $this->setConnection($db);
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144
    public function disconnect()
145
    {
146
        $this->connection = null;
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152
    public function hasTransactions()
153
    {
154
        return true;
155
    }
156
157
    /**
158
     * {@inheritdoc}
159
     */
160
    public function beginTransaction()
161
    {
162
        $this->execute('BEGIN TRANSACTION');
163
    }
164
165
    /**
166
     * {@inheritdoc}
167
     */
168
    public function commitTransaction()
169
    {
170
        $this->execute('COMMIT TRANSACTION');
171
    }
172
173
    /**
174
     * {@inheritdoc}
175
     */
176
    public function rollbackTransaction()
177
    {
178
        $this->execute('ROLLBACK TRANSACTION');
179
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184
    public function quoteTableName($tableName)
185
    {
186
        return str_replace('.', '].[', $this->quoteColumnName($tableName));
187
    }
188
189
    /**
190
     * {@inheritdoc}
191
     */
192
    public function quoteColumnName($columnName)
193
    {
194
        return '[' . str_replace(']', '\]', $columnName) . ']';
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200
    public function hasTable($tableName)
201
    {
202
        if ($this->hasCreatedTable($tableName)) {
203
            return true;
204
        }
205
206
        $result = $this->fetchRow(sprintf('SELECT count(*) as [count] FROM information_schema.tables WHERE table_name = \'%s\';', $tableName));
207
208
        return $result['count'] > 0;
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214
    public function createTable(Table $table, array $columns = [], array $indexes = [])
215
    {
216
        $options = $table->getOptions();
217
218
        // Add the default primary key
219 View Code Duplication
        if (!isset($options['id']) || (isset($options['id']) && $options['id'] === true)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
220
            $options['id'] = 'id';
221
        }
222
223 View Code Duplication
        if (isset($options['id']) && is_string($options['id'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
224
            // Handle id => "field_name" to support AUTO_INCREMENT
225
            $column = new Column();
226
            $column->setName($options['id'])
227
                   ->setType('integer')
228
                   ->setIdentity(true);
229
230
            array_unshift($columns, $column);
231
            $options['primary_key'] = $options['id'];
232
        }
233
234
        $sql = 'CREATE TABLE ';
235
        $sql .= $this->quoteTableName($table->getName()) . ' (';
236
        $sqlBuffer = [];
237
        $columnsWithComments = [];
238 View Code Duplication
        foreach ($columns as $column) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
239
            $sqlBuffer[] = $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column);
240
241
            // set column comments, if needed
242
            if ($column->getComment()) {
243
                $columnsWithComments[] = $column;
244
            }
245
        }
246
247
        // set the primary key(s)
248
        if (isset($options['primary_key'])) {
249
            $pkSql = sprintf('CONSTRAINT PK_%s PRIMARY KEY (', $table->getName());
250
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
251
                $pkSql .= $this->quoteColumnName($options['primary_key']);
252
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
253
                $pkSql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
254
            }
255
            $pkSql .= ')';
256
            $sqlBuffer[] = $pkSql;
257
        }
258
259
        $sql .= implode(', ', $sqlBuffer);
260
        $sql .= ');';
261
262
        // process column comments
263
        foreach ($columnsWithComments as $column) {
264
            $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
265
        }
266
267
        // set the indexes
268
        foreach ($indexes as $index) {
269
            $sql .= $this->getIndexSqlDefinition($index, $table->getName());
270
        }
271
272
        // execute the sql
273
        $this->execute($sql);
274
275
        $this->addCreatedTable($table->getName());
276
    }
277
278
    /**
279
     * {@inheritdoc}
280
     */
281 View Code Duplication
    protected function getChangePrimaryKeyInstructions(Table $table, $newColumns)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
282
    {
283
        $instructions = new AlterInstructions();
284
285
        // Drop the existing primary key
286
        $primaryKey = $this->getPrimaryKey($table->getName());
287
        if (!empty($primaryKey['constraint'])) {
288
            $sql = sprintf(
289
                'DROP CONSTRAINT %s',
290
                $this->quoteColumnName($primaryKey['constraint'])
291
            );
292
            $instructions->addAlter($sql);
293
        }
294
295
        // Add the primary key(s)
296
        if (!empty($newColumns)) {
297
            $sql = sprintf(
298
                'ALTER TABLE %s ADD CONSTRAINT %s PRIMARY KEY (',
299
                $this->quoteTableName($table->getName()),
300
                $this->quoteColumnName('PK_' . $table->getName())
301
            );
302
            if (is_string($newColumns)) { // handle primary_key => 'id'
303
                $sql .= $this->quoteColumnName($newColumns);
304
            } elseif (is_array($newColumns)) { // handle primary_key => array('tag_id', 'resource_id')
305
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $newColumns));
306
            } else {
307
                throw new \InvalidArgumentException(sprintf(
308
                    "Invalid value for primary key: %s",
309
                    json_encode($newColumns)
310
                ));
311
            }
312
            $sql .= ')';
313
            $instructions->addPostStep($sql);
314
        }
315
316
        return $instructions;
317
    }
318
319
    /**
320
     * {@inheritdoc}
321
     */
322
    protected function getChangeCommentInstructions(Table $table, $newComment)
323
    {
324
        throw new \BadMethodCallException('SQLite does not have table comments');
325
    }
326
327
    /**
328
     * Gets the SqlServer Column Comment Defininition for a column object.
329
     *
330
     * @param \Phinx\Db\Table\Column $column    Column
331
     * @param string $tableName Table name
332
     *
333
     * @return string
334
     */
335
    protected function getColumnCommentSqlDefinition(Column $column, $tableName)
336
    {
337
        // passing 'null' is to remove column comment
338
        $currentComment = $this->getColumnComment($tableName, $column->getName());
339
340
        $comment = (strcasecmp($column->getComment(), 'NULL') !== 0) ? $this->getConnection()->quote($column->getComment()) : '\'\'';
341
        $command = $currentComment === false ? 'sp_addextendedproperty' : 'sp_updateextendedproperty';
342
343
        return sprintf(
344
            "EXECUTE %s N'MS_Description', N%s, N'SCHEMA', N'%s', N'TABLE', N'%s', N'COLUMN', N'%s';",
345
            $command,
346
            $comment,
347
            $this->schema,
348
            $tableName,
349
            $column->getName()
350
        );
351
    }
352
353
    /**
354
     * {@inheritdoc}
355
     */
356 View Code Duplication
    protected function getRenameTableInstructions($tableName, $newTableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
357
    {
358
        $this->updateCreatedTableName($tableName, $newTableName);
359
        $sql = sprintf(
360
            'EXEC sp_rename \'%s\', \'%s\'',
361
            $tableName,
362
            $newTableName
363
        );
364
365
        return new AlterInstructions([], [$sql]);
366
    }
367
368
    /**
369
     * {@inheritdoc}
370
     */
371 View Code Duplication
    protected function getDropTableInstructions($tableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
372
    {
373
        $this->removeCreatedTable($tableName);
374
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
375
376
        return new AlterInstructions([], [$sql]);
377
    }
378
379
    /**
380
     * {@inheritdoc}
381
     */
382
    public function truncateTable($tableName)
383
    {
384
        $sql = sprintf(
385
            'TRUNCATE TABLE %s',
386
            $this->quoteTableName($tableName)
387
        );
388
389
        $this->execute($sql);
390
    }
391
392
    public function getColumnComment($tableName, $columnName)
393
    {
394
        $sql = sprintf("SELECT cast(extended_properties.[value] as nvarchar(4000)) comment
395
  FROM sys.schemas
396
 INNER JOIN sys.tables
397
    ON schemas.schema_id = tables.schema_id
398
 INNER JOIN sys.columns
399
    ON tables.object_id = columns.object_id
400
 INNER JOIN sys.extended_properties
401
    ON tables.object_id = extended_properties.major_id
402
   AND columns.column_id = extended_properties.minor_id
403
   AND extended_properties.name = 'MS_Description'
404
   WHERE schemas.[name] = '%s' AND tables.[name] = '%s' AND columns.[name] = '%s'", $this->schema, $tableName, $columnName);
405
        $row = $this->fetchRow($sql);
406
407
        if ($row) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $row of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
408
            return trim($row['comment']);
409
        }
410
411
        return false;
412
    }
413
414
    /**
415
     * {@inheritdoc}
416
     */
417
    public function getColumns($tableName)
418
    {
419
        $columns = [];
420
        $sql = sprintf(
421
            "SELECT DISTINCT TABLE_SCHEMA AS [schema], TABLE_NAME as [table_name], COLUMN_NAME AS [name], DATA_TYPE AS [type],
422
            IS_NULLABLE AS [null], COLUMN_DEFAULT AS [default],
423
            CHARACTER_MAXIMUM_LENGTH AS [char_length],
424
            NUMERIC_PRECISION AS [precision],
425
            NUMERIC_SCALE AS [scale], ORDINAL_POSITION AS [ordinal_position],
426
            COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as [identity]
427
        FROM INFORMATION_SCHEMA.COLUMNS
428
        WHERE TABLE_NAME = '%s'
429
        ORDER BY ordinal_position",
430
            $tableName
431
        );
432
        $rows = $this->fetchAll($sql);
433
        foreach ($rows as $columnInfo) {
434
            try {
435
                $type = $this->getPhinxType($columnInfo['type']);
436
            } catch (UnsupportedColumnTypeException $e) {
437
                $type = Literal::from($columnInfo['type']);
438
            }
439
440
            $column = new Column();
441
            $column->setName($columnInfo['name'])
442
                   ->setType($type)
443
                   ->setNull($columnInfo['null'] !== 'NO')
444
                   ->setDefault($this->parseDefault($columnInfo['default']))
445
                   ->setIdentity($columnInfo['identity'] === '1')
446
                   ->setComment($this->getColumnComment($columnInfo['table_name'], $columnInfo['name']));
0 ignored issues
show
Security Bug introduced by
It seems like $this->getColumnComment(...], $columnInfo['name']) targeting Phinx\Db\Adapter\SqlServ...ter::getColumnComment() can also be of type false; however, Phinx\Db\Table\Column::setComment() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
447
448
            if (!empty($columnInfo['char_length'])) {
449
                $column->setLimit($columnInfo['char_length']);
450
            }
451
452
            $columns[$columnInfo['name']] = $column;
453
        }
454
455
        return $columns;
456
    }
457
458
    protected function parseDefault($default)
459
    {
460
        $default = preg_replace(["/\('(.*)'\)/", "/\(\((.*)\)\)/", "/\((.*)\)/"], '$1', $default);
461
462
        if (strtoupper($default) === 'NULL') {
463
            $default = null;
464
        } elseif (is_numeric($default)) {
465
            $default = (int)$default;
466
        }
467
468
        return $default;
469
    }
470
471
    /**
472
     * {@inheritdoc}
473
     */
474
    public function hasColumn($tableName, $columnName)
475
    {
476
        $sql = sprintf(
477
            "SELECT count(*) as [count]
478
             FROM information_schema.columns
479
             WHERE table_name = '%s' AND column_name = '%s'",
480
            $tableName,
481
            $columnName
482
        );
483
        $result = $this->fetchRow($sql);
484
485
        return $result['count'] > 0;
486
    }
487
488
    /**
489
     * {@inheritdoc}
490
     */
491 View Code Duplication
    protected function getAddColumnInstructions(Table $table, Column $column)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
492
    {
493
        $alter = sprintf(
494
            'ALTER TABLE %s ADD %s %s',
495
            $table->getName(),
496
            $this->quoteColumnName($column->getName()),
497
            $this->getColumnSqlDefinition($column)
498
        );
499
500
        return new AlterInstructions([], [$alter]);
501
    }
502
503
    /**
504
     * {@inheritdoc}
505
     */
506
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
507
    {
508
        if (!$this->hasColumn($tableName, $columnName)) {
509
            throw new \InvalidArgumentException("The specified column does not exist: $columnName");
510
        }
511
512
        $instructions = new AlterInstructions();
513
514
        $oldConstraintName = "DF_{$tableName}_{$columnName}";
515
        $newConstraintName = "DF_{$tableName}_{$newColumnName}";
516
        $sql = <<<SQL
517
IF (OBJECT_ID('$oldConstraintName', 'D') IS NOT NULL)
518
BEGIN
519
     EXECUTE sp_rename N'%s', N'%s', N'OBJECT'
520
END
521
SQL;
522
        $instructions->addPostStep(sprintf(
523
            $sql,
524
            $oldConstraintName,
525
            $newConstraintName
526
        ));
527
528
        $instructions->addPostStep(sprintf(
529
            "EXECUTE sp_rename N'%s.%s', N'%s', 'COLUMN' ",
530
            $tableName,
531
            $columnName,
532
            $newColumnName
533
        ));
534
535
        return $instructions;
536
    }
537
538
    /**
539
     * Returns the instructions to change a column default value
540
     *
541
     * @param string $tableName The table where the column is
542
     * @param Column $newColumn The column to alter
543
     * @return AlterInstructions
544
     */
545
    protected function getChangeDefault($tableName, Column $newColumn)
546
    {
547
        $constraintName = "DF_{$tableName}_{$newColumn->getName()}";
548
        $default = $newColumn->getDefault();
549
        $instructions = new AlterInstructions();
550
551
        if ($default === null) {
552
            $default = 'DEFAULT NULL';
553
        } else {
554
            $default = ltrim($this->getDefaultValueDefinition($default));
555
        }
556
557
        if (empty($default)) {
558
            return $instructions;
559
        }
560
561
        $instructions->addPostStep(sprintf(
562
            'ALTER TABLE %s ADD CONSTRAINT %s %s FOR %s',
563
            $this->quoteTableName($tableName),
564
            $constraintName,
565
            $default,
566
            $this->quoteColumnName($newColumn->getName())
567
        ));
568
569
        return $instructions;
570
    }
571
572
    /**
573
     * {@inheritdoc}
574
     */
575
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
576
    {
577
        $columns = $this->getColumns($tableName);
578
        $changeDefault =
579
            $newColumn->getDefault() !== $columns[$columnName]->getDefault() ||
580
            $newColumn->getType() !== $columns[$columnName]->getType();
581
582
        $instructions = new AlterInstructions();
583
584
        if ($columnName !== $newColumn->getName()) {
585
            $instructions->merge(
586
                $this->getRenameColumnInstructions($tableName, $columnName, $newColumn->getName())
587
            );
588
        }
589
590
        if ($changeDefault) {
591
            $instructions->merge($this->getDropDefaultConstraint($tableName, $newColumn->getName()));
592
        }
593
594
        $instructions->addPostStep(sprintf(
595
            'ALTER TABLE %s ALTER COLUMN %s %s',
596
            $this->quoteTableName($tableName),
597
            $this->quoteColumnName($newColumn->getName()),
598
            $this->getColumnSqlDefinition($newColumn, false)
599
        ));
600
        // change column comment if needed
601
        if ($newColumn->getComment()) {
602
            $instructions->addPostStep($this->getColumnCommentSqlDefinition($newColumn, $tableName));
603
        }
604
605
        if ($changeDefault) {
606
            $instructions->merge($this->getChangeDefault($tableName, $newColumn));
607
        }
608
609
        return $instructions;
610
    }
611
612
    /**
613
     * {@inheritdoc}
614
     */
615
    protected function getDropColumnInstructions($tableName, $columnName)
616
    {
617
        $instructions = $this->getDropDefaultConstraint($tableName, $columnName);
618
619
        $instructions->addPostStep(sprintf(
620
            'ALTER TABLE %s DROP COLUMN %s',
621
            $this->quoteTableName($tableName),
622
            $this->quoteColumnName($columnName)
623
        ));
624
625
        return $instructions;
626
    }
627
628
    /**
629
     * {@inheritdoc}
630
     */
631
    protected function getDropDefaultConstraint($tableName, $columnName)
632
    {
633
        $defaultConstraint = $this->getDefaultConstraint($tableName, $columnName);
634
635
        if (!$defaultConstraint) {
636
            return new AlterInstructions();
637
        }
638
639
        return $this->getDropForeignKeyInstructions($tableName, $defaultConstraint);
640
    }
641
642
    protected function getDefaultConstraint($tableName, $columnName)
643
    {
644
        $sql = "SELECT
645
    default_constraints.name
646
FROM
647
    sys.all_columns
648
649
        INNER JOIN
650
    sys.tables
651
        ON all_columns.object_id = tables.object_id
652
653
        INNER JOIN
654
    sys.schemas
655
        ON tables.schema_id = schemas.schema_id
656
657
        INNER JOIN
658
    sys.default_constraints
659
        ON all_columns.default_object_id = default_constraints.object_id
660
661
WHERE
662
        schemas.name = 'dbo'
663
    AND tables.name = '{$tableName}'
664
    AND all_columns.name = '{$columnName}'";
665
666
        $rows = $this->fetchAll($sql);
667
668
        return empty($rows) ? false : $rows[0]['name'];
669
    }
670
671
    protected function getIndexColums($tableId, $indexId)
672
    {
673
        $sql = "SELECT AC.[name] AS [column_name]
674
FROM sys.[index_columns] IC
675
  INNER JOIN sys.[all_columns] AC ON IC.[column_id] = AC.[column_id]
676
WHERE AC.[object_id] = {$tableId} AND IC.[index_id] = {$indexId}  AND IC.[object_id] = {$tableId}
677
ORDER BY IC.[key_ordinal];";
678
679
        $rows = $this->fetchAll($sql);
680
        $columns = [];
681
        foreach ($rows as $row) {
682
            $columns[] = strtolower($row['column_name']);
683
        }
684
685
        return $columns;
686
    }
687
688
    /**
689
     * Get an array of indexes from a particular table.
690
     *
691
     * @param string $tableName Table Name
692
     * @return array
693
     */
694
    public function getIndexes($tableName)
695
    {
696
        $indexes = [];
697
        $sql = "SELECT I.[name] AS [index_name], I.[index_id] as [index_id], T.[object_id] as [table_id]
698
FROM sys.[tables] AS T
699
  INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]
700
WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP'  AND T.[name] = '{$tableName}'
701
ORDER BY T.[name], I.[index_id];";
702
703
        $rows = $this->fetchAll($sql);
704
        foreach ($rows as $row) {
705
            $columns = $this->getIndexColums($row['table_id'], $row['index_id']);
706
            $indexes[$row['index_name']] = ['columns' => $columns];
707
        }
708
709
        return $indexes;
710
    }
711
712
    /**
713
     * {@inheritdoc}
714
     */
715 View Code Duplication
    public function hasIndex($tableName, $columns)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
716
    {
717
        if (is_string($columns)) {
718
            $columns = [$columns]; // str to array
719
        }
720
721
        $columns = array_map('strtolower', $columns);
722
        $indexes = $this->getIndexes($tableName);
723
724
        foreach ($indexes as $index) {
725
            $a = array_diff($columns, $index['columns']);
726
727
            if (empty($a)) {
728
                return true;
729
            }
730
        }
731
732
        return false;
733
    }
734
735
    /**
736
     * {@inheritdoc}
737
     */
738 View Code Duplication
    public function hasIndexByName($tableName, $indexName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
739
    {
740
        $indexes = $this->getIndexes($tableName);
741
742
        foreach ($indexes as $name => $index) {
743
            if ($name === $indexName) {
744
                 return true;
745
            }
746
        }
747
748
        return false;
749
    }
750
751
    /**
752
     * {@inheritdoc}
753
     */
754
    protected function getAddIndexInstructions(Table $table, Index $index)
755
    {
756
        $sql = $this->getIndexSqlDefinition($index, $table->getName());
757
758
        return new AlterInstructions([], [$sql]);
759
    }
760
761
    /**
762
     * {@inheritdoc}
763
     */
764
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
765
    {
766
        if (is_string($columns)) {
767
            $columns = [$columns]; // str to array
768
        }
769
770
        $indexes = $this->getIndexes($tableName);
771
        $columns = array_map('strtolower', $columns);
772
        $instructions = new AlterInstructions();
773
774
        foreach ($indexes as $indexName => $index) {
775
            $a = array_diff($columns, $index['columns']);
776 View Code Duplication
            if (empty($a)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
777
                $instructions->addPostStep(sprintf(
778
                    'DROP INDEX %s ON %s',
779
                    $this->quoteColumnName($indexName),
780
                    $this->quoteTableName($tableName)
781
                ));
782
783
                return $instructions;
784
            }
785
        }
786
787
        throw new \InvalidArgumentException(sprintf(
788
            "The specified index on columns '%s' does not exist",
789
            implode(',', $columns)
790
        ));
791
    }
792
793
    /**
794
     * {@inheritdoc}
795
     */
796
    protected function getDropIndexByNameInstructions($tableName, $indexName)
797
    {
798
        $indexes = $this->getIndexes($tableName);
799
        $instructions = new AlterInstructions();
800
801
        foreach ($indexes as $name => $index) {
802 View Code Duplication
            if ($name === $indexName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
803
                $instructions->addPostStep(sprintf(
804
                    'DROP INDEX %s ON %s',
805
                    $this->quoteColumnName($indexName),
806
                    $this->quoteTableName($tableName)
807
                ));
808
809
                return $instructions;
810
            }
811
        }
812
813
        throw new \InvalidArgumentException(sprintf(
814
            "The specified index name '%s' does not exist",
815
            $indexName
816
        ));
817
    }
818
819
    /**
820
     * {@inheritdoc}
821
     */
822 View Code Duplication
    public function hasPrimaryKey($tableName, $columns, $constraint = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
823
    {
824
        $primaryKey = $this->getPrimaryKey($tableName);
825
826
        if (empty($primaryKey)) {
827
            return false;
828
        }
829
830
        if ($constraint) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $constraint of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
831
            return ($primaryKey['constraint'] === $constraint);
832
        } else {
833
            if (is_string($columns)) {
834
                $columns = [$columns]; // str to array
835
            }
836
            $missingColumns = array_diff($columns, $primaryKey['columns']);
837
838
            return empty($missingColumns);
839
        }
840
    }
841
842
    /**
843
     * Get the primary key from a particular table.
844
     *
845
     * @param string $tableName Table Name
846
     * @return array
847
     */
848 View Code Duplication
    public function getPrimaryKey($tableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
849
    {
850
        $rows = $this->fetchAll(sprintf(
851
            "SELECT
852
                    tc.constraint_name,
853
                    kcu.column_name
854
                FROM information_schema.table_constraints AS tc
855
                JOIN information_schema.key_column_usage AS kcu
856
                    ON tc.constraint_name = kcu.constraint_name
857
                WHERE constraint_type = 'PRIMARY KEY'
858
                    AND tc.table_name = '%s'
859
                ORDER BY kcu.ordinal_position",
860
            $tableName
861
        ));
862
863
        $primaryKey = [
864
            'columns' => [],
865
        ];
866
        foreach ($rows as $row) {
867
            $primaryKey['constraint'] = $row['constraint_name'];
868
            $primaryKey['columns'][] = $row['column_name'];
869
        }
870
871
        return $primaryKey;
872
    }
873
874
    /**
875
     * {@inheritdoc}
876
     */
877 View Code Duplication
    public function hasForeignKey($tableName, $columns, $constraint = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
878
    {
879
        if (is_string($columns)) {
880
            $columns = [$columns]; // str to array
881
        }
882
        $foreignKeys = $this->getForeignKeys($tableName);
883
        if ($constraint) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $constraint of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
884
            if (isset($foreignKeys[$constraint])) {
885
                return !empty($foreignKeys[$constraint]);
886
            }
887
888
            return false;
889
        } else {
890
            foreach ($foreignKeys as $key) {
891
                $a = array_diff($columns, $key['columns']);
892
                if (empty($a)) {
893
                    return true;
894
                }
895
            }
896
897
            return false;
898
        }
899
    }
900
901
    /**
902
     * Get an array of foreign keys from a particular table.
903
     *
904
     * @param string $tableName Table Name
905
     * @return array
906
     */
907 View Code Duplication
    protected function getForeignKeys($tableName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
908
    {
909
        $foreignKeys = [];
910
        $rows = $this->fetchAll(sprintf(
911
            "SELECT
912
                    tc.constraint_name,
913
                    tc.table_name, kcu.column_name,
914
                    ccu.table_name AS referenced_table_name,
915
                    ccu.column_name AS referenced_column_name
916
                FROM
917
                    information_schema.table_constraints AS tc
918
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
919
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
920
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s'
921
                ORDER BY kcu.ordinal_position",
922
            $tableName
923
        ));
924
        foreach ($rows as $row) {
925
            $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
926
            $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
927
            $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
928
            $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
929
        }
930
931
        return $foreignKeys;
932
    }
933
934
    /**
935
     * {@inheritdoc}
936
     */
937
    protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreignKey)
938
    {
939
        $instructions = new AlterInstructions();
940
        $instructions->addPostStep(sprintf(
941
            'ALTER TABLE %s ADD %s',
942
            $this->quoteTableName($table->getName()),
943
            $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
944
        ));
945
946
        return $instructions;
947
    }
948
949
    /**
950
     * {@inheritdoc}
951
     */
952
    protected function getDropForeignKeyInstructions($tableName, $constraint)
953
    {
954
        $instructions = new AlterInstructions();
955
        $instructions->addPostStep(sprintf(
956
            'ALTER TABLE %s DROP CONSTRAINT %s',
957
            $this->quoteTableName($tableName),
958
            $constraint
959
        ));
960
961
        return $instructions;
962
    }
963
964
    /**
965
     * {@inheritdoc}
966
     */
967
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
968
    {
969
        $instructions = new AlterInstructions();
970
971 View Code Duplication
        foreach ($columns as $column) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
972
            $rows = $this->fetchAll(sprintf(
973
                "SELECT
974
                tc.constraint_name,
975
                tc.table_name, kcu.column_name,
976
                ccu.table_name AS referenced_table_name,
977
                ccu.column_name AS referenced_column_name
978
            FROM
979
                information_schema.table_constraints AS tc
980
                JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
981
                JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
982
            WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' and ccu.column_name='%s'
983
            ORDER BY kcu.ordinal_position",
984
                $tableName,
985
                $column
986
            ));
987
            foreach ($rows as $row) {
988
                $instructions->merge(
989
                    $this->getDropForeignKeyInstructions($tableName, $row['constraint_name'])
990
                );
991
            }
992
        }
993
994
        return $instructions;
995
    }
996
997
    /**
998
     * {@inheritdoc}
999
     */
1000
    public function getSqlType($type, $limit = null)
1001
    {
1002
        switch ($type) {
1003
            case static::PHINX_TYPE_FLOAT:
1004
            case static::PHINX_TYPE_DECIMAL:
1005
            case static::PHINX_TYPE_DATETIME:
1006
            case static::PHINX_TYPE_TIME:
1007
            case static::PHINX_TYPE_DATE:
1008
                return ['name' => $type];
1009
            case static::PHINX_TYPE_STRING:
1010
                return ['name' => 'nvarchar', 'limit' => 255];
1011
            case static::PHINX_TYPE_CHAR:
1012
                return ['name' => 'nchar', 'limit' => 255];
1013
            case static::PHINX_TYPE_TEXT:
1014
                return ['name' => 'ntext'];
1015
            case static::PHINX_TYPE_INTEGER:
1016
                return ['name' => 'int'];
1017
            case static::PHINX_TYPE_SMALL_INTEGER:
1018
                return ['name' => 'smallint'];
1019
            case static::PHINX_TYPE_BIG_INTEGER:
1020
                return ['name' => 'bigint'];
1021
            case static::PHINX_TYPE_TIMESTAMP:
1022
                return ['name' => 'datetime'];
1023
            case static::PHINX_TYPE_BLOB:
1024
            case static::PHINX_TYPE_BINARY:
1025
                return ['name' => 'varbinary'];
1026
            case static::PHINX_TYPE_BOOLEAN:
1027
                return ['name' => 'bit'];
1028
            case static::PHINX_TYPE_UUID:
1029
                return ['name' => 'uniqueidentifier'];
1030
            case static::PHINX_TYPE_FILESTREAM:
1031
                return ['name' => 'varbinary', 'limit' => 'max'];
1032
            // Geospatial database types
1033
            case static::PHINX_TYPE_GEOMETRY:
1034
            case static::PHINX_TYPE_POINT:
1035
            case static::PHINX_TYPE_LINESTRING:
1036
            case static::PHINX_TYPE_POLYGON:
1037
                // SQL Server stores all spatial data using a single data type.
1038
                // Specific types (point, polygon, etc) are set at insert time.
1039
                return ['name' => 'geography'];
1040
            default:
1041
                throw new UnsupportedColumnTypeException('Column type "' . $type . '" is not supported by SqlServer.');
1042
        }
1043
    }
1044
1045
    /**
1046
     * Returns Phinx type by SQL type
1047
     *
1048
     * @param string $sqlType SQL Type definition
1049
     * @throws UnsupportedColumnTypeException
1050
     * @internal param string $sqlType SQL type
1051
     * @return string Phinx type
1052
     */
1053
    public function getPhinxType($sqlType)
1054
    {
1055
        switch ($sqlType) {
1056
            case 'nvarchar':
1057
            case 'varchar':
1058
                return static::PHINX_TYPE_STRING;
1059
            case 'char':
1060
            case 'nchar':
1061
                return static::PHINX_TYPE_CHAR;
1062
            case 'text':
1063
            case 'ntext':
1064
                return static::PHINX_TYPE_TEXT;
1065
            case 'int':
1066
            case 'integer':
1067
                return static::PHINX_TYPE_INTEGER;
1068
            case 'decimal':
1069
            case 'numeric':
1070
            case 'money':
1071
                return static::PHINX_TYPE_DECIMAL;
1072
            case 'smallint':
1073
                return static::PHINX_TYPE_SMALL_INTEGER;
1074
            case 'bigint':
1075
                return static::PHINX_TYPE_BIG_INTEGER;
1076
            case 'real':
1077
            case 'float':
1078
                return static::PHINX_TYPE_FLOAT;
1079
            case 'binary':
1080
            case 'image':
1081
            case 'varbinary':
1082
                return static::PHINX_TYPE_BINARY;
1083
            case 'time':
1084
                return static::PHINX_TYPE_TIME;
1085
            case 'date':
1086
                return static::PHINX_TYPE_DATE;
1087
            case 'datetime':
1088
            case 'timestamp':
1089
                return static::PHINX_TYPE_DATETIME;
1090
            case 'bit':
1091
                return static::PHINX_TYPE_BOOLEAN;
1092
            case 'uniqueidentifier':
1093
                return static::PHINX_TYPE_UUID;
1094
            case 'filestream':
1095
                return static::PHINX_TYPE_FILESTREAM;
1096
            default:
1097
                throw new UnsupportedColumnTypeException('Column type "' . $sqlType . '" is not supported by SqlServer.');
1098
        }
1099
    }
1100
1101
    /**
1102
     * {@inheritdoc}
1103
     */
1104
    public function createDatabase($name, $options = [])
1105
    {
1106 View Code Duplication
        if (isset($options['collation'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1107
            $this->execute(sprintf('CREATE DATABASE [%s] COLLATE [%s]', $name, $options['collation']));
1108
        } else {
1109
            $this->execute(sprintf('CREATE DATABASE [%s]', $name));
1110
        }
1111
        $this->execute(sprintf('USE [%s]', $name));
1112
    }
1113
1114
    /**
1115
     * {@inheritdoc}
1116
     */
1117
    public function hasDatabase($name)
1118
    {
1119
        $result = $this->fetchRow(
1120
            sprintf(
1121
                'SELECT count(*) as [count] FROM master.dbo.sysdatabases WHERE [name] = \'%s\'',
1122
                $name
1123
            )
1124
        );
1125
1126
        return $result['count'] > 0;
1127
    }
1128
1129
    /**
1130
     * {@inheritdoc}
1131
     */
1132
    public function dropDatabase($name)
1133
    {
1134
        $sql = <<<SQL
1135
USE master;
1136
IF EXISTS(select * from sys.databases where name=N'$name')
1137
ALTER DATABASE [$name] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
1138
DROP DATABASE [$name];
1139
SQL;
1140
        $this->execute($sql);
1141
    }
1142
1143
    /**
1144
     * Gets the SqlServer Column Definition for a Column object.
1145
     *
1146
     * @param \Phinx\Db\Table\Column $column Column
1147
     * @return string
1148
     */
1149
    protected function getColumnSqlDefinition(Column $column, $create = true)
1150
    {
1151
        $buffer = [];
1152
        if ($column->getType() instanceof Literal) {
1153
            $buffer[] = (string)$column->getType();
1154
        } else {
1155
            $sqlType = $this->getSqlType($column->getType());
1156
            $buffer[] = strtoupper($sqlType['name']);
1157
            // integers cant have limits in SQlServer
1158
            $noLimits = [
1159
                'bigint',
1160
                'int',
1161
                'tinyint'
1162
            ];
1163
            if (static::PHINX_TYPE_DECIMAL === $sqlType['name'] && $column->getPrecision() && $column->getScale()) {
1164
                $buffer[] = sprintf(
1165
                    '(%s, %s)',
1166
                    $column->getPrecision() ?: $sqlType['precision'],
1167
                    $column->getScale() ?: $sqlType['scale']
1168
                );
1169
            } elseif (!in_array($sqlType['name'], $noLimits) && ($column->getLimit() || isset($sqlType['limit']))) {
1170
                $buffer[] = sprintf('(%s)', $column->getLimit() ? $column->getLimit() : $sqlType['limit']);
1171
            }
1172
        }
1173
1174
        $properties = $column->getProperties();
1175
        $buffer[] = $column->getType() === 'filestream' ? 'FILESTREAM' : '';
1176
        $buffer[] = isset($properties['rowguidcol']) ? 'ROWGUIDCOL' : '';
1177
1178
        $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';
1179
1180
        if ($create === true) {
1181
            if ($column->getDefault() === null && $column->isNull()) {
1182
                $buffer[] = ' DEFAULT NULL';
1183
            } else {
1184
                $buffer[] = $this->getDefaultValueDefinition($column->getDefault());
1185
            }
1186
        }
1187
1188
        if ($column->isIdentity()) {
1189
            $seed = $column->getSeed() ?: 1;
1190
            $increment = $column->getIncrement() ?: 1;
1191
            $buffer[] = sprintf('IDENTITY(%d,%d)', $seed, $increment);
1192
        }
1193
1194
        return implode(' ', $buffer);
1195
    }
1196
1197
    /**
1198
     * Gets the SqlServer Index Definition for an Index object.
1199
     *
1200
     * @param \Phinx\Db\Table\Index $index Index
1201
     * @return string
1202
     */
1203
    protected function getIndexSqlDefinition(Index $index, $tableName)
1204
    {
1205 View Code Duplication
        if (is_string($index->getName())) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1206
            $indexName = $index->getName();
1207
        } else {
1208
            $columnNames = $index->getColumns();
1209
            $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames));
1210
        }
1211
        $def = sprintf(
1212
            "CREATE %s INDEX %s ON %s (%s);",
1213
            ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
1214
            $indexName,
1215
            $this->quoteTableName($tableName),
1216
            '[' . implode('],[', $index->getColumns()) . ']'
1217
        );
1218
1219
        return $def;
1220
    }
1221
1222
    /**
1223
     * Gets the SqlServer Foreign Key Definition for an ForeignKey object.
1224
     *
1225
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1226
     * @return string
1227
     */
1228
    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName)
1229
    {
1230
        $constraintName = $foreignKey->getConstraint() ?: $tableName . '_' . implode('_', $foreignKey->getColumns());
1231
        $def = ' CONSTRAINT ' . $this->quoteColumnName($constraintName);
0 ignored issues
show
Bug introduced by
It seems like $constraintName defined by $foreignKey->getConstrai...reignKey->getColumns()) on line 1230 can also be of type boolean; however, Phinx\Db\Adapter\SqlServ...pter::quoteColumnName() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1232
        $def .= ' FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")';
1233
        $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")';
1234
        if ($foreignKey->getOnDelete()) {
1235
            $def .= " ON DELETE {$foreignKey->getOnDelete()}";
1236
        }
1237
        if ($foreignKey->getOnUpdate()) {
1238
            $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
1239
        }
1240
1241
        return $def;
1242
    }
1243
1244
    /**
1245
     * {@inheritdoc}
1246
     */
1247
    public function getColumnTypes()
1248
    {
1249
        return array_merge(parent::getColumnTypes(), ['filestream']);
1250
    }
1251
1252
    /**
1253
     * Records a migration being run.
1254
     *
1255
     * @param \Phinx\Migration\MigrationInterface $migration Migration
1256
     * @param string $direction Direction
1257
     * @param int $startTime Start Time
1258
     * @param int $endTime End Time
1259
     * @return \Phinx\Db\Adapter\AdapterInterface
1260
     */
1261
    public function migrated(\Phinx\Migration\MigrationInterface $migration, $direction, $startTime, $endTime)
1262
    {
1263
        $startTime = str_replace(' ', 'T', $startTime);
1264
        $endTime = str_replace(' ', 'T', $endTime);
1265
1266
        return parent::migrated($migration, $direction, $startTime, $endTime);
1267
    }
1268
1269
    /**
1270
     * {@inheritDoc}
1271
     *
1272
     */
1273 View Code Duplication
    public function getDecoratedConnection()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1274
    {
1275
        $options = $this->getOptions();
1276
        $options = [
1277
            'username' => $options['user'],
1278
            'password' => $options['pass'],
1279
            'database' => $options['name'],
1280
            'quoteIdentifiers' => true,
1281
        ] + $options;
1282
1283
        $driver = new SqlServerDriver($options);
1284
1285
        if (method_exists($driver, 'setConnection')) {
1286
            $driver->setConnection($this->connection);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can be null; however, setConnection() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1287
        } else {
1288
            $driver->connection($this->connection);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Database\Driver::connection() has been deprecated with message: 3.6.0 Use getConnection()/setConnection() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1289
        }
1290
1291
        return new Connection(['driver' => $driver] + $options);
1292
    }
1293
}
1294