Completed
Push — master ( e33650...15989c )
by José
02:42
created

getChangePrimaryKeyInstructions()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 37

Duplication

Lines 37
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 37
loc 37
rs 9.0168
c 0
b 0
f 0
ccs 0
cts 20
cp 0
cc 5
nc 8
nop 2
crap 30
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
        $result = $this->fetchRow(sprintf('SELECT count(*) as [count] FROM information_schema.tables WHERE table_name = \'%s\';', $tableName));
203
204
        return $result['count'] > 0;
205
    }
206
207
    /**
208
     * {@inheritdoc}
209
     */
210
    public function createTable(Table $table, array $columns = [], array $indexes = [])
211
    {
212
        $options = $table->getOptions();
213
214
        // Add the default primary key
215 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...
216
            $column = new Column();
217
            $column->setName('id')
218
                   ->setType('integer')
219
                   ->setIdentity(true);
220
221
            array_unshift($columns, $column);
222
            $options['primary_key'] = 'id';
223
        } elseif (isset($options['id']) && is_string($options['id'])) {
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')
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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
276
    /**
277
     * {@inheritdoc}
278
     */
279 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...
280
    {
281
        $instructions = new AlterInstructions();
282
283
        // Drop the existing primary key
284
        $primaryKey = $this->getPrimaryKey($table->getName());
285
        if (!empty($primaryKey['constraint'])) {
286
            $sql = sprintf(
287
                'DROP CONSTRAINT %s',
288
                $this->quoteColumnName($primaryKey['constraint'])
289
            );
290
            $instructions->addAlter($sql);
291
        }
292
293
        // Add the primary key(s)
294
        if (!empty($newColumns)) {
295
            $sql = sprintf(
296
                'ALTER TABLE %s ADD CONSTRAINT %s PRIMARY KEY (',
297
                $this->quoteTableName($table->getName()),
298
                $this->quoteColumnName('PK_' . $table->getName())
299
            );
300
            if (is_string($newColumns)) { // handle primary_key => 'id'
301
                $sql .= $this->quoteColumnName($newColumns);
302
            } elseif (is_array($newColumns)) { // handle primary_key => array('tag_id', 'resource_id')
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
303
                $sql .= implode(',', array_map([$this, 'quoteColumnName'], $newColumns));
304
            } else {
305
                throw new \InvalidArgumentException(sprintf(
306
                    "Invalid value for primary key: %s",
307
                    json_encode($newColumns)
308
                ));
309
            }
310
            $sql .= ')';
311
            $instructions->addPostStep($sql);
312
        }
313
314
        return $instructions;
315
    }
316
317
    /**
318
     * {@inheritdoc}
319
     */
320
    protected function getChangeCommentInstructions(Table $table, $newComment)
321
    {
322
        throw new \BadMethodCallException('SQLite does not have table comments');
323
    }
324
325
    /**
326
     * Gets the SqlServer Column Comment Defininition for a column object.
327
     *
328
     * @param \Phinx\Db\Table\Column $column    Column
329
     * @param string $tableName Table name
330
     *
331
     * @return string
332
     */
333
    protected function getColumnCommentSqlDefinition(Column $column, $tableName)
334
    {
335
        // passing 'null' is to remove column comment
336
        $currentComment = $this->getColumnComment($tableName, $column->getName());
337
338
        $comment = (strcasecmp($column->getComment(), 'NULL') !== 0) ? $this->getConnection()->quote($column->getComment()) : '\'\'';
339
        $command = $currentComment === false ? 'sp_addextendedproperty' : 'sp_updateextendedproperty';
340
341
        return sprintf(
342
            "EXECUTE %s N'MS_Description', N%s, N'SCHEMA', N'%s', N'TABLE', N'%s', N'COLUMN', N'%s';",
343
            $command,
344
            $comment,
345
            $this->schema,
346
            $tableName,
347
            $column->getName()
348
        );
349
    }
350
351
    /**
352
     * {@inheritdoc}
353
     */
354
    protected function getRenameTableInstructions($tableName, $newTableName)
355
    {
356
        $sql = sprintf(
357
            'EXEC sp_rename \'%s\', \'%s\'',
358
            $tableName,
359
            $newTableName
360
        );
361
362
        return new AlterInstructions([], [$sql]);
363
    }
364
365
    /**
366
     * {@inheritdoc}
367
     */
368
    protected function getDropTableInstructions($tableName)
369
    {
370
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
371
372
        return new AlterInstructions([], [$sql]);
373
    }
374
375
    /**
376
     * {@inheritdoc}
377
     */
378
    public function truncateTable($tableName)
379
    {
380
        $sql = sprintf(
381
            'TRUNCATE TABLE %s',
382
            $this->quoteTableName($tableName)
383
        );
384
385
        $this->execute($sql);
386
    }
387
388
    public function getColumnComment($tableName, $columnName)
389
    {
390
        $sql = sprintf("SELECT cast(extended_properties.[value] as nvarchar(4000)) comment
391
  FROM sys.schemas
392
 INNER JOIN sys.tables
393
    ON schemas.schema_id = tables.schema_id
394
 INNER JOIN sys.columns
395
    ON tables.object_id = columns.object_id
396
 INNER JOIN sys.extended_properties
397
    ON tables.object_id = extended_properties.major_id
398
   AND columns.column_id = extended_properties.minor_id
399
   AND extended_properties.name = 'MS_Description'
400
   WHERE schemas.[name] = '%s' AND tables.[name] = '%s' AND columns.[name] = '%s'", $this->schema, $tableName, $columnName);
401
        $row = $this->fetchRow($sql);
402
403
        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...
404
            return trim($row['comment']);
405
        }
406
407
        return false;
408
    }
409
410
    /**
411
     * {@inheritdoc}
412
     */
413
    public function getColumns($tableName)
414
    {
415
        $columns = [];
416
        $sql = sprintf(
417
            "SELECT DISTINCT TABLE_SCHEMA AS [schema], TABLE_NAME as [table_name], COLUMN_NAME AS [name], DATA_TYPE AS [type],
418
            IS_NULLABLE AS [null], COLUMN_DEFAULT AS [default],
419
            CHARACTER_MAXIMUM_LENGTH AS [char_length],
420
            NUMERIC_PRECISION AS [precision],
421
            NUMERIC_SCALE AS [scale], ORDINAL_POSITION AS [ordinal_position],
422
            COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as [identity]
423
        FROM INFORMATION_SCHEMA.COLUMNS
424
        WHERE TABLE_NAME = '%s'
425
        ORDER BY ordinal_position",
426
            $tableName
427
        );
428
        $rows = $this->fetchAll($sql);
429
        foreach ($rows as $columnInfo) {
430
            $column = new Column();
431
            $column->setName($columnInfo['name'])
432
                   ->setType($this->getPhinxType($columnInfo['type']))
433
                   ->setNull($columnInfo['null'] !== 'NO')
434
                   ->setDefault($this->parseDefault($columnInfo['default']))
435
                   ->setIdentity($columnInfo['identity'] === '1')
436
                   ->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...
437
438
            if (!empty($columnInfo['char_length'])) {
439
                $column->setLimit($columnInfo['char_length']);
440
            }
441
442
            $columns[$columnInfo['name']] = $column;
443
        }
444
445
        return $columns;
446
    }
447
448
    protected function parseDefault($default)
449
    {
450
        $default = preg_replace(["/\('(.*)'\)/", "/\(\((.*)\)\)/", "/\((.*)\)/"], '$1', $default);
451
452
        if (strtoupper($default) === 'NULL') {
453
            $default = null;
454
        } elseif (is_numeric($default)) {
455
            $default = (int)$default;
456
        }
457
458
        return $default;
459
    }
460
461
    /**
462
     * {@inheritdoc}
463
     */
464
    public function hasColumn($tableName, $columnName)
465
    {
466
        $sql = sprintf(
467
            "SELECT count(*) as [count]
468
             FROM information_schema.columns
469
             WHERE table_name = '%s' AND column_name = '%s'",
470
            $tableName,
471
            $columnName
472
        );
473
        $result = $this->fetchRow($sql);
474
475
        return $result['count'] > 0;
476
    }
477
478
    /**
479
     * {@inheritdoc}
480
     */
481 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...
482
    {
483
        $alter = sprintf(
484
            'ALTER TABLE %s ADD %s %s',
485
            $table->getName(),
486
            $this->quoteColumnName($column->getName()),
487
            $this->getColumnSqlDefinition($column)
488
        );
489
490
        return new AlterInstructions([], [$alter]);
491
    }
492
493
    /**
494
     * {@inheritdoc}
495
     */
496
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
497
    {
498
        if (!$this->hasColumn($tableName, $columnName)) {
499
            throw new \InvalidArgumentException("The specified column does not exist: $columnName");
500
        }
501
502
        $instructions = new AlterInstructions();
503
504
        $oldConstraintName = "DF_{$tableName}_{$columnName}";
505
        $newConstraintName = "DF_{$tableName}_{$newColumnName}";
506
        $sql = <<<SQL
507
IF (OBJECT_ID('$oldConstraintName', 'D') IS NOT NULL)
508
BEGIN
509
     EXECUTE sp_rename N'%s', N'%s', N'OBJECT'
510
END
511
SQL;
512
        $instructions->addPostStep(sprintf(
513
            $sql,
514
            $oldConstraintName,
515
            $newConstraintName
516
        ));
517
518
        $instructions->addPostStep(sprintf(
519
            "EXECUTE sp_rename N'%s.%s', N'%s', 'COLUMN' ",
520
            $tableName,
521
            $columnName,
522
            $newColumnName
523
        ));
524
525
        return $instructions;
526
    }
527
528
    /**
529
     * Returns the instructions to change a column default value
530
     *
531
     * @param string $tableName The table where the column is
532
     * @param Column $newColumn The column to alter
533
     * @return AlterInstructions
534
     */
535
    protected function getChangeDefault($tableName, Column $newColumn)
536
    {
537
        $constraintName = "DF_{$tableName}_{$newColumn->getName()}";
538
        $default = $newColumn->getDefault();
539
        $instructions = new AlterInstructions();
540
541
        if ($default === null) {
542
            $default = 'DEFAULT NULL';
543
        } else {
544
            $default = ltrim($this->getDefaultValueDefinition($default));
545
        }
546
547
        if (empty($default)) {
548
            return $instructions;
549
        }
550
551
        $instructions->addPostStep(sprintf(
552
            'ALTER TABLE %s ADD CONSTRAINT %s %s FOR %s',
553
            $this->quoteTableName($tableName),
554
            $constraintName,
555
            $default,
556
            $this->quoteColumnName($newColumn->getName())
557
        ));
558
559
        return $instructions;
560
    }
561
562
    /**
563
     * {@inheritdoc}
564
     */
565
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
566
    {
567
        $columns = $this->getColumns($tableName);
568
        $changeDefault =
569
            $newColumn->getDefault() !== $columns[$columnName]->getDefault() ||
570
            $newColumn->getType() !== $columns[$columnName]->getType();
571
572
        $instructions = new AlterInstructions();
573
574
        if ($columnName !== $newColumn->getName()) {
575
            $instructions->merge(
576
                $this->getRenameColumnInstructions($tableName, $columnName, $newColumn->getName())
577
            );
578
        }
579
580
        if ($changeDefault) {
581
            $instructions->merge($this->getDropDefaultConstraint($tableName, $newColumn->getName()));
582
        }
583
584
        $instructions->addPostStep(sprintf(
585
            'ALTER TABLE %s ALTER COLUMN %s %s',
586
            $this->quoteTableName($tableName),
587
            $this->quoteColumnName($newColumn->getName()),
588
            $this->getColumnSqlDefinition($newColumn, false)
589
        ));
590
        // change column comment if needed
591
        if ($newColumn->getComment()) {
592
            $instructions->addPostStep($this->getColumnCommentSqlDefinition($newColumn, $tableName));
593
        }
594
595
        if ($changeDefault) {
596
            $instructions->merge($this->getChangeDefault($tableName, $newColumn));
597
        }
598
599
        return $instructions;
600
    }
601
602
    /**
603
     * {@inheritdoc}
604
     */
605
    protected function getDropColumnInstructions($tableName, $columnName)
606
    {
607
        $instructions = $this->getDropDefaultConstraint($tableName, $columnName);
608
609
        $instructions->addPostStep(sprintf(
610
            'ALTER TABLE %s DROP COLUMN %s',
611
            $this->quoteTableName($tableName),
612
            $this->quoteColumnName($columnName)
613
        ));
614
615
        return $instructions;
616
    }
617
618
    /**
619
     * {@inheritdoc}
620
     */
621
    protected function getDropDefaultConstraint($tableName, $columnName)
622
    {
623
        $defaultConstraint = $this->getDefaultConstraint($tableName, $columnName);
624
625
        if (!$defaultConstraint) {
626
            return new AlterInstructions();
627
        }
628
629
        return $this->getDropForeignKeyInstructions($tableName, $defaultConstraint);
630
    }
631
632
    protected function getDefaultConstraint($tableName, $columnName)
633
    {
634
        $sql = "SELECT
635
    default_constraints.name
636
FROM
637
    sys.all_columns
638
639
        INNER JOIN
640
    sys.tables
641
        ON all_columns.object_id = tables.object_id
642
643
        INNER JOIN
644
    sys.schemas
645
        ON tables.schema_id = schemas.schema_id
646
647
        INNER JOIN
648
    sys.default_constraints
649
        ON all_columns.default_object_id = default_constraints.object_id
650
651
WHERE
652
        schemas.name = 'dbo'
653
    AND tables.name = '{$tableName}'
654
    AND all_columns.name = '{$columnName}'";
655
656
        $rows = $this->fetchAll($sql);
657
658
        return empty($rows) ? false : $rows[0]['name'];
659
    }
660
661
    protected function getIndexColums($tableId, $indexId)
662
    {
663
        $sql = "SELECT AC.[name] AS [column_name]
664
FROM sys.[index_columns] IC
665
  INNER JOIN sys.[all_columns] AC ON IC.[column_id] = AC.[column_id]
666
WHERE AC.[object_id] = {$tableId} AND IC.[index_id] = {$indexId}  AND IC.[object_id] = {$tableId}
667
ORDER BY IC.[key_ordinal];";
668
669
        $rows = $this->fetchAll($sql);
670
        $columns = [];
671
        foreach ($rows as $row) {
672
            $columns[] = strtolower($row['column_name']);
673
        }
674
675
        return $columns;
676
    }
677
678
    /**
679
     * Get an array of indexes from a particular table.
680
     *
681
     * @param string $tableName Table Name
682
     * @return array
683
     */
684
    public function getIndexes($tableName)
685
    {
686
        $indexes = [];
687
        $sql = "SELECT I.[name] AS [index_name], I.[index_id] as [index_id], T.[object_id] as [table_id]
688
FROM sys.[tables] AS T
689
  INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]
690
WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP'  AND T.[name] = '{$tableName}'
691
ORDER BY T.[name], I.[index_id];";
692
693
        $rows = $this->fetchAll($sql);
694
        foreach ($rows as $row) {
695
            $columns = $this->getIndexColums($row['table_id'], $row['index_id']);
696
            $indexes[$row['index_name']] = ['columns' => $columns];
697
        }
698
699
        return $indexes;
700
    }
701
702
    /**
703
     * {@inheritdoc}
704
     */
705 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...
706
    {
707
        if (is_string($columns)) {
708
            $columns = [$columns]; // str to array
709
        }
710
711
        $columns = array_map('strtolower', $columns);
712
        $indexes = $this->getIndexes($tableName);
713
714
        foreach ($indexes as $index) {
715
            $a = array_diff($columns, $index['columns']);
716
717
            if (empty($a)) {
718
                return true;
719
            }
720
        }
721
722
        return false;
723
    }
724
725
    /**
726
     * {@inheritdoc}
727
     */
728 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...
729
    {
730
        $indexes = $this->getIndexes($tableName);
731
732
        foreach ($indexes as $name => $index) {
733
            if ($name === $indexName) {
734
                 return true;
735
            }
736
        }
737
738
        return false;
739
    }
740
741
    /**
742
     * {@inheritdoc}
743
     */
744
    protected function getAddIndexInstructions(Table $table, Index $index)
745
    {
746
        $sql = $this->getIndexSqlDefinition($index, $table->getName());
747
748
        return new AlterInstructions([], [$sql]);
749
    }
750
751
    /**
752
     * {@inheritdoc}
753
     */
754
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
755
    {
756
        if (is_string($columns)) {
757
            $columns = [$columns]; // str to array
758
        }
759
760
        $indexes = $this->getIndexes($tableName);
761
        $columns = array_map('strtolower', $columns);
762
        $instructions = new AlterInstructions();
763
764
        foreach ($indexes as $indexName => $index) {
765
            $a = array_diff($columns, $index['columns']);
766 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...
767
                $instructions->addPostStep(sprintf(
768
                    'DROP INDEX %s ON %s',
769
                    $this->quoteColumnName($indexName),
770
                    $this->quoteTableName($tableName)
771
                ));
772
773
                return $instructions;
774
            }
775
        }
776
777
        throw new \InvalidArgumentException(sprintf(
778
            "The specified index on columns '%s' does not exist",
779
            implode(',', $columns)
780
        ));
781
    }
782
783
    /**
784
     * {@inheritdoc}
785
     */
786
    protected function getDropIndexByNameInstructions($tableName, $indexName)
787
    {
788
        $indexes = $this->getIndexes($tableName);
789
        $instructions = new AlterInstructions();
790
791
        foreach ($indexes as $name => $index) {
792 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...
793
                $instructions->addPostStep(sprintf(
794
                    'DROP INDEX %s ON %s',
795
                    $this->quoteColumnName($indexName),
796
                    $this->quoteTableName($tableName)
797
                ));
798
799
                return $instructions;
800
            }
801
        }
802
803
        throw new \InvalidArgumentException(sprintf(
804
            "The specified index name '%s' does not exist",
805
            $indexName
806
        ));
807
    }
808
809
    /**
810
     * {@inheritdoc}
811
     */
812 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...
813
    {
814
        $primaryKey = $this->getPrimaryKey($tableName);
815
816
        if (empty($primaryKey)) {
817
            return false;
818
        }
819
820
        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...
821
            return ($primaryKey['constraint'] === $constraint);
822
        } else {
823
            if (is_string($columns)) {
824
                $columns = [$columns]; // str to array
825
            }
826
            $missingColumns = array_diff($columns, $primaryKey['columns']);
827
828
            return empty($missingColumns);
829
        }
830
    }
831
832
    /**
833
     * Get the primary key from a particular table.
834
     *
835
     * @param string $tableName Table Name
836
     * @return array
837
     */
838 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...
839
    {
840
        $rows = $this->fetchAll(sprintf(
841
            "SELECT
842
                    tc.constraint_name,
843
                    kcu.column_name
844
                FROM information_schema.table_constraints AS tc
845
                JOIN information_schema.key_column_usage AS kcu
846
                    ON tc.constraint_name = kcu.constraint_name
847
                WHERE constraint_type = 'PRIMARY KEY'
848
                    AND tc.table_name = '%s'
849
                ORDER BY kcu.ordinal_position",
850
            $tableName
851
        ));
852
853
        $primaryKey = [
854
            'columns' => [],
855
        ];
856
        foreach ($rows as $row) {
857
            $primaryKey['constraint'] = $row['constraint_name'];
858
            $primaryKey['columns'][] = $row['column_name'];
859
        }
860
861
        return $primaryKey;
862
    }
863
864
    /**
865
     * {@inheritdoc}
866
     */
867 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...
868
    {
869
        if (is_string($columns)) {
870
            $columns = [$columns]; // str to array
871
        }
872
        $foreignKeys = $this->getForeignKeys($tableName);
873
        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...
874
            if (isset($foreignKeys[$constraint])) {
875
                return !empty($foreignKeys[$constraint]);
876
            }
877
878
            return false;
879
        } else {
880
            foreach ($foreignKeys as $key) {
881
                $a = array_diff($columns, $key['columns']);
882
                if (empty($a)) {
883
                    return true;
884
                }
885
            }
886
887
            return false;
888
        }
889
    }
890
891
    /**
892
     * Get an array of foreign keys from a particular table.
893
     *
894
     * @param string $tableName Table Name
895
     * @return array
896
     */
897 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...
898
    {
899
        $foreignKeys = [];
900
        $rows = $this->fetchAll(sprintf(
901
            "SELECT
902
                    tc.constraint_name,
903
                    tc.table_name, kcu.column_name,
904
                    ccu.table_name AS referenced_table_name,
905
                    ccu.column_name AS referenced_column_name
906
                FROM
907
                    information_schema.table_constraints AS tc
908
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
909
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
910
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s'
911
                ORDER BY kcu.ordinal_position",
912
            $tableName
913
        ));
914
        foreach ($rows as $row) {
915
            $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
916
            $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
917
            $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
918
            $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
919
        }
920
921
        return $foreignKeys;
922
    }
923
924
    /**
925
     * {@inheritdoc}
926
     */
927
    protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreignKey)
928
    {
929
        $instructions = new AlterInstructions();
930
        $instructions->addPostStep(sprintf(
931
            'ALTER TABLE %s ADD %s',
932
            $this->quoteTableName($table->getName()),
933
            $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
934
        ));
935
936
        return $instructions;
937
    }
938
939
    /**
940
     * {@inheritdoc}
941
     */
942
    protected function getDropForeignKeyInstructions($tableName, $constraint)
943
    {
944
        $instructions = new AlterInstructions();
945
        $instructions->addPostStep(sprintf(
946
            'ALTER TABLE %s DROP CONSTRAINT %s',
947
            $this->quoteTableName($tableName),
948
            $constraint
949
        ));
950
951
        return $instructions;
952
    }
953
954
    /**
955
     * {@inheritdoc}
956
     */
957
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
958
    {
959
        $instructions = new AlterInstructions();
960
961 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...
962
            $rows = $this->fetchAll(sprintf(
963
                "SELECT
964
                tc.constraint_name,
965
                tc.table_name, kcu.column_name,
966
                ccu.table_name AS referenced_table_name,
967
                ccu.column_name AS referenced_column_name
968
            FROM
969
                information_schema.table_constraints AS tc
970
                JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
971
                JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
972
            WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' and ccu.column_name='%s'
973
            ORDER BY kcu.ordinal_position",
974
                $tableName,
975
                $column
976
            ));
977
            foreach ($rows as $row) {
978
                $instructions->merge(
979
                    $this->getDropForeignKeyInstructions($tableName, $row['constraint_name'])
980
                );
981
            }
982
        }
983
984
        return $instructions;
985
    }
986
987
    /**
988
     * {@inheritdoc}
989
     */
990
    public function getSqlType($type, $limit = null)
991
    {
992
        switch ($type) {
993
            case static::PHINX_TYPE_FLOAT:
994
            case static::PHINX_TYPE_DECIMAL:
995
            case static::PHINX_TYPE_DATETIME:
996
            case static::PHINX_TYPE_TIME:
997
            case static::PHINX_TYPE_DATE:
998
                return ['name' => $type];
999
            case static::PHINX_TYPE_STRING:
1000
                return ['name' => 'nvarchar', 'limit' => 255];
1001
            case static::PHINX_TYPE_CHAR:
1002
                return ['name' => 'nchar', 'limit' => 255];
1003
            case static::PHINX_TYPE_TEXT:
1004
                return ['name' => 'ntext'];
1005
            case static::PHINX_TYPE_INTEGER:
1006
                return ['name' => 'int'];
1007
            case static::PHINX_TYPE_BIG_INTEGER:
1008
                return ['name' => 'bigint'];
1009
            case static::PHINX_TYPE_TIMESTAMP:
1010
                return ['name' => 'datetime'];
1011
            case static::PHINX_TYPE_BLOB:
1012
            case static::PHINX_TYPE_BINARY:
1013
                return ['name' => 'varbinary'];
1014
            case static::PHINX_TYPE_BOOLEAN:
1015
                return ['name' => 'bit'];
1016
            case static::PHINX_TYPE_UUID:
1017
                return ['name' => 'uniqueidentifier'];
1018
            case static::PHINX_TYPE_FILESTREAM:
1019
                return ['name' => 'varbinary', 'limit' => 'max'];
1020
            // Geospatial database types
1021
            case static::PHINX_TYPE_GEOMETRY:
1022
            case static::PHINX_TYPE_POINT:
1023
            case static::PHINX_TYPE_LINESTRING:
1024
            case static::PHINX_TYPE_POLYGON:
1025
                // SQL Server stores all spatial data using a single data type.
1026
                // Specific types (point, polygon, etc) are set at insert time.
1027
                return ['name' => 'geography'];
1028
            default:
1029
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
1030
        }
1031
    }
1032
1033
    /**
1034
     * Returns Phinx type by SQL type
1035
     *
1036
     * @param string $sqlType SQL Type definition
1037
     * @throws \RuntimeException
1038
     * @internal param string $sqlType SQL type
1039
     * @returns string Phinx type
1040
     */
1041
    public function getPhinxType($sqlType)
1042
    {
1043
        switch ($sqlType) {
1044
            case 'nvarchar':
1045
            case 'varchar':
1046
                return static::PHINX_TYPE_STRING;
1047
            case 'char':
1048
            case 'nchar':
1049
                return static::PHINX_TYPE_CHAR;
1050
            case 'text':
1051
            case 'ntext':
1052
                return static::PHINX_TYPE_TEXT;
1053
            case 'int':
1054
            case 'integer':
1055
                return static::PHINX_TYPE_INTEGER;
1056
            case 'decimal':
1057
            case 'numeric':
1058
            case 'money':
1059
                return static::PHINX_TYPE_DECIMAL;
1060
            case 'bigint':
1061
                return static::PHINX_TYPE_BIG_INTEGER;
1062
            case 'real':
1063
            case 'float':
1064
                return static::PHINX_TYPE_FLOAT;
1065
            case 'binary':
1066
            case 'image':
1067
            case 'varbinary':
1068
                return static::PHINX_TYPE_BINARY;
1069
            case 'time':
1070
                return static::PHINX_TYPE_TIME;
1071
            case 'date':
1072
                return static::PHINX_TYPE_DATE;
1073
            case 'datetime':
1074
            case 'timestamp':
1075
                return static::PHINX_TYPE_DATETIME;
1076
            case 'bit':
1077
                return static::PHINX_TYPE_BOOLEAN;
1078
            case 'uniqueidentifier':
1079
                return static::PHINX_TYPE_UUID;
1080
            case 'filestream':
1081
                return static::PHINX_TYPE_FILESTREAM;
1082
            default:
1083
                throw new \RuntimeException('The SqlServer type: "' . $sqlType . '" is not supported');
1084
        }
1085
    }
1086
1087
    /**
1088
     * {@inheritdoc}
1089
     */
1090
    public function createDatabase($name, $options = [])
1091
    {
1092 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...
1093
            $this->execute(sprintf('CREATE DATABASE [%s] COLLATE [%s]', $name, $options['collation']));
1094
        } else {
1095
            $this->execute(sprintf('CREATE DATABASE [%s]', $name));
1096
        }
1097
        $this->execute(sprintf('USE [%s]', $name));
1098
    }
1099
1100
    /**
1101
     * {@inheritdoc}
1102
     */
1103
    public function hasDatabase($name)
1104
    {
1105
        $result = $this->fetchRow(
1106
            sprintf(
1107
                'SELECT count(*) as [count] FROM master.dbo.sysdatabases WHERE [name] = \'%s\'',
1108
                $name
1109
            )
1110
        );
1111
1112
        return $result['count'] > 0;
1113
    }
1114
1115
    /**
1116
     * {@inheritdoc}
1117
     */
1118
    public function dropDatabase($name)
1119
    {
1120
        $sql = <<<SQL
1121
USE master;
1122
IF EXISTS(select * from sys.databases where name=N'$name')
1123
ALTER DATABASE [$name] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
1124
DROP DATABASE [$name];
1125
SQL;
1126
        $this->execute($sql);
1127
    }
1128
1129
    /**
1130
     * Gets the SqlServer Column Definition for a Column object.
1131
     *
1132
     * @param \Phinx\Db\Table\Column $column Column
1133
     * @return string
1134
     */
1135
    protected function getColumnSqlDefinition(Column $column, $create = true)
1136
    {
1137
        $buffer = [];
1138
        if ($column->getType() instanceof Literal) {
1139
            $buffer[] = (string)$column->getType();
1140
        } else {
1141
            $sqlType = $this->getSqlType($column->getType());
1142
            $buffer[] = strtoupper($sqlType['name']);
1143
            // integers cant have limits in SQlServer
1144
            $noLimits = [
1145
                'bigint',
1146
                'int',
1147
                'tinyint'
1148
            ];
1149
            if (!in_array($sqlType['name'], $noLimits) && ($column->getLimit() || isset($sqlType['limit']))) {
1150
                $buffer[] = sprintf('(%s)', $column->getLimit() ? $column->getLimit() : $sqlType['limit']);
1151
            }
1152
        }
1153 View Code Duplication
        if ($column->getPrecision() && $column->getScale()) {
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...
1154
            $buffer[] = '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1155
        }
1156
1157
        $properties = $column->getProperties();
1158
        $buffer[] = $column->getType() === 'filestream' ? 'FILESTREAM' : '';
1159
        $buffer[] = isset($properties['rowguidcol']) ? 'ROWGUIDCOL' : '';
1160
1161
        $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';
1162
1163
        if ($create === true) {
1164
            if ($column->getDefault() === null && $column->isNull()) {
1165
                $buffer[] = ' DEFAULT NULL';
1166
            } else {
1167
                $buffer[] = $this->getDefaultValueDefinition($column->getDefault());
1168
            }
1169
        }
1170
1171
        if ($column->isIdentity()) {
1172
            $buffer[] = 'IDENTITY(1, 1)';
1173
        }
1174
1175
        return implode(' ', $buffer);
1176
    }
1177
1178
    /**
1179
     * Gets the SqlServer Index Definition for an Index object.
1180
     *
1181
     * @param \Phinx\Db\Table\Index $index Index
1182
     * @return string
1183
     */
1184
    protected function getIndexSqlDefinition(Index $index, $tableName)
1185
    {
1186 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...
1187
            $indexName = $index->getName();
1188
        } else {
1189
            $columnNames = $index->getColumns();
1190
            $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames));
1191
        }
1192
        $def = sprintf(
1193
            "CREATE %s INDEX %s ON %s (%s);",
1194
            ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
1195
            $indexName,
1196
            $this->quoteTableName($tableName),
1197
            '[' . implode('],[', $index->getColumns()) . ']'
1198
        );
1199
1200
        return $def;
1201
    }
1202
1203
    /**
1204
     * Gets the SqlServer Foreign Key Definition for an ForeignKey object.
1205
     *
1206
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1207
     * @return string
1208
     */
1209
    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName)
1210
    {
1211
        $constraintName = $foreignKey->getConstraint() ?: $tableName . '_' . implode('_', $foreignKey->getColumns());
1212
        $def = ' CONSTRAINT ' . $this->quoteColumnName($constraintName);
0 ignored issues
show
Bug introduced by
It seems like $constraintName defined by $foreignKey->getConstrai...reignKey->getColumns()) on line 1211 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...
1213
        $def .= ' FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")';
1214
        $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")';
1215
        if ($foreignKey->getOnDelete()) {
1216
            $def .= " ON DELETE {$foreignKey->getOnDelete()}";
1217
        }
1218
        if ($foreignKey->getOnUpdate()) {
1219
            $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
1220
        }
1221
1222
        return $def;
1223
    }
1224
1225
    /**
1226
     * {@inheritdoc}
1227
     */
1228
    public function getColumnTypes()
1229
    {
1230
        return array_merge(parent::getColumnTypes(), ['filestream']);
1231
    }
1232
1233
    /**
1234
     * Records a migration being run.
1235
     *
1236
     * @param \Phinx\Migration\MigrationInterface $migration Migration
1237
     * @param string $direction Direction
1238
     * @param int $startTime Start Time
1239
     * @param int $endTime End Time
1240
     * @return \Phinx\Db\Adapter\AdapterInterface
1241
     */
1242
    public function migrated(\Phinx\Migration\MigrationInterface $migration, $direction, $startTime, $endTime)
1243
    {
1244
        $startTime = str_replace(' ', 'T', $startTime);
1245
        $endTime = str_replace(' ', 'T', $endTime);
1246
1247
        return parent::migrated($migration, $direction, $startTime, $endTime);
1248
    }
1249
1250
    /**
1251
     * {@inheritDoc}
1252
     *
1253
     */
1254 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...
1255
    {
1256
        $options = $this->getOptions();
1257
        $options = [
1258
            'username' => $options['user'],
1259
            'password' => $options['pass'],
1260
            'database' => $options['name'],
1261
            'quoteIdentifiers' => true,
1262
        ] + $options;
1263
1264
        $driver = new SqlServerDriver($options);
1265
1266
        if (method_exists($driver, 'setConnection')) {
1267
            $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...
1268
        } else {
1269
            $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...
1270
        }
1271
1272
        return new Connection(['driver' => $driver] + $options);
1273
    }
1274
}
1275