Completed
Pull Request — master (#1175)
by
unknown
01:55
created

SqlServerAdapter::getColumnSqlDefinition()   C

Complexity

Conditions 13
Paths 49

Size

Total Lines 41
Code Lines 26

Duplication

Lines 3
Ratio 7.32 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 0
Metric Value
dl 3
loc 41
ccs 0
cts 29
cp 0
rs 5.1234
c 0
b 0
f 0
cc 13
eloc 26
nc 49
nop 2
crap 182

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Phinx\Db\Table;
32
use Phinx\Db\Table\Column;
33
use Phinx\Db\Table\Index;
34
use Phinx\Db\Table\ForeignKey;
35
36
/**
37
 * Phinx SqlServer Adapter.
38
 *
39
 * @author Rob Morgan <[email protected]>
40
 */
41
class SqlServerAdapter extends PdoAdapter implements AdapterInterface
42
{
43
    protected $schema = 'dbo';
44
45
    protected $signedColumnTypes = ['integer' => true, 'biginteger' => true, 'float' => true, 'decimal' => true];
46
47
    /**
48
     * {@inheritdoc}
49
     */
50
    public function connect()
51
    {
52
        if ($this->connection === null) {
53
            if (!class_exists('PDO') || !in_array('sqlsrv', \PDO::getAvailableDrivers(), true)) {
54
                // try our connection via freetds (Mac/Linux)
55
                $this->connectDblib();
56
                return;
57
            }
58
59
            $db = null;
60
            $options = $this->getOptions();
61
62
            // if port is specified use it, otherwise use the SqlServer default
63 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...
64
                $dsn = 'sqlsrv:server=' . $options['host'] . ';database=' . $options['name'];
65
            } else {
66
                $dsn = 'sqlsrv:server=' . $options['host'] . ',' . $options['port'] . ';database=' . $options['name'];
67
            }
68
            $dsn .= ';MultipleActiveResultSets=false';
69
70
            $driverOptions = [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION];
71
72
            // charset support
73
            if (isset($options['charset'])) {
74
                $driverOptions[\PDO::SQLSRV_ATTR_ENCODING] = $options['charset'];
75
            }
76
77
            // support arbitrary \PDO::SQLSRV_ATTR_* driver options and pass them to PDO
78
            // http://php.net/manual/en/ref.pdo-sqlsrv.php#pdo-sqlsrv.constants
79 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...
80
                if (strpos($key, 'sqlsrv_attr_') === 0) {
81
                    $driverOptions[constant('\PDO::' . strtoupper($key))] = $option;
82
                }
83
            }
84
85
            try {
86
                $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
87
            } catch (\PDOException $exception) {
88
                throw new \InvalidArgumentException(sprintf(
89
                    'There was a problem connecting to the database: %s',
90
                    $exception->getMessage()
91
                ));
92
            }
93
94
            $this->setConnection($db);
95
        }
96
    }
97
98
    /**
99
     * Connect to MSSQL using dblib/freetds.
100
     *
101
     * The "sqlsrv" driver is not available on Unix machines.
102
     *
103
     * @throws \InvalidArgumentException
104
     */
105
    protected function connectDblib()
106
    {
107
        if (!class_exists('PDO') || !in_array('dblib', \PDO::getAvailableDrivers(), true)) {
108
            // @codeCoverageIgnoreStart
109
            throw new \RuntimeException('You need to enable the PDO_Dblib extension for Phinx to run properly.');
110
            // @codeCoverageIgnoreEnd
111
        }
112
113
        $options = $this->getOptions();
114
115
        // if port is specified use it, otherwise use the SqlServer default
116 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...
117
            $dsn = 'dblib:host=' . $options['host'] . ';dbname=' . $options['name'];
118
        } else {
119
            $dsn = 'dblib:host=' . $options['host'] . ':' . $options['port'] . ';dbname=' . $options['name'];
120
        }
121
122
        $driverOptions = [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION];
123
124
125
        try {
126
            $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
127
        } catch (\PDOException $exception) {
128
            throw new \InvalidArgumentException(sprintf(
129
                'There was a problem connecting to the database: %s',
130
                $exception->getMessage()
131
            ));
132
        }
133
134
        $this->setConnection($db);
135
    }
136
137
    /**
138
     * {@inheritdoc}
139
     */
140
    public function disconnect()
141
    {
142
        $this->connection = null;
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148
    public function hasTransactions()
149
    {
150
        return true;
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156
    public function beginTransaction()
157
    {
158
        $this->execute('BEGIN TRANSACTION');
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164
    public function commitTransaction()
165
    {
166
        $this->execute('COMMIT TRANSACTION');
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172
    public function rollbackTransaction()
173
    {
174
        $this->execute('ROLLBACK TRANSACTION');
175
    }
176
177
    /**
178
     * {@inheritdoc}
179
     */
180
    public function quoteTableName($tableName)
181
    {
182
        return str_replace('.', '].[', $this->quoteColumnName($tableName));
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     */
188
    public function quoteColumnName($columnName)
189
    {
190
        return '[' . str_replace(']', '\]', $columnName) . ']';
191
    }
192
193
    /**
194
     * {@inheritdoc}
195
     */
196
    public function hasTable($tableName)
197
    {
198
        $result = $this->fetchRow(sprintf('SELECT count(*) as [count] FROM information_schema.tables WHERE table_name = \'%s\';', $tableName));
199
        return $result['count'] > 0;
200
    }
201
202
    /**
203
     * {@inheritdoc}
204
     */
205
    public function createTable(Table $table)
206
    {
207
        $options = $table->getOptions();
208
209
        // Add the default primary key
210
        $columns = $table->getPendingColumns();
211 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...
212
            $column = new Column();
213
            $column->setName('id')
214
                   ->setType('integer')
215
                   ->setIdentity(true);
216
217
            array_unshift($columns, $column);
218
            $options['primary_key'] = 'id';
219
        } elseif (isset($options['id']) && is_string($options['id'])) {
220
            // Handle id => "field_name" to support AUTO_INCREMENT
221
            $column = new Column();
222
            $column->setName($options['id'])
223
                   ->setType('integer')
224
                   ->setIdentity(true);
225
226
            array_unshift($columns, $column);
227
            $options['primary_key'] = $options['id'];
228
        }
229
230
        $sql = 'CREATE TABLE ';
231
        $sql .= $this->quoteTableName($table->getName()) . ' (';
232
        $sqlBuffer = [];
233
        $columnsWithComments = [];
234 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...
235
            $sqlBuffer[] = $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column);
236
237
            // set column comments, if needed
238
            if ($column->getComment()) {
239
                $columnsWithComments[] = $column;
240
            }
241
        }
242
243
        // set the primary key(s)
244
        if (isset($options['primary_key'])) {
245
            $pkSql = sprintf('CONSTRAINT PK_%s PRIMARY KEY (', $table->getName());
246
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
247
                $pkSql .= $this->quoteColumnName($options['primary_key']);
248
            } 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...
249
                // PHP 5.4 will allow access of $this, so we can call quoteColumnName() directly in the anonymous function,
250
                // but for now just hard-code the adapter quotes
251
                $pkSql .= implode(
252
                    ',',
253
                    array_map(
254
                        function ($v) {
255
                            return '[' . $v . ']';
256
                        },
257
                        $options['primary_key']
258
                    )
259
                );
260
            }
261
            $pkSql .= ')';
262
            $sqlBuffer[] = $pkSql;
263
        }
264
265
        // set the foreign keys
266
        $foreignKeys = $table->getForeignKeys();
267
        foreach ($foreignKeys as $foreignKey) {
268
            $sqlBuffer[] = $this->getForeignKeySqlDefinition($foreignKey, $table->getName());
269
        }
270
271
        $sql .= implode(', ', $sqlBuffer);
272
        $sql .= ');';
273
274
        // process column comments
275
        foreach ($columnsWithComments as $column) {
276
            $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
277
        }
278
279
        // set the indexes
280
        $indexes = $table->getIndexes();
281
        foreach ($indexes as $index) {
282
            $sql .= $this->getIndexSqlDefinition($index, $table->getName());
283
        }
284
285
        // execute the sql
286
        $this->execute($sql);
287
    }
288
289
    /**
290
     * Gets the SqlServer Column Comment Defininition for a column object.
291
     *
292
     * @param Column $column    Column
293
     * @param string $tableName Table name
294
     *
295
     * @return string
296
     */
297
    protected function getColumnCommentSqlDefinition(Column $column, $tableName)
298
    {
299
        // passing 'null' is to remove column comment
300
        $currentComment = $this->getColumnComment($tableName, $column->getName());
301
302
        $comment = (strcasecmp($column->getComment(), 'NULL') !== 0) ? $this->getConnection()->quote($column->getComment()) : '\'\'';
303
        $command = $currentComment === false ? 'sp_addextendedproperty' : 'sp_updateextendedproperty';
304
        return sprintf(
305
            "EXECUTE %s N'MS_Description', N%s, N'SCHEMA', N'%s', N'TABLE', N'%s', N'COLUMN', N'%s';",
306
            $command,
307
            $comment,
308
            $this->schema,
309
            $tableName,
310
            $column->getName()
311
        );
312
    }
313
314
    /**
315
     * {@inheritdoc}
316
     */
317
    public function renameTable($tableName, $newTableName)
318
    {
319
        $this->execute(sprintf('EXEC sp_rename \'%s\', \'%s\'', $tableName, $newTableName));
320
    }
321
322
    /**
323
     * {@inheritdoc}
324
     */
325
    public function dropTable($tableName)
326
    {
327
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
328
    }
329
330
    /**
331
     * {@inheritdoc}
332
     */
333
    public function truncateTable($tableName)
334
    {
335
        $sql = sprintf(
336
            'TRUNCATE TABLE %s',
337
            $this->quoteTableName($tableName)
338
        );
339
340
        $this->execute($sql);
341
    }
342
343
    public function getColumnComment($tableName, $columnName)
344
    {
345
        $sql = sprintf("SELECT cast(extended_properties.[value] as nvarchar(4000)) comment
346
  FROM sys.schemas
347
 INNER JOIN sys.tables
348
    ON schemas.schema_id = tables.schema_id
349
 INNER JOIN sys.columns
350
    ON tables.object_id = columns.object_id
351
 INNER JOIN sys.extended_properties
352
    ON tables.object_id = extended_properties.major_id
353
   AND columns.column_id = extended_properties.minor_id
354
   AND extended_properties.name = 'MS_Description'
355
   WHERE schemas.[name] = '%s' AND tables.[name] = '%s' AND columns.[name] = '%s'", $this->schema, $tableName, $columnName);
356
        $row = $this->fetchRow($sql);
357
358
        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...
359
            return $row['comment'];
360
        }
361
362
        return false;
363
    }
364
365
    /**
366
     * {@inheritdoc}
367
     */
368
    public function getColumns($tableName)
369
    {
370
        $columns = [];
371
        $sql = sprintf(
372
            "SELECT DISTINCT TABLE_SCHEMA AS [schema], TABLE_NAME as [table_name], COLUMN_NAME AS [name], DATA_TYPE AS [type],
373
            IS_NULLABLE AS [null], COLUMN_DEFAULT AS [default],
374
            CHARACTER_MAXIMUM_LENGTH AS [char_length],
375
            NUMERIC_PRECISION AS [precision],
376
            NUMERIC_SCALE AS [scale], ORDINAL_POSITION AS [ordinal_position],
377
            COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as [identity]
378
        FROM INFORMATION_SCHEMA.COLUMNS
379
        WHERE TABLE_NAME = '%s'
380
        ORDER BY ordinal_position",
381
            $tableName
382
        );
383
        $rows = $this->fetchAll($sql);
384
        foreach ($rows as $columnInfo) {
385
            $column = new Column();
386
            $column->setName($columnInfo['name'])
387
                   ->setType($this->getPhinxType($columnInfo['type']))
388
                   ->setNull($columnInfo['null'] !== 'NO')
389
                   ->setDefault($this->parseDefault($columnInfo['default']))
390
                   ->setIdentity($columnInfo['identity'] === '1')
391
                   ->setComment($this->getColumnComment($columnInfo['table_name'], $columnInfo['name']));
392
393
            if (!empty($columnInfo['char_length'])) {
394
                $column->setLimit($columnInfo['char_length']);
395
            }
396
397
            $columns[$columnInfo['name']] = $column;
398
        }
399
400
        return $columns;
401
    }
402
403
    protected function parseDefault($default)
404
    {
405
        $default = preg_replace(["/\('(.*)'\)/", "/\(\((.*)\)\)/", "/\((.*)\)/"], '$1', $default);
406
407
        if (strtoupper($default) === 'NULL') {
408
            $default = null;
409
        } elseif (is_numeric($default)) {
410
            $default = (int) $default;
411
        }
412
413
        return $default;
414
    }
415
416
    /**
417
     * {@inheritdoc}
418
     */
419 View Code Duplication
    public function hasColumn($tableName, $columnName)
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...
420
    {
421
        $sql = sprintf(
422
            "SELECT count(*) as [count]
423
             FROM information_schema.columns
424
             WHERE table_name = '%s' AND column_name = '%s'",
425
            $tableName,
426
            $columnName
427
        );
428
        $result = $this->fetchRow($sql);
429
430
        return $result['count'] > 0;
431
    }
432
433
    /**
434
     * {@inheritdoc}
435
     */
436 View Code Duplication
    public function addColumn(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...
437
    {
438
        $sql = sprintf(
439
            'ALTER TABLE %s ADD %s %s',
440
            $this->quoteTableName($table->getName()),
441
            $this->quoteColumnName($column->getName()),
442
            $this->getColumnSqlDefinition($column)
443
        );
444
445
        $this->execute($sql);
446
    }
447
448
    /**
449
     * {@inheritdoc}
450
     */
451
    public function renameColumn($tableName, $columnName, $newColumnName)
452
    {
453
        if (!$this->hasColumn($tableName, $columnName)) {
454
            throw new \InvalidArgumentException("The specified column does not exist: $columnName");
455
        }
456
        $this->renameDefault($tableName, $columnName, $newColumnName);
457
        $this->execute(
458
            sprintf(
459
                "EXECUTE sp_rename N'%s.%s', N'%s', 'COLUMN' ",
460
                $tableName,
461
                $columnName,
462
                $newColumnName
463
            )
464
        );
465
    }
466
467
    protected function renameDefault($tableName, $columnName, $newColumnName)
468
    {
469
        $oldConstraintName = "DF_{$tableName}_{$columnName}";
470
        $newConstraintName = "DF_{$tableName}_{$newColumnName}";
471
        $sql = <<<SQL
472
IF (OBJECT_ID('$oldConstraintName', 'D') IS NOT NULL)
473
BEGIN
474
     EXECUTE sp_rename N'%s', N'%s', N'OBJECT'
475
END
476
SQL;
477
        $this->execute(sprintf(
478
            $sql,
479
            $oldConstraintName,
480
            $newConstraintName
481
        ));
482
    }
483
484
    public function changeDefault($tableName, Column $newColumn)
485
    {
486
        $constraintName = "DF_{$tableName}_{$newColumn->getName()}";
487
        $default = $newColumn->getDefault();
488
489
        if ($default === null) {
490
            $default = 'DEFAULT NULL';
491
        } else {
492
            $default = ltrim($this->getDefaultValueDefinition($default));
493
        }
494
495
        if (empty($default)) {
496
            return;
497
        }
498
499
        $this->execute(sprintf(
500
            'ALTER TABLE %s ADD CONSTRAINT %s %s FOR %s',
501
            $this->quoteTableName($tableName),
502
            $constraintName,
503
            $default,
504
            $this->quoteColumnName($newColumn->getName())
505
        ));
506
    }
507
508
    /**
509
     * {@inheritdoc}
510
     */
511
    public function changeColumn($tableName, $columnName, Column $newColumn)
512
    {
513
        $columns = $this->getColumns($tableName);
514
        $changeDefault = $newColumn->getDefault() !== $columns[$columnName]->getDefault() || $newColumn->getType() !== $columns[$columnName]->getType();
515
        if ($columnName !== $newColumn->getName()) {
516
            $this->renameColumn($tableName, $columnName, $newColumn->getName());
517
        }
518
519
        if ($changeDefault) {
520
            $this->dropDefaultConstraint($tableName, $newColumn->getName());
521
        }
522
523
        $this->execute(
524
            sprintf(
525
                'ALTER TABLE %s ALTER COLUMN %s %s',
526
                $this->quoteTableName($tableName),
527
                $this->quoteColumnName($newColumn->getName()),
528
                $this->getColumnSqlDefinition($newColumn, false)
529
            )
530
        );
531
        // change column comment if needed
532
        if ($newColumn->getComment()) {
533
            $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName);
534
            $this->execute($sql);
535
        }
536
537
        if ($changeDefault) {
538
            $this->changeDefault($tableName, $newColumn);
539
        }
540
    }
541
542
    /**
543
     * {@inheritdoc}
544
     */
545
    public function dropColumn($tableName, $columnName)
546
    {
547
        $this->dropDefaultConstraint($tableName, $columnName);
548
549
        $this->execute(
550
            sprintf(
551
                'ALTER TABLE %s DROP COLUMN %s',
552
                $this->quoteTableName($tableName),
553
                $this->quoteColumnName($columnName)
554
            )
555
        );
556
    }
557
558
    protected function dropDefaultConstraint($tableName, $columnName)
559
    {
560
        $defaultConstraint = $this->getDefaultConstraint($tableName, $columnName);
561
562
        if (!$defaultConstraint) {
563
            return;
564
        }
565
566
        $this->dropForeignKey($tableName, $columnName, $defaultConstraint);
567
    }
568
569
    protected function getDefaultConstraint($tableName, $columnName)
570
    {
571
        $sql = "SELECT
572
    default_constraints.name
573
FROM
574
    sys.all_columns
575
576
        INNER JOIN
577
    sys.tables
578
        ON all_columns.object_id = tables.object_id
579
580
        INNER JOIN
581
    sys.schemas
582
        ON tables.schema_id = schemas.schema_id
583
584
        INNER JOIN
585
    sys.default_constraints
586
        ON all_columns.default_object_id = default_constraints.object_id
587
588
WHERE
589
        schemas.name = 'dbo'
590
    AND tables.name = '{$tableName}'
591
    AND all_columns.name = '{$columnName}'";
592
593
        $rows = $this->fetchAll($sql);
594
        return empty($rows) ? false : $rows[0]['name'];
595
    }
596
597
    protected function getIndexColums($tableId, $indexId)
598
    {
599
        $sql = "SELECT AC.[name] AS [column_name]
600
FROM sys.[index_columns] IC
601
  INNER JOIN sys.[all_columns] AC ON IC.[column_id] = AC.[column_id]
602
WHERE AC.[object_id] = {$tableId} AND IC.[index_id] = {$indexId}  AND IC.[object_id] = {$tableId}
603
ORDER BY IC.[key_ordinal];";
604
605
        $rows = $this->fetchAll($sql);
606
        $columns = [];
607
        foreach ($rows as $row) {
608
            $columns[] = strtolower($row['column_name']);
609
        }
610
        return $columns;
611
    }
612
613
    /**
614
     * Get an array of indexes from a particular table.
615
     *
616
     * @param string $tableName Table Name
617
     * @return array
618
     */
619
    public function getIndexes($tableName)
620
    {
621
        $indexes = [];
622
        $sql = "SELECT I.[name] AS [index_name], I.[index_id] as [index_id], T.[object_id] as [table_id]
623
FROM sys.[tables] AS T
624
  INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]
625
WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP'  AND T.[name] = '{$tableName}'
626
ORDER BY T.[name], I.[index_id];";
627
628
        $rows = $this->fetchAll($sql);
629
        foreach ($rows as $row) {
630
            $columns = $this->getIndexColums($row['table_id'], $row['index_id']);
631
            $indexes[$row['index_name']] = ['columns' => $columns];
632
        }
633
634
        return $indexes;
635
    }
636
637
    /**
638
     * {@inheritdoc}
639
     */
640 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...
641
    {
642
        if (is_string($columns)) {
643
            $columns = [$columns]; // str to array
644
        }
645
646
        $columns = array_map('strtolower', $columns);
647
        $indexes = $this->getIndexes($tableName);
648
649
        foreach ($indexes as $index) {
650
            $a = array_diff($columns, $index['columns']);
651
652
            if (empty($a)) {
653
                return true;
654
            }
655
        }
656
657
        return false;
658
    }
659
660
    /**
661
     * {@inheritdoc}
662
     */
663 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...
664
    {
665
        $indexes = $this->getIndexes($tableName);
666
667
        foreach ($indexes as $name => $index) {
668
            if ($name === $indexName) {
669
                 return true;
670
            }
671
        }
672
673
        return false;
674
    }
675
676
    /**
677
     * {@inheritdoc}
678
     */
679
    public function addIndex(Table $table, Index $index)
680
    {
681
        $sql = $this->getIndexSqlDefinition($index, $table->getName());
682
        $this->execute($sql);
683
    }
684
685
    /**
686
     * {@inheritdoc}
687
     */
688 View Code Duplication
    public function dropIndex($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...
689
    {
690
        if (is_string($columns)) {
691
            $columns = [$columns]; // str to array
692
        }
693
694
        $indexes = $this->getIndexes($tableName);
695
        $columns = array_map('strtolower', $columns);
696
697
        foreach ($indexes as $indexName => $index) {
698
            $a = array_diff($columns, $index['columns']);
699
            if (empty($a)) {
700
                $this->execute(
701
                    sprintf(
702
                        'DROP INDEX %s ON %s',
703
                        $this->quoteColumnName($indexName),
704
                        $this->quoteTableName($tableName)
705
                    )
706
                );
707
                return;
708
            }
709
        }
710
    }
711
712
    /**
713
     * {@inheritdoc}
714
     */
715 View Code Duplication
    public function dropIndexByName($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...
716
    {
717
        $indexes = $this->getIndexes($tableName);
718
719
        foreach ($indexes as $name => $index) {
720
            if ($name === $indexName) {
721
                $this->execute(
722
                    sprintf(
723
                        'DROP INDEX %s ON %s',
724
                        $this->quoteColumnName($indexName),
725
                        $this->quoteTableName($tableName)
726
                    )
727
                );
728
                return;
729
            }
730
        }
731
    }
732
733
    /**
734
     * {@inheritdoc}
735
     */
736 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...
737
    {
738
        if (is_string($columns)) {
739
            $columns = [$columns]; // str to array
740
        }
741
        $foreignKeys = $this->getForeignKeys($tableName);
742
        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...
743
            if (isset($foreignKeys[$constraint])) {
744
                return !empty($foreignKeys[$constraint]);
745
            }
746
            return false;
747
        } else {
748
            foreach ($foreignKeys as $key) {
749
                $a = array_diff($columns, $key['columns']);
750
                if (empty($a)) {
751
                    return true;
752
                }
753
            }
754
            return false;
755
        }
756
    }
757
758
    /**
759
     * Get an array of foreign keys from a particular table.
760
     *
761
     * @param string $tableName Table Name
762
     * @return array
763
     */
764 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...
765
    {
766
        $foreignKeys = [];
767
        $rows = $this->fetchAll(sprintf(
768
            "SELECT
769
                    tc.constraint_name,
770
                    tc.table_name, kcu.column_name,
771
                    ccu.table_name AS referenced_table_name,
772
                    ccu.column_name AS referenced_column_name
773
                FROM
774
                    information_schema.table_constraints AS tc
775
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
776
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
777
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s'
778
                ORDER BY kcu.ordinal_position",
779
            $tableName
780
        ));
781
        foreach ($rows as $row) {
782
            $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
783
            $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
784
            $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
785
            $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
786
        }
787
788
        return $foreignKeys;
789
    }
790
791
    /**
792
     * {@inheritdoc}
793
     */
794 View Code Duplication
    public function addForeignKey(Table $table, ForeignKey $foreignKey)
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...
795
    {
796
        $this->execute(
797
            sprintf(
798
                'ALTER TABLE %s ADD %s',
799
                $this->quoteTableName($table->getName()),
800
                $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
801
            )
802
        );
803
    }
804
805
    /**
806
     * {@inheritdoc}
807
     */
808 View Code Duplication
    public function dropForeignKey($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...
809
    {
810
        if (is_string($columns)) {
811
            $columns = [$columns]; // str to array
812
        }
813
814
        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...
815
            $this->execute(
816
                sprintf(
817
                    'ALTER TABLE %s DROP CONSTRAINT %s',
818
                    $this->quoteTableName($tableName),
819
                    $constraint
820
                )
821
            );
822
            return;
823
        } else {
824
            foreach ($columns as $column) {
825
                $rows = $this->fetchAll(sprintf(
826
                    "SELECT
827
                    tc.constraint_name,
828
                    tc.table_name, kcu.column_name,
829
                    ccu.table_name AS referenced_table_name,
830
                    ccu.column_name AS referenced_column_name
831
                FROM
832
                    information_schema.table_constraints AS tc
833
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
834
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
835
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' and ccu.column_name='%s'
836
                ORDER BY kcu.ordinal_position",
837
                    $tableName,
838
                    $column
839
                ));
840
                foreach ($rows as $row) {
841
                    $this->dropForeignKey($tableName, $columns, $row['constraint_name']);
842
                }
843
            }
844
        }
845
    }
846
847
    /**
848
     * {@inheritdoc}
849
     */
850
    public function getSqlType($type, $limit = null)
851
    {
852
        switch ($type) {
853
            case static::PHINX_TYPE_STRING:
854
                return ['name' => 'nvarchar', 'limit' => 255];
855
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
856
            case static::PHINX_TYPE_CHAR:
857
                return ['name' => 'nchar', 'limit' => 255];
858
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
859
            case static::PHINX_TYPE_TEXT:
860
                return ['name' => 'ntext'];
861
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
862
            case static::PHINX_TYPE_INTEGER:
863
                return ['name' => 'int'];
864
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
865
            case static::PHINX_TYPE_BIG_INTEGER:
866
                return ['name' => 'bigint'];
867
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
868
            case static::PHINX_TYPE_FLOAT:
869
                return ['name' => 'float'];
870
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
871
            case static::PHINX_TYPE_DECIMAL:
872
                return ['name' => 'decimal'];
873
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
874
            case static::PHINX_TYPE_DATETIME:
875
            case static::PHINX_TYPE_TIMESTAMP:
876
                return ['name' => 'datetime'];
877
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
878
            case static::PHINX_TYPE_TIME:
879
                return ['name' => 'time'];
880
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
881
            case static::PHINX_TYPE_DATE:
882
                return ['name' => 'date'];
883
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
884
            case static::PHINX_TYPE_BLOB:
885
            case static::PHINX_TYPE_BINARY:
886
                return ['name' => 'varbinary'];
887
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
888
            case static::PHINX_TYPE_BOOLEAN:
889
                return ['name' => 'bit'];
890
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
891
            case static::PHINX_TYPE_UUID:
892
                return ['name' => 'uniqueidentifier'];
893
            case static::PHINX_TYPE_FILESTREAM:
894
                return ['name' => 'varbinary', 'limit' => 'max'];
895
            // Geospatial database types
896
            case static::PHINX_TYPE_GEOMETRY:
897
            case static::PHINX_TYPE_POINT:
898
            case static::PHINX_TYPE_LINESTRING:
899
            case static::PHINX_TYPE_POLYGON:
900
                // SQL Server stores all spatial data using a single data type.
901
                // Specific types (point, polygon, etc) are set at insert time.
902
                return ['name' => 'geography'];
903
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
904
            default:
905
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
906
        }
907
    }
908
909
    /**
910
     * Returns Phinx type by SQL type
911
     *
912
     * @param $sqlTypeDef
913
     * @throws \RuntimeException
914
     * @internal param string $sqlType SQL type
915
     * @returns string Phinx type
916
     */
917
    public function getPhinxType($sqlType)
918
    {
919
        switch ($sqlType) {
920
            case 'nvarchar':
921
            case 'varchar':
922
                return static::PHINX_TYPE_STRING;
923
            case 'char':
924
            case 'nchar':
925
                return static::PHINX_TYPE_CHAR;
926
            case 'text':
927
            case 'ntext':
928
                return static::PHINX_TYPE_TEXT;
929
            case 'int':
930
            case 'integer':
931
                return static::PHINX_TYPE_INTEGER;
932
            case 'decimal':
933
            case 'numeric':
934
            case 'money':
935
                return static::PHINX_TYPE_DECIMAL;
936
            case 'bigint':
937
                return static::PHINX_TYPE_BIG_INTEGER;
938
            case 'real':
939
            case 'float':
940
                return static::PHINX_TYPE_FLOAT;
941
            case 'binary':
942
            case 'image':
943
            case 'varbinary':
944
                return static::PHINX_TYPE_BINARY;
945
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
946
            case 'time':
947
                return static::PHINX_TYPE_TIME;
948
            case 'date':
949
                return static::PHINX_TYPE_DATE;
950
            case 'datetime':
951
            case 'timestamp':
952
                return static::PHINX_TYPE_DATETIME;
953
            case 'bit':
954
                return static::PHINX_TYPE_BOOLEAN;
955
            case 'uniqueidentifier':
956
                return static::PHINX_TYPE_UUID;
957
            case 'filestream':
958
                return static::PHINX_TYPE_FILESTREAM;
959
            default:
960
                throw new \RuntimeException('The SqlServer type: "' . $sqlType . '" is not supported');
961
        }
962
    }
963
964
    /**
965
     * {@inheritdoc}
966
     */
967
    public function createDatabase($name, $options = [])
968
    {
969 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...
970
            $this->execute(sprintf('CREATE DATABASE [%s] COLLATE [%s]', $name, $options['collation']));
971
        } else {
972
            $this->execute(sprintf('CREATE DATABASE [%s]', $name));
973
        }
974
        $this->execute(sprintf('USE [%s]', $name));
975
    }
976
977
    /**
978
     * {@inheritdoc}
979
     */
980
    public function hasDatabase($name)
981
    {
982
        $result = $this->fetchRow(
983
            sprintf(
984
                'SELECT count(*) as [count] FROM master.dbo.sysdatabases WHERE [name] = \'%s\'',
985
                $name
986
            )
987
        );
988
989
        return $result['count'] > 0;
990
    }
991
992
    /**
993
     * {@inheritdoc}
994
     */
995
    public function dropDatabase($name)
996
    {
997
        $sql = <<<SQL
998
USE master;
999
IF EXISTS(select * from sys.databases where name=N'$name')
1000
ALTER DATABASE [$name] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
1001
DROP DATABASE [$name];
1002
SQL;
1003
        $this->execute($sql);
1004
    }
1005
1006
    /**
1007
     * Gets the SqlServer Column Definition for a Column object.
1008
     *
1009
     * @param Column $column Column
1010
     * @return string
1011
     */
1012
    protected function getColumnSqlDefinition(Column $column, $create = true)
1013
    {
1014
        $buffer = array();
1015
        $isCustomColumn = $column instanceof Table\CustomColumn;
1016
        if ($isCustomColumn) {
1017
            $buffer[] = $column->getType();
1018
        } else {
1019
            $buffer = [];
1020
1021
            $sqlType = $this->getSqlType($column->getType());
1022
            $buffer[] = strtoupper($sqlType['name']);
1023
            // integers cant have limits in SQlServer
1024
            $noLimits = [
1025
                'bigint',
1026
                'int',
1027
                'tinyint'
1028
            ];
1029
            if (!in_array($sqlType['name'], $noLimits) && ($column->getLimit() || isset($sqlType['limit']))) {
1030
                $buffer[] = sprintf('(%s)', $column->getLimit() ? $column->getLimit() : $sqlType['limit']);
1031
            }
1032 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...
1033
                $buffer[] = '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1034
            }
1035
1036
            $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';
1037
1038
            if ($create === true) {
1039
                if ($column->getDefault() === null && $column->isNull()) {
1040
                    $buffer[] = ' DEFAULT NULL';
1041
                } else {
1042
                    $buffer[] = $this->getDefaultValueDefinition($column->getDefault());
1043
                }
1044
            }
1045
1046
            if ($column->isIdentity()) {
1047
                $buffer[] = 'IDENTITY(1, 1)';
1048
            }
1049
        }
1050
1051
        return implode(' ', $buffer);
1052
    }
1053
1054
    /**
1055
     * Gets the SqlServer Index Definition for an Index object.
1056
     *
1057
     * @param Index $index Index
1058
     * @return string
1059
     */
1060
    protected function getIndexSqlDefinition(Index $index, $tableName)
1061
    {
1062 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...
1063
            $indexName = $index->getName();
1064
        } else {
1065
            $columnNames = $index->getColumns();
1066
            if (is_string($columnNames)) {
1067
                $columnNames = [$columnNames];
1068
            }
1069
            $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames));
1070
        }
1071
        $def = sprintf(
1072
            "CREATE %s INDEX %s ON %s (%s);",
1073
            ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
1074
            $indexName,
1075
            $this->quoteTableName($tableName),
1076
            '[' . implode('],[', $index->getColumns()) . ']'
1077
        );
1078
1079
        return $def;
1080
    }
1081
1082
    /**
1083
     * Gets the SqlServer Foreign Key Definition for an ForeignKey object.
1084
     *
1085
     * @param ForeignKey $foreignKey
1086
     * @return string
1087
     */
1088 View Code Duplication
    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $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...
1089
    {
1090
        $constraintName = $foreignKey->getConstraint()
1091
            ? $foreignKey->getConstraint()
1092
            : $tableName . '_' . implode('_', $foreignKey->getColumns());
1093
        $def = ' CONSTRAINT ' . $this->quoteColumnName($constraintName);
0 ignored issues
show
Bug introduced by
It seems like $constraintName defined by $foreignKey->getConstrai...reignKey->getColumns()) on line 1090 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...
1094
        $def .= ' FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")';
1095
        $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")';
1096
        if ($foreignKey->getOnDelete()) {
1097
            $def .= " ON DELETE {$foreignKey->getOnDelete()}";
1098
        }
1099
        if ($foreignKey->getOnUpdate()) {
1100
            $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
1101
        }
1102
1103
        return $def;
1104
    }
1105
1106
    /**
1107
     * {@inheritdoc}
1108
     */
1109
    public function getColumnTypes()
1110
    {
1111
        return array_merge(parent::getColumnTypes(), ['filestream']);
1112
    }
1113
1114
    /**
1115
     * Records a migration being run.
1116
     *
1117
     * @param \Phinx\Migration\MigrationInterface $migration Migration
1118
     * @param string $direction Direction
1119
     * @param int $startTime Start Time
1120
     * @param int $endTime End Time
1121
     * @return AdapterInterface
1122
     */
1123
    public function migrated(\Phinx\Migration\MigrationInterface $migration, $direction, $startTime, $endTime)
1124
    {
1125
        $startTime = str_replace(' ', 'T', $startTime);
1126
        $endTime = str_replace(' ', 'T', $endTime);
1127
        return parent::migrated($migration, $direction, $startTime, $endTime);
1128
    }
1129
}
1130