Completed
Pull Request — master (#1251)
by
unknown
01:42
created

SqlServerAdapter::getSqlType()   C

Complexity

Conditions 23
Paths 25

Size

Total Lines 55
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 552

Importance

Changes 0
Metric Value
dl 0
loc 55
ccs 0
cts 51
cp 0
rs 6.5378
c 0
b 0
f 0
cc 23
eloc 44
nc 25
nop 3
crap 552

How to fix   Long Method    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\ForeignKey;
34
use Phinx\Db\Table\Index;
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
57
                return;
58
            }
59
60
            $db = null;
61
            $options = $this->getOptions();
62
63
            // if port is specified use it, otherwise use the SqlServer default
64 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...
65
                $dsn = 'sqlsrv:server=' . $options['host'] . ';database=' . $options['name'];
66
            } else {
67
                $dsn = 'sqlsrv:server=' . $options['host'] . ',' . $options['port'] . ';database=' . $options['name'];
68
            }
69
            $dsn .= ';MultipleActiveResultSets=false';
70
71
            $driverOptions = [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION];
72
73
            // charset support
74
            if (isset($options['charset'])) {
75
                $driverOptions[\PDO::SQLSRV_ATTR_ENCODING] = $options['charset'];
76
            }
77
78
            // support arbitrary \PDO::SQLSRV_ATTR_* driver options and pass them to PDO
79
            // http://php.net/manual/en/ref.pdo-sqlsrv.php#pdo-sqlsrv.constants
80 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...
81
                if (strpos($key, 'sqlsrv_attr_') === 0) {
82
                    $driverOptions[constant('\PDO::' . strtoupper($key))] = $option;
83
                }
84
            }
85
86
            try {
87
                $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
88
            } catch (\PDOException $exception) {
89
                throw new \InvalidArgumentException(sprintf(
90
                    'There was a problem connecting to the database: %s',
91
                    $exception->getMessage()
92
                ));
93
            }
94
95
            $this->setConnection($db);
96
        }
97
    }
98
99
    /**
100
     * Connect to MSSQL using dblib/freetds.
101
     *
102
     * The "sqlsrv" driver is not available on Unix machines.
103
     *
104
     * @throws \InvalidArgumentException
105
     */
106
    protected function connectDblib()
107
    {
108
        if (!class_exists('PDO') || !in_array('dblib', \PDO::getAvailableDrivers(), true)) {
109
            // @codeCoverageIgnoreStart
110
            throw new \RuntimeException('You need to enable the PDO_Dblib extension for Phinx to run properly.');
111
            // @codeCoverageIgnoreEnd
112
        }
113
114
        $options = $this->getOptions();
115
116
        // if port is specified use it, otherwise use the SqlServer default
117 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...
118
            $dsn = 'dblib:host=' . $options['host'] . ';dbname=' . $options['name'];
119
        } else {
120
            $dsn = 'dblib:host=' . $options['host'] . ':' . $options['port'] . ';dbname=' . $options['name'];
121
        }
122
123
        $driverOptions = [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION];
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
200
        return $result['count'] > 0;
201
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206
    public function createTable(Table $table)
207
    {
208
        $options = $table->getOptions();
209
210
        // Add the default primary key
211
        $columns = $table->getPendingColumns();
212 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...
213
            $column = new Column();
214
            $column->setName('id')
215
                   ->setType('integer')
216
                   ->setIdentity(true);
217
218
            array_unshift($columns, $column);
219
            $options['primary_key'] = 'id';
220
        } elseif (isset($options['id']) && is_string($options['id'])) {
221
            // Handle id => "field_name" to support AUTO_INCREMENT
222
            $column = new Column();
223
            $column->setName($options['id'])
224
                   ->setType('integer')
225
                   ->setIdentity(true);
226
227
            array_unshift($columns, $column);
228
            $options['primary_key'] = $options['id'];
229
        }
230
231
        $sql = 'CREATE TABLE ';
232
        $sql .= $this->quoteTableName($table->getName()) . ' (';
233
        $sqlBuffer = [];
234
        $columnsWithComments = [];
235 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...
236
            $sqlBuffer[] = $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column);
237
238
            // set column comments, if needed
239
            if ($column->getComment()) {
240
                $columnsWithComments[] = $column;
241
            }
242
        }
243
244
        // set the primary key(s)
245
        if (isset($options['primary_key'])) {
246
            $pkSql = sprintf('CONSTRAINT PK_%s PRIMARY KEY (', $table->getName());
247
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
248
                $pkSql .= $this->quoteColumnName($options['primary_key']);
249
            } 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...
250
                $pkSql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
251
            }
252
            $pkSql .= ')';
253
            $sqlBuffer[] = $pkSql;
254
        }
255
256
        // set the foreign keys
257
        $foreignKeys = $table->getForeignKeys();
258
        foreach ($foreignKeys as $foreignKey) {
259
            $sqlBuffer[] = $this->getForeignKeySqlDefinition($foreignKey, $table->getName());
260
        }
261
262
        $sql .= implode(', ', $sqlBuffer);
263
        $sql .= ');';
264
265
        // process column comments
266
        foreach ($columnsWithComments as $column) {
267
            $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
268
        }
269
270
        // set the indexes
271
        $indexes = $table->getIndexes();
272
        foreach ($indexes as $index) {
273
            $sql .= $this->getIndexSqlDefinition($index, $table->getName());
274
        }
275
276
        // execute the sql
277
        $this->execute($sql);
278
    }
279
280
    /**
281
     * Gets the SqlServer Column Comment Defininition for a column object.
282
     *
283
     * @param \Phinx\Db\Table\Column $column    Column
284
     * @param string $tableName Table name
285
     *
286
     * @return string
287
     */
288
    protected function getColumnCommentSqlDefinition(Column $column, $tableName)
289
    {
290
        // passing 'null' is to remove column comment
291
        $currentComment = $this->getColumnComment($tableName, $column->getName());
292
293
        $comment = (strcasecmp($column->getComment(), 'NULL') !== 0) ? $this->getConnection()->quote($column->getComment()) : '\'\'';
294
        $command = $currentComment === false ? 'sp_addextendedproperty' : 'sp_updateextendedproperty';
295
296
        return sprintf(
297
            "EXECUTE %s N'MS_Description', N%s, N'SCHEMA', N'%s', N'TABLE', N'%s', N'COLUMN', N'%s';",
298
            $command,
299
            $comment,
300
            $this->schema,
301
            $tableName,
302
            $column->getName()
303
        );
304
    }
305
306
    /**
307
     * {@inheritdoc}
308
     */
309
    public function renameTable($tableName, $newTableName)
310
    {
311
        $this->execute(sprintf('EXEC sp_rename \'%s\', \'%s\'', $tableName, $newTableName));
312
    }
313
314
    /**
315
     * {@inheritdoc}
316
     */
317
    public function dropTable($tableName)
318
    {
319
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
320
    }
321
322
    /**
323
     * {@inheritdoc}
324
     */
325
    public function truncateTable($tableName)
326
    {
327
        $sql = sprintf(
328
            'TRUNCATE TABLE %s',
329
            $this->quoteTableName($tableName)
330
        );
331
332
        $this->execute($sql);
333
    }
334
335
    public function getColumnComment($tableName, $columnName)
336
    {
337
        $sql = sprintf("SELECT cast(extended_properties.[value] as nvarchar(4000)) comment
338
  FROM sys.schemas
339
 INNER JOIN sys.tables
340
    ON schemas.schema_id = tables.schema_id
341
 INNER JOIN sys.columns
342
    ON tables.object_id = columns.object_id
343
 INNER JOIN sys.extended_properties
344
    ON tables.object_id = extended_properties.major_id
345
   AND columns.column_id = extended_properties.minor_id
346
   AND extended_properties.name = 'MS_Description'
347
   WHERE schemas.[name] = '%s' AND tables.[name] = '%s' AND columns.[name] = '%s'", $this->schema, $tableName, $columnName);
348
        $row = $this->fetchRow($sql);
349
350
        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...
351
            return $row['comment'];
352
        }
353
354
        return false;
355
    }
356
357
    /**
358
     * {@inheritdoc}
359
     */
360
    public function getColumns($tableName)
361
    {
362
        $columns = [];
363
        $sql = sprintf(
364
            "SELECT DISTINCT TABLE_SCHEMA AS [schema], TABLE_NAME as [table_name], COLUMN_NAME AS [name], DATA_TYPE AS [type],
365
            IS_NULLABLE AS [null], COLUMN_DEFAULT AS [default],
366
            CHARACTER_MAXIMUM_LENGTH AS [char_length],
367
            NUMERIC_PRECISION AS [precision],
368
            NUMERIC_SCALE AS [scale], ORDINAL_POSITION AS [ordinal_position],
369
            COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as [identity]
370
        FROM INFORMATION_SCHEMA.COLUMNS
371
        WHERE TABLE_NAME = '%s'
372
        ORDER BY ordinal_position",
373
            $tableName
374
        );
375
        $rows = $this->fetchAll($sql);
376
        foreach ($rows as $columnInfo) {
377
            $column = new Column();
378
            $column->setName($columnInfo['name'])
379
                   ->setType($this->getPhinxType($columnInfo['type']))
380
                   ->setNull($columnInfo['null'] !== 'NO')
381
                   ->setDefault($this->parseDefault($columnInfo['default']))
382
                   ->setIdentity($columnInfo['identity'] === '1')
383
                   ->setComment($this->getColumnComment($columnInfo['table_name'], $columnInfo['name']));
384
385
            if (!empty($columnInfo['char_length'])) {
386
                $column->setLimit($columnInfo['char_length']);
387
            }
388
389
            $columns[$columnInfo['name']] = $column;
390
        }
391
392
        return $columns;
393
    }
394
395
    protected function parseDefault($default)
396
    {
397
        $default = preg_replace(["/\('(.*)'\)/", "/\(\((.*)\)\)/", "/\((.*)\)/"], '$1', $default);
398
399
        if (strtoupper($default) === 'NULL') {
400
            $default = null;
401
        } elseif (is_numeric($default)) {
402
            $default = (int)$default;
403
        }
404
405
        return $default;
406
    }
407
408
    /**
409
     * {@inheritdoc}
410
     */
411 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...
412
    {
413
        $sql = sprintf(
414
            "SELECT count(*) as [count]
415
             FROM information_schema.columns
416
             WHERE table_name = '%s' AND column_name = '%s'",
417
            $tableName,
418
            $columnName
419
        );
420
        $result = $this->fetchRow($sql);
421
422
        return $result['count'] > 0;
423
    }
424
425
    /**
426
     * {@inheritdoc}
427
     */
428 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...
429
    {
430
        $sql = sprintf(
431
            'ALTER TABLE %s ADD %s %s',
432
            $this->quoteTableName($table->getName()),
433
            $this->quoteColumnName($column->getName()),
434
            $this->getColumnSqlDefinition($column)
435
        );
436
437
        $this->execute($sql);
438
    }
439
440
    /**
441
     * {@inheritdoc}
442
     */
443
    public function renameColumn($tableName, $columnName, $newColumnName)
444
    {
445
        if (!$this->hasColumn($tableName, $columnName)) {
446
            throw new \InvalidArgumentException("The specified column does not exist: $columnName");
447
        }
448
        $this->renameDefault($tableName, $columnName, $newColumnName);
449
        $this->execute(
450
            sprintf(
451
                "EXECUTE sp_rename N'%s.%s', N'%s', 'COLUMN' ",
452
                $tableName,
453
                $columnName,
454
                $newColumnName
455
            )
456
        );
457
    }
458
459
    protected function renameDefault($tableName, $columnName, $newColumnName)
460
    {
461
        $oldConstraintName = "DF_{$tableName}_{$columnName}";
462
        $newConstraintName = "DF_{$tableName}_{$newColumnName}";
463
        $sql = <<<SQL
464
IF (OBJECT_ID('$oldConstraintName', 'D') IS NOT NULL)
465
BEGIN
466
     EXECUTE sp_rename N'%s', N'%s', N'OBJECT'
467
END
468
SQL;
469
        $this->execute(sprintf(
470
            $sql,
471
            $oldConstraintName,
472
            $newConstraintName
473
        ));
474
    }
475
476
    public function changeDefault($tableName, Column $newColumn)
477
    {
478
        $constraintName = "DF_{$tableName}_{$newColumn->getName()}";
479
        $default = $newColumn->getDefault();
480
481
        if ($default === null) {
482
            $default = 'DEFAULT NULL';
483
        } else {
484
            $default = $this->getDefaultValueDefinition($default);
485
        }
486
487
        if (empty($default)) {
488
            return;
489
        }
490
491
        $this->execute(sprintf(
492
            'ALTER TABLE %s ADD CONSTRAINT %s %s FOR %s',
493
            $this->quoteTableName($tableName),
494
            $constraintName,
495
            $default,
496
            $this->quoteColumnName($newColumn->getName())
497
        ));
498
    }
499
500
    /**
501
     * {@inheritdoc}
502
     */
503
    public function changeColumn($tableName, $columnName, Column $newColumn)
504
    {
505
        $columns = $this->getColumns($tableName);
506
        $changeDefault = $newColumn->getDefault() !== $columns[$columnName]->getDefault() || $newColumn->getType() !== $columns[$columnName]->getType();
507
        if ($columnName !== $newColumn->getName()) {
508
            $this->renameColumn($tableName, $columnName, $newColumn->getName());
509
        }
510
511
        if ($changeDefault) {
512
            $this->dropDefaultConstraint($tableName, $newColumn->getName());
513
        }
514
515
        $this->execute(
516
            sprintf(
517
                'ALTER TABLE %s ALTER COLUMN %s %s',
518
                $this->quoteTableName($tableName),
519
                $this->quoteColumnName($newColumn->getName()),
520
                $this->getColumnSqlDefinition($newColumn, false)
521
            )
522
        );
523
        // change column comment if needed
524
        if ($newColumn->getComment()) {
525
            $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName);
526
            $this->execute($sql);
527
        }
528
529
        if ($changeDefault) {
530
            $this->changeDefault($tableName, $newColumn);
531
        }
532
    }
533
534
    /**
535
     * {@inheritdoc}
536
     */
537
    public function dropColumn($tableName, $columnName)
538
    {
539
        $this->dropDefaultConstraint($tableName, $columnName);
540
541
        $this->execute(
542
            sprintf(
543
                'ALTER TABLE %s DROP COLUMN %s',
544
                $this->quoteTableName($tableName),
545
                $this->quoteColumnName($columnName)
546
            )
547
        );
548
    }
549
550
    protected function dropDefaultConstraint($tableName, $columnName)
551
    {
552
        $defaultConstraint = $this->getDefaultConstraint($tableName, $columnName);
553
554
        if (!$defaultConstraint) {
555
            return;
556
        }
557
558
        $this->dropForeignKey($tableName, $columnName, $defaultConstraint);
559
    }
560
561
    protected function getDefaultConstraint($tableName, $columnName)
562
    {
563
        $sql = "SELECT
564
    default_constraints.name
565
FROM
566
    sys.all_columns
567
568
        INNER JOIN
569
    sys.tables
570
        ON all_columns.object_id = tables.object_id
571
572
        INNER JOIN
573
    sys.schemas
574
        ON tables.schema_id = schemas.schema_id
575
576
        INNER JOIN
577
    sys.default_constraints
578
        ON all_columns.default_object_id = default_constraints.object_id
579
580
WHERE
581
        schemas.name = 'dbo'
582
    AND tables.name = '{$tableName}'
583
    AND all_columns.name = '{$columnName}'";
584
585
        $rows = $this->fetchAll($sql);
586
587
        return empty($rows) ? false : $rows[0]['name'];
588
    }
589
590
    protected function getIndexColums($tableId, $indexId)
591
    {
592
        $sql = "SELECT AC.[name] AS [column_name]
593
FROM sys.[index_columns] IC
594
  INNER JOIN sys.[all_columns] AC ON IC.[column_id] = AC.[column_id]
595
WHERE AC.[object_id] = {$tableId} AND IC.[index_id] = {$indexId}  AND IC.[object_id] = {$tableId}
596
ORDER BY IC.[key_ordinal];";
597
598
        $rows = $this->fetchAll($sql);
599
        $columns = [];
600
        foreach ($rows as $row) {
601
            $columns[] = strtolower($row['column_name']);
602
        }
603
604
        return $columns;
605
    }
606
607
    /**
608
     * Get an array of indexes from a particular table.
609
     *
610
     * @param string $tableName Table Name
611
     * @return array
612
     */
613
    public function getIndexes($tableName)
614
    {
615
        $indexes = [];
616
        $sql = "SELECT I.[name] AS [index_name], I.[index_id] as [index_id], T.[object_id] as [table_id]
617
FROM sys.[tables] AS T
618
  INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]
619
WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP'  AND T.[name] = '{$tableName}'
620
ORDER BY T.[name], I.[index_id];";
621
622
        $rows = $this->fetchAll($sql);
623
        foreach ($rows as $row) {
624
            $columns = $this->getIndexColums($row['table_id'], $row['index_id']);
625
            $indexes[$row['index_name']] = ['columns' => $columns];
626
        }
627
628
        return $indexes;
629
    }
630
631
    /**
632
     * {@inheritdoc}
633
     */
634 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...
635
    {
636
        if (is_string($columns)) {
637
            $columns = [$columns]; // str to array
638
        }
639
640
        $columns = array_map('strtolower', $columns);
641
        $indexes = $this->getIndexes($tableName);
642
643
        foreach ($indexes as $index) {
644
            $a = array_diff($columns, $index['columns']);
645
646
            if (empty($a)) {
647
                return true;
648
            }
649
        }
650
651
        return false;
652
    }
653
654
    /**
655
     * {@inheritdoc}
656
     */
657 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...
658
    {
659
        $indexes = $this->getIndexes($tableName);
660
661
        foreach ($indexes as $name => $index) {
662
            if ($name === $indexName) {
663
                 return true;
664
            }
665
        }
666
667
        return false;
668
    }
669
670
    /**
671
     * {@inheritdoc}
672
     */
673
    public function addIndex(Table $table, Index $index)
674
    {
675
        $sql = $this->getIndexSqlDefinition($index, $table->getName());
676
        $this->execute($sql);
677
    }
678
679
    /**
680
     * {@inheritdoc}
681
     */
682 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...
683
    {
684
        if (is_string($columns)) {
685
            $columns = [$columns]; // str to array
686
        }
687
688
        $indexes = $this->getIndexes($tableName);
689
        $columns = array_map('strtolower', $columns);
690
691
        foreach ($indexes as $indexName => $index) {
692
            $a = array_diff($columns, $index['columns']);
693
            if (empty($a)) {
694
                $this->execute(
695
                    sprintf(
696
                        'DROP INDEX %s ON %s',
697
                        $this->quoteColumnName($indexName),
698
                        $this->quoteTableName($tableName)
699
                    )
700
                );
701
702
                return;
703
            }
704
        }
705
    }
706
707
    /**
708
     * {@inheritdoc}
709
     */
710 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...
711
    {
712
        $indexes = $this->getIndexes($tableName);
713
714
        foreach ($indexes as $name => $index) {
715
            if ($name === $indexName) {
716
                $this->execute(
717
                    sprintf(
718
                        'DROP INDEX %s ON %s',
719
                        $this->quoteColumnName($indexName),
720
                        $this->quoteTableName($tableName)
721
                    )
722
                );
723
724
                return;
725
            }
726
        }
727
    }
728
729
    /**
730
     * {@inheritdoc}
731
     */
732 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...
733
    {
734
        if (is_string($columns)) {
735
            $columns = [$columns]; // str to array
736
        }
737
        $foreignKeys = $this->getForeignKeys($tableName);
738
        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...
739
            if (isset($foreignKeys[$constraint])) {
740
                return !empty($foreignKeys[$constraint]);
741
            }
742
743
            return false;
744
        } else {
745
            foreach ($foreignKeys as $key) {
746
                $a = array_diff($columns, $key['columns']);
747
                if (empty($a)) {
748
                    return true;
749
                }
750
            }
751
752
            return false;
753
        }
754
    }
755
756
    /**
757
     * Get an array of foreign keys from a particular table.
758
     *
759
     * @param string $tableName Table Name
760
     * @return array
761
     */
762 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...
763
    {
764
        $foreignKeys = [];
765
        $rows = $this->fetchAll(sprintf(
766
            "SELECT
767
                    tc.constraint_name,
768
                    tc.table_name, kcu.column_name,
769
                    ccu.table_name AS referenced_table_name,
770
                    ccu.column_name AS referenced_column_name
771
                FROM
772
                    information_schema.table_constraints AS tc
773
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
774
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
775
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s'
776
                ORDER BY kcu.ordinal_position",
777
            $tableName
778
        ));
779
        foreach ($rows as $row) {
780
            $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
781
            $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
782
            $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
783
            $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
784
        }
785
786
        return $foreignKeys;
787
    }
788
789
    /**
790
     * {@inheritdoc}
791
     */
792 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...
793
    {
794
        $this->execute(
795
            sprintf(
796
                'ALTER TABLE %s ADD %s',
797
                $this->quoteTableName($table->getName()),
798
                $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
799
            )
800
        );
801
    }
802
803
    /**
804
     * {@inheritdoc}
805
     */
806 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...
807
    {
808
        if (is_string($columns)) {
809
            $columns = [$columns]; // str to array
810
        }
811
812
        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...
813
            $this->execute(
814
                sprintf(
815
                    'ALTER TABLE %s DROP CONSTRAINT %s',
816
                    $this->quoteTableName($tableName),
817
                    $constraint
818
                )
819
            );
820
821
            return;
822
        } else {
823
            foreach ($columns as $column) {
824
                $rows = $this->fetchAll(sprintf(
825
                    "SELECT
826
                    tc.constraint_name,
827
                    tc.table_name, kcu.column_name,
828
                    ccu.table_name AS referenced_table_name,
829
                    ccu.column_name AS referenced_column_name
830
                FROM
831
                    information_schema.table_constraints AS tc
832
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
833
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
834
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' and ccu.column_name='%s'
835
                ORDER BY kcu.ordinal_position",
836
                    $tableName,
837
                    $column
838
                ));
839
                foreach ($rows as $row) {
840
                    $this->dropForeignKey($tableName, $columns, $row['constraint_name']);
841
                }
842
            }
843
        }
844
    }
845
846
    /**
847
     * {@inheritdoc}
848
     */
849
    public function getSqlType($type, $limit = null, $properties = null)
850
    {
851
        switch ($type) {
852
            case static::PHINX_TYPE_STRING:
853
                return ['name' => 'nvarchar', 'limit' => 255];
854
            case static::PHINX_TYPE_CHAR:
855
                return ['name' => 'nchar', 'limit' => 255];
856
            case static::PHINX_TYPE_TEXT:
857
                return ['name' => 'ntext'];
858
            case static::PHINX_TYPE_INTEGER:
859
                return ['name' => 'int'];
860
            case static::PHINX_TYPE_BIG_INTEGER:
861
                return ['name' => 'bigint'];
862
            case static::PHINX_TYPE_FLOAT:
863
                return ['name' => 'float'];
864
            case static::PHINX_TYPE_DECIMAL:
865
                return ['name' => 'decimal'];
866
            case static::PHINX_TYPE_DATETIME:
867
            case static::PHINX_TYPE_TIMESTAMP:
868
                if (isset($properties['sql_type'])) {
869
                    $type = ['name' => $properties['sql_type']];
870
871
                    if ($limit !== null) {
872
                        $type['limit'] = $limit;
873
                    }
874
875
                    return $type;
876
                }
877
878
                return ['name' => 'datetime'];
879
            case static::PHINX_TYPE_TIME:
880
                return ['name' => 'time'];
881
            case static::PHINX_TYPE_DATE:
882
                return ['name' => 'date'];
883
            case static::PHINX_TYPE_BLOB:
884
            case static::PHINX_TYPE_BINARY:
885
                return ['name' => 'varbinary'];
886
            case static::PHINX_TYPE_BOOLEAN:
887
                return ['name' => 'bit'];
888
            case static::PHINX_TYPE_UUID:
889
                return ['name' => 'uniqueidentifier'];
890
            case static::PHINX_TYPE_FILESTREAM:
891
                return ['name' => 'varbinary', 'limit' => 'max'];
892
            // Geospatial database types
893
            case static::PHINX_TYPE_GEOMETRY:
894
            case static::PHINX_TYPE_POINT:
895
            case static::PHINX_TYPE_LINESTRING:
896
            case static::PHINX_TYPE_POLYGON:
897
                // SQL Server stores all spatial data using a single data type.
898
                // Specific types (point, polygon, etc) are set at insert time.
899
                return ['name' => 'geography'];
900
            default:
901
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
902
        }
903
    }
904
905
    /**
906
     * Returns Phinx type by SQL type
907
     *
908
     * @param string $sqlType SQL Type definition
909
     * @throws \RuntimeException
910
     * @internal param string $sqlType SQL type
911
     * @returns string Phinx type
912
     */
913
    public function getPhinxType($sqlType)
914
    {
915
        switch ($sqlType) {
916
            case 'nvarchar':
917
            case 'varchar':
918
                return static::PHINX_TYPE_STRING;
919
            case 'char':
920
            case 'nchar':
921
                return static::PHINX_TYPE_CHAR;
922
            case 'text':
923
            case 'ntext':
924
                return static::PHINX_TYPE_TEXT;
925
            case 'int':
926
            case 'integer':
927
                return static::PHINX_TYPE_INTEGER;
928
            case 'decimal':
929
            case 'numeric':
930
            case 'money':
931
                return static::PHINX_TYPE_DECIMAL;
932
            case 'bigint':
933
                return static::PHINX_TYPE_BIG_INTEGER;
934
            case 'real':
935
            case 'float':
936
                return static::PHINX_TYPE_FLOAT;
937
            case 'binary':
938
            case 'image':
939
            case 'varbinary':
940
                return static::PHINX_TYPE_BINARY;
941
            case 'time':
942
                return static::PHINX_TYPE_TIME;
943
            case 'date':
944
                return static::PHINX_TYPE_DATE;
945
            case 'datetime':
946
            case 'datetime2':
947
            case 'timestamp':
948
                return static::PHINX_TYPE_DATETIME;
949
            case 'bit':
950
                return static::PHINX_TYPE_BOOLEAN;
951
            case 'uniqueidentifier':
952
                return static::PHINX_TYPE_UUID;
953
            case 'filestream':
954
                return static::PHINX_TYPE_FILESTREAM;
955
            default:
956
                throw new \RuntimeException('The SqlServer type: "' . $sqlType . '" is not supported');
957
        }
958
    }
959
960
    /**
961
     * {@inheritdoc}
962
     */
963
    public function createDatabase($name, $options = [])
964
    {
965 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...
966
            $this->execute(sprintf('CREATE DATABASE [%s] COLLATE [%s]', $name, $options['collation']));
967
        } else {
968
            $this->execute(sprintf('CREATE DATABASE [%s]', $name));
969
        }
970
        $this->execute(sprintf('USE [%s]', $name));
971
    }
972
973
    /**
974
     * {@inheritdoc}
975
     */
976
    public function hasDatabase($name)
977
    {
978
        $result = $this->fetchRow(
979
            sprintf(
980
                'SELECT count(*) as [count] FROM master.dbo.sysdatabases WHERE [name] = \'%s\'',
981
                $name
982
            )
983
        );
984
985
        return $result['count'] > 0;
986
    }
987
988
    /**
989
     * {@inheritdoc}
990
     */
991
    public function dropDatabase($name)
992
    {
993
        $sql = <<<SQL
994
USE master;
995
IF EXISTS(select * from sys.databases where name=N'$name')
996
ALTER DATABASE [$name] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
997
DROP DATABASE [$name];
998
SQL;
999
        $this->execute($sql);
1000
    }
1001
1002
    /**
1003
     * Get the defintion for a `DEFAULT` statement.
1004
     *
1005
     * @param  mixed $default
1006
     * @return string
1007
     */
1008 View Code Duplication
    protected function getDefaultValueDefinition($default)
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...
1009
    {
1010
        if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
1011
            $default = $this->getConnection()->quote($default);
1012
        } elseif (is_bool($default)) {
1013
            $default = $this->castToBool($default);
1014
        }
1015
1016
        return isset($default) ? ' DEFAULT ' . $default : '';
1017
    }
1018
1019
    /**
1020
     * Gets the SqlServer Column Definition for a Column object.
1021
     *
1022
     * @param \Phinx\Db\Table\Column $column Column
1023
     * @return string
1024
     */
1025
    protected function getColumnSqlDefinition(Column $column, $create = true)
1026
    {
1027
        $buffer = [];
1028
1029
        $sqlType = $this->getSqlType($column->getType(), $column->getLimit(), $column->getProperties());
1030
        $buffer[] = strtoupper($sqlType['name']);
1031
        // integers cant have limits in SQlServer
1032
        $noLimits = [
1033
            'bigint',
1034
            'int',
1035
            'tinyint'
1036
        ];
1037
        if (!in_array($sqlType['name'], $noLimits) && ($column->getLimit() || isset($sqlType['limit']))) {
1038
            $buffer[] = sprintf('(%s)', $column->getLimit() ?: $sqlType['limit']);
1039
        }
1040 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...
1041
            $buffer[] = '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1042
        }
1043
1044
        $properties = $column->getProperties();
1045
        $buffer[] = $column->getType() === 'filestream' ? 'FILESTREAM' : '';
1046
        $buffer[] = isset($properties['rowguidcol']) ? 'ROWGUIDCOL' : '';
1047
1048
        $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';
1049
1050
        if ($create === true) {
1051
            if ($column->getDefault() === null && $column->isNull()) {
1052
                $buffer[] = ' DEFAULT NULL';
1053
            } else {
1054
                $buffer[] = $this->getDefaultValueDefinition($column->getDefault());
1055
            }
1056
        }
1057
1058
        if ($column->isIdentity()) {
1059
            $buffer[] = 'IDENTITY(1, 1)';
1060
        }
1061
1062
        return implode(' ', $buffer);
1063
    }
1064
1065
    /**
1066
     * Gets the SqlServer Index Definition for an Index object.
1067
     *
1068
     * @param \Phinx\Db\Table\Index $index Index
1069
     * @return string
1070
     */
1071
    protected function getIndexSqlDefinition(Index $index, $tableName)
1072
    {
1073 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...
1074
            $indexName = $index->getName();
1075
        } else {
1076
            $columnNames = $index->getColumns();
1077
            if (is_string($columnNames)) {
1078
                $columnNames = [$columnNames];
1079
            }
1080
            $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames));
1081
        }
1082
        $def = sprintf(
1083
            "CREATE %s INDEX %s ON %s (%s);",
1084
            ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
1085
            $indexName,
1086
            $this->quoteTableName($tableName),
1087
            '[' . implode('],[', $index->getColumns()) . ']'
1088
        );
1089
1090
        return $def;
1091
    }
1092
1093
    /**
1094
     * Gets the SqlServer Foreign Key Definition for an ForeignKey object.
1095
     *
1096
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1097
     * @return string
1098
     */
1099 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...
1100
    {
1101
        $constraintName = $foreignKey->getConstraint() ?: $tableName . '_' . implode('_', $foreignKey->getColumns());
1102
        $def = ' CONSTRAINT ' . $this->quoteColumnName($constraintName);
0 ignored issues
show
Bug introduced by
It seems like $constraintName defined by $foreignKey->getConstrai...reignKey->getColumns()) on line 1101 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...
1103
        $def .= ' FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")';
1104
        $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")';
1105
        if ($foreignKey->getOnDelete()) {
1106
            $def .= " ON DELETE {$foreignKey->getOnDelete()}";
1107
        }
1108
        if ($foreignKey->getOnUpdate()) {
1109
            $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
1110
        }
1111
1112
        return $def;
1113
    }
1114
1115
    /**
1116
     * {@inheritdoc}
1117
     */
1118
    public function getColumnTypes()
1119
    {
1120
        return array_merge(parent::getColumnTypes(), ['filestream']);
1121
    }
1122
1123
    /**
1124
     * Records a migration being run.
1125
     *
1126
     * @param \Phinx\Migration\MigrationInterface $migration Migration
1127
     * @param string $direction Direction
1128
     * @param int $startTime Start Time
1129
     * @param int $endTime End Time
1130
     * @return \Phinx\Db\Adapter\AdapterInterface
1131
     */
1132
    public function migrated(\Phinx\Migration\MigrationInterface $migration, $direction, $startTime, $endTime)
1133
    {
1134
        $startTime = str_replace(' ', 'T', $startTime);
1135
        $endTime = str_replace(' ', 'T', $endTime);
1136
1137
        return parent::migrated($migration, $direction, $startTime, $endTime);
1138
    }
1139
}
1140