Completed
Pull Request — master (#1250)
by
unknown
01:57
created

SqlServerAdapter::connectDblib()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 30
Code Lines 16

Duplication

Lines 5
Ratio 16.67 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 5
loc 30
ccs 0
cts 19
cp 0
rs 8.439
c 0
b 0
f 0
cc 5
eloc 16
nc 5
nop 0
crap 30
1
<?php
2
/**
3
 * Phinx
4
 *
5
 * (The MIT license)
6
 * Copyright (c) 2015 Rob Morgan
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy
9
 * of this software and associated * documentation files (the "Software"), to
10
 * deal in the Software without restriction, including without limitation the
11
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12
 * sell copies of the Software, and to permit persons to whom the Software is
13
 * furnished to do so, subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24
 * IN THE SOFTWARE.
25
 *
26
 * @package    Phinx
27
 * @subpackage Phinx\Db\Adapter
28
 */
29
namespace Phinx\Db\Adapter;
30
31
use 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
    const INT_TINY = 255;
48
    const INT_SMALL = 65535;
49
    const INT_MEDIUM = 16777215;
50
    const INT_REGULAR = 4294967295;
51
    const INT_BIG = 18446744073709551615;
52
53
    /**
54
     * {@inheritdoc}
55
     */
56
    public function connect()
57
    {
58
        if ($this->connection === null) {
59
            if (!class_exists('PDO') || !in_array('sqlsrv', \PDO::getAvailableDrivers(), true)) {
60
                // try our connection via freetds (Mac/Linux)
61
                $this->connectDblib();
62
63
                return;
64
            }
65
66
            $db = null;
67
            $options = $this->getOptions();
68
69
            // if port is specified use it, otherwise use the SqlServer default
70 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...
71
                $dsn = 'sqlsrv:server=' . $options['host'] . ';database=' . $options['name'];
72
            } else {
73
                $dsn = 'sqlsrv:server=' . $options['host'] . ',' . $options['port'] . ';database=' . $options['name'];
74
            }
75
            $dsn .= ';MultipleActiveResultSets=false';
76
77
            $driverOptions = [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION];
78
79
            // charset support
80
            if (isset($options['charset'])) {
81
                $driverOptions[\PDO::SQLSRV_ATTR_ENCODING] = $options['charset'];
82
            }
83
84
            // support arbitrary \PDO::SQLSRV_ATTR_* driver options and pass them to PDO
85
            // http://php.net/manual/en/ref.pdo-sqlsrv.php#pdo-sqlsrv.constants
86 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...
87
                if (strpos($key, 'sqlsrv_attr_') === 0) {
88
                    $driverOptions[constant('\PDO::' . strtoupper($key))] = $option;
89
                }
90
            }
91
92
            try {
93
                $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
94
            } catch (\PDOException $exception) {
95
                throw new \InvalidArgumentException(sprintf(
96
                    'There was a problem connecting to the database: %s',
97
                    $exception->getMessage()
98
                ));
99
            }
100
101
            $this->setConnection($db);
102
        }
103
    }
104
105
    /**
106
     * Connect to MSSQL using dblib/freetds.
107
     *
108
     * The "sqlsrv" driver is not available on Unix machines.
109
     *
110
     * @throws \InvalidArgumentException
111
     */
112
    protected function connectDblib()
113
    {
114
        if (!class_exists('PDO') || !in_array('dblib', \PDO::getAvailableDrivers(), true)) {
115
            // @codeCoverageIgnoreStart
116
            throw new \RuntimeException('You need to enable the PDO_Dblib extension for Phinx to run properly.');
117
            // @codeCoverageIgnoreEnd
118
        }
119
120
        $options = $this->getOptions();
121
122
        // if port is specified use it, otherwise use the SqlServer default
123 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...
124
            $dsn = 'dblib:host=' . $options['host'] . ';dbname=' . $options['name'];
125
        } else {
126
            $dsn = 'dblib:host=' . $options['host'] . ':' . $options['port'] . ';dbname=' . $options['name'];
127
        }
128
129
        $driverOptions = [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION];
130
131
        try {
132
            $db = new \PDO($dsn, $options['user'], $options['pass'], $driverOptions);
133
        } catch (\PDOException $exception) {
134
            throw new \InvalidArgumentException(sprintf(
135
                'There was a problem connecting to the database: %s',
136
                $exception->getMessage()
137
            ));
138
        }
139
140
        $this->setConnection($db);
141
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146
    public function disconnect()
147
    {
148
        $this->connection = null;
149
    }
150
151
    /**
152
     * {@inheritdoc}
153
     */
154
    public function hasTransactions()
155
    {
156
        return true;
157
    }
158
159
    /**
160
     * {@inheritdoc}
161
     */
162
    public function beginTransaction()
163
    {
164
        $this->execute('BEGIN TRANSACTION');
165
    }
166
167
    /**
168
     * {@inheritdoc}
169
     */
170
    public function commitTransaction()
171
    {
172
        $this->execute('COMMIT TRANSACTION');
173
    }
174
175
    /**
176
     * {@inheritdoc}
177
     */
178
    public function rollbackTransaction()
179
    {
180
        $this->execute('ROLLBACK TRANSACTION');
181
    }
182
183
    /**
184
     * {@inheritdoc}
185
     */
186
    public function quoteTableName($tableName)
187
    {
188
        return str_replace('.', '].[', $this->quoteColumnName($tableName));
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194
    public function quoteColumnName($columnName)
195
    {
196
        return '[' . str_replace(']', '\]', $columnName) . ']';
197
    }
198
199
    /**
200
     * {@inheritdoc}
201
     */
202
    public function hasTable($tableName)
203
    {
204
        $result = $this->fetchRow(sprintf('SELECT count(*) as [count] FROM information_schema.tables WHERE table_name = \'%s\';', $tableName));
205
206
        return $result['count'] > 0;
207
    }
208
209
    /**
210
     * {@inheritdoc}
211
     */
212
    public function createTable(Table $table)
213
    {
214
        $options = $table->getOptions();
215
216
        // Add the default primary key
217
        $columns = $table->getPendingColumns();
218 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...
219
            $column = new Column();
220
            $column->setName('id')
221
                   ->setType('integer')
222
                   ->setIdentity(true);
223
224
            array_unshift($columns, $column);
225
            $options['primary_key'] = 'id';
226
        } elseif (isset($options['id']) && is_string($options['id'])) {
227
            // Handle id => "field_name" to support AUTO_INCREMENT
228
            $column = new Column();
229
            $column->setName($options['id'])
230
                   ->setType('integer')
231
                   ->setIdentity(true);
232
233
            array_unshift($columns, $column);
234
            $options['primary_key'] = $options['id'];
235
        }
236
237
        $sql = 'CREATE TABLE ';
238
        $sql .= $this->quoteTableName($table->getName()) . ' (';
239
        $sqlBuffer = [];
240
        $columnsWithComments = [];
241 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...
242
            $sqlBuffer[] = $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column);
243
244
            // set column comments, if needed
245
            if ($column->getComment()) {
246
                $columnsWithComments[] = $column;
247
            }
248
        }
249
250
        // set the primary key(s)
251
        if (isset($options['primary_key'])) {
252
            $pkSql = sprintf('CONSTRAINT PK_%s PRIMARY KEY (', $table->getName());
253
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
254
                $pkSql .= $this->quoteColumnName($options['primary_key']);
255
            } 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...
256
                $pkSql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
257
            }
258
            $pkSql .= ')';
259
            $sqlBuffer[] = $pkSql;
260
        }
261
262
        // set the foreign keys
263
        $foreignKeys = $table->getForeignKeys();
264
        foreach ($foreignKeys as $foreignKey) {
265
            $sqlBuffer[] = $this->getForeignKeySqlDefinition($foreignKey, $table->getName());
266
        }
267
268
        $sql .= implode(', ', $sqlBuffer);
269
        $sql .= ');';
270
271
        // process column comments
272
        foreach ($columnsWithComments as $column) {
273
            $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
274
        }
275
276
        // set the indexes
277
        $indexes = $table->getIndexes();
278
        foreach ($indexes as $index) {
279
            $sql .= $this->getIndexSqlDefinition($index, $table->getName());
280
        }
281
282
        // execute the sql
283
        $this->execute($sql);
284
    }
285
286
    /**
287
     * Gets the SqlServer Column Comment Defininition for a column object.
288
     *
289
     * @param \Phinx\Db\Table\Column $column    Column
290
     * @param string $tableName Table name
291
     *
292
     * @return string
293
     */
294
    protected function getColumnCommentSqlDefinition(Column $column, $tableName)
295
    {
296
        // passing 'null' is to remove column comment
297
        $currentComment = $this->getColumnComment($tableName, $column->getName());
298
299
        $comment = (strcasecmp($column->getComment(), 'NULL') !== 0) ? $this->getConnection()->quote($column->getComment()) : '\'\'';
300
        $command = $currentComment === false ? 'sp_addextendedproperty' : 'sp_updateextendedproperty';
301
302
        return sprintf(
303
            "EXECUTE %s N'MS_Description', N%s, N'SCHEMA', N'%s', N'TABLE', N'%s', N'COLUMN', N'%s';",
304
            $command,
305
            $comment,
306
            $this->schema,
307
            $tableName,
308
            $column->getName()
309
        );
310
    }
311
312
    /**
313
     * {@inheritdoc}
314
     */
315
    public function renameTable($tableName, $newTableName)
316
    {
317
        $this->execute(sprintf('EXEC sp_rename \'%s\', \'%s\'', $tableName, $newTableName));
318
    }
319
320
    /**
321
     * {@inheritdoc}
322
     */
323
    public function dropTable($tableName)
324
    {
325
        $this->execute(sprintf('DROP TABLE %s', $this->quoteTableName($tableName)));
326
    }
327
328
    /**
329
     * {@inheritdoc}
330
     */
331
    public function truncateTable($tableName)
332
    {
333
        $sql = sprintf(
334
            'TRUNCATE TABLE %s',
335
            $this->quoteTableName($tableName)
336
        );
337
338
        $this->execute($sql);
339
    }
340
341
    public function getColumnComment($tableName, $columnName)
342
    {
343
        $sql = sprintf("SELECT cast(extended_properties.[value] as nvarchar(4000)) comment
344
  FROM sys.schemas
345
 INNER JOIN sys.tables
346
    ON schemas.schema_id = tables.schema_id
347
 INNER JOIN sys.columns
348
    ON tables.object_id = columns.object_id
349
 INNER JOIN sys.extended_properties
350
    ON tables.object_id = extended_properties.major_id
351
   AND columns.column_id = extended_properties.minor_id
352
   AND extended_properties.name = 'MS_Description'
353
   WHERE schemas.[name] = '%s' AND tables.[name] = '%s' AND columns.[name] = '%s'", $this->schema, $tableName, $columnName);
354
        $row = $this->fetchRow($sql);
355
356
        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...
357
            return $row['comment'];
358
        }
359
360
        return false;
361
    }
362
363
    /**
364
     * {@inheritdoc}
365
     */
366
    public function getColumns($tableName)
367
    {
368
        $columns = [];
369
        $sql = sprintf(
370
            "SELECT DISTINCT TABLE_SCHEMA AS [schema], TABLE_NAME as [table_name], COLUMN_NAME AS [name], DATA_TYPE AS [type],
371
            IS_NULLABLE AS [null], COLUMN_DEFAULT AS [default],
372
            CHARACTER_MAXIMUM_LENGTH AS [char_length],
373
            NUMERIC_PRECISION AS [precision],
374
            NUMERIC_SCALE AS [scale], ORDINAL_POSITION AS [ordinal_position],
375
            COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as [identity]
376
        FROM INFORMATION_SCHEMA.COLUMNS
377
        WHERE TABLE_NAME = '%s'
378
        ORDER BY ordinal_position",
379
            $tableName
380
        );
381
        $rows = $this->fetchAll($sql);
382
        foreach ($rows as $columnInfo) {
383
            $column = new Column();
384
            $column->setName($columnInfo['name'])
385
                   ->setType($this->getPhinxType($columnInfo['type']))
386
                   ->setNull($columnInfo['null'] !== 'NO')
387
                   ->setDefault($this->parseDefault($columnInfo['default']))
388
                   ->setIdentity($columnInfo['identity'] === '1')
389
                   ->setComment($this->getColumnComment($columnInfo['table_name'], $columnInfo['name']));
390
391
            if (!empty($columnInfo['char_length'])) {
392
                $column->setLimit($columnInfo['char_length']);
393
            }
394
395
            $columns[$columnInfo['name']] = $column;
396
        }
397
398
        return $columns;
399
    }
400
401
    protected function parseDefault($default)
402
    {
403
        $default = preg_replace(["/\('(.*)'\)/", "/\(\((.*)\)\)/", "/\((.*)\)/"], '$1', $default);
404
405
        if (strtoupper($default) === 'NULL') {
406
            $default = null;
407
        } elseif (is_numeric($default)) {
408
            $default = (int)$default;
409
        }
410
411
        return $default;
412
    }
413
414
    /**
415
     * {@inheritdoc}
416
     */
417 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...
418
    {
419
        $sql = sprintf(
420
            "SELECT count(*) as [count]
421
             FROM information_schema.columns
422
             WHERE table_name = '%s' AND column_name = '%s'",
423
            $tableName,
424
            $columnName
425
        );
426
        $result = $this->fetchRow($sql);
427
428
        return $result['count'] > 0;
429
    }
430
431
    /**
432
     * {@inheritdoc}
433
     */
434 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...
435
    {
436
        $sql = sprintf(
437
            'ALTER TABLE %s ADD %s %s',
438
            $this->quoteTableName($table->getName()),
439
            $this->quoteColumnName($column->getName()),
440
            $this->getColumnSqlDefinition($column)
441
        );
442
443
        $this->execute($sql);
444
    }
445
446
    /**
447
     * {@inheritdoc}
448
     */
449
    public function renameColumn($tableName, $columnName, $newColumnName)
450
    {
451
        if (!$this->hasColumn($tableName, $columnName)) {
452
            throw new \InvalidArgumentException("The specified column does not exist: $columnName");
453
        }
454
        $this->renameDefault($tableName, $columnName, $newColumnName);
455
        $this->execute(
456
            sprintf(
457
                "EXECUTE sp_rename N'%s.%s', N'%s', 'COLUMN' ",
458
                $tableName,
459
                $columnName,
460
                $newColumnName
461
            )
462
        );
463
    }
464
465
    protected function renameDefault($tableName, $columnName, $newColumnName)
466
    {
467
        $oldConstraintName = "DF_{$tableName}_{$columnName}";
468
        $newConstraintName = "DF_{$tableName}_{$newColumnName}";
469
        $sql = <<<SQL
470
IF (OBJECT_ID('$oldConstraintName', 'D') IS NOT NULL)
471
BEGIN
472
     EXECUTE sp_rename N'%s', N'%s', N'OBJECT'
473
END
474
SQL;
475
        $this->execute(sprintf(
476
            $sql,
477
            $oldConstraintName,
478
            $newConstraintName
479
        ));
480
    }
481
482
    public function changeDefault($tableName, Column $newColumn)
483
    {
484
        $constraintName = "DF_{$tableName}_{$newColumn->getName()}";
485
        $default = $newColumn->getDefault();
486
487
        if ($default === null) {
488
            $default = 'DEFAULT NULL';
489
        } else {
490
            $default = $this->getDefaultValueDefinition($default);
491
        }
492
493
        if (empty($default)) {
494
            return;
495
        }
496
497
        $this->execute(sprintf(
498
            'ALTER TABLE %s ADD CONSTRAINT %s %s FOR %s',
499
            $this->quoteTableName($tableName),
500
            $constraintName,
501
            $default,
502
            $this->quoteColumnName($newColumn->getName())
503
        ));
504
    }
505
506
    /**
507
     * {@inheritdoc}
508
     */
509
    public function changeColumn($tableName, $columnName, Column $newColumn)
510
    {
511
        $columns = $this->getColumns($tableName);
512
        $changeDefault = $newColumn->getDefault() !== $columns[$columnName]->getDefault() || $newColumn->getType() !== $columns[$columnName]->getType();
513
        if ($columnName !== $newColumn->getName()) {
514
            $this->renameColumn($tableName, $columnName, $newColumn->getName());
515
        }
516
517
        if ($changeDefault) {
518
            $this->dropDefaultConstraint($tableName, $newColumn->getName());
519
        }
520
521
        $this->execute(
522
            sprintf(
523
                'ALTER TABLE %s ALTER COLUMN %s %s',
524
                $this->quoteTableName($tableName),
525
                $this->quoteColumnName($newColumn->getName()),
526
                $this->getColumnSqlDefinition($newColumn, false)
527
            )
528
        );
529
        // change column comment if needed
530
        if ($newColumn->getComment()) {
531
            $sql = $this->getColumnCommentSqlDefinition($newColumn, $tableName);
532
            $this->execute($sql);
533
        }
534
535
        if ($changeDefault) {
536
            $this->changeDefault($tableName, $newColumn);
537
        }
538
    }
539
540
    /**
541
     * {@inheritdoc}
542
     */
543
    public function dropColumn($tableName, $columnName)
544
    {
545
        $this->dropDefaultConstraint($tableName, $columnName);
546
547
        $this->execute(
548
            sprintf(
549
                'ALTER TABLE %s DROP COLUMN %s',
550
                $this->quoteTableName($tableName),
551
                $this->quoteColumnName($columnName)
552
            )
553
        );
554
    }
555
556
    protected function dropDefaultConstraint($tableName, $columnName)
557
    {
558
        $defaultConstraint = $this->getDefaultConstraint($tableName, $columnName);
559
560
        if (!$defaultConstraint) {
561
            return;
562
        }
563
564
        $this->dropForeignKey($tableName, $columnName, $defaultConstraint);
565
    }
566
567
    protected function getDefaultConstraint($tableName, $columnName)
568
    {
569
        $sql = "SELECT
570
    default_constraints.name
571
FROM
572
    sys.all_columns
573
574
        INNER JOIN
575
    sys.tables
576
        ON all_columns.object_id = tables.object_id
577
578
        INNER JOIN
579
    sys.schemas
580
        ON tables.schema_id = schemas.schema_id
581
582
        INNER JOIN
583
    sys.default_constraints
584
        ON all_columns.default_object_id = default_constraints.object_id
585
586
WHERE
587
        schemas.name = 'dbo'
588
    AND tables.name = '{$tableName}'
589
    AND all_columns.name = '{$columnName}'";
590
591
        $rows = $this->fetchAll($sql);
592
593
        return empty($rows) ? false : $rows[0]['name'];
594
    }
595
596
    protected function getIndexColums($tableId, $indexId)
597
    {
598
        $sql = "SELECT AC.[name] AS [column_name]
599
FROM sys.[index_columns] IC
600
  INNER JOIN sys.[all_columns] AC ON IC.[column_id] = AC.[column_id]
601
WHERE AC.[object_id] = {$tableId} AND IC.[index_id] = {$indexId}  AND IC.[object_id] = {$tableId}
602
ORDER BY IC.[key_ordinal];";
603
604
        $rows = $this->fetchAll($sql);
605
        $columns = [];
606
        foreach ($rows as $row) {
607
            $columns[] = strtolower($row['column_name']);
608
        }
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
708
                return;
709
            }
710
        }
711
    }
712
713
    /**
714
     * {@inheritdoc}
715
     */
716 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...
717
    {
718
        $indexes = $this->getIndexes($tableName);
719
720
        foreach ($indexes as $name => $index) {
721
            if ($name === $indexName) {
722
                $this->execute(
723
                    sprintf(
724
                        'DROP INDEX %s ON %s',
725
                        $this->quoteColumnName($indexName),
726
                        $this->quoteTableName($tableName)
727
                    )
728
                );
729
730
                return;
731
            }
732
        }
733
    }
734
735
    /**
736
     * {@inheritdoc}
737
     */
738 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...
739
    {
740
        if (is_string($columns)) {
741
            $columns = [$columns]; // str to array
742
        }
743
        $foreignKeys = $this->getForeignKeys($tableName);
744
        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...
745
            if (isset($foreignKeys[$constraint])) {
746
                return !empty($foreignKeys[$constraint]);
747
            }
748
749
            return false;
750
        } else {
751
            foreach ($foreignKeys as $key) {
752
                $a = array_diff($columns, $key['columns']);
753
                if (empty($a)) {
754
                    return true;
755
                }
756
            }
757
758
            return false;
759
        }
760
    }
761
762
    /**
763
     * Get an array of foreign keys from a particular table.
764
     *
765
     * @param string $tableName Table Name
766
     * @return array
767
     */
768 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...
769
    {
770
        $foreignKeys = [];
771
        $rows = $this->fetchAll(sprintf(
772
            "SELECT
773
                    tc.constraint_name,
774
                    tc.table_name, kcu.column_name,
775
                    ccu.table_name AS referenced_table_name,
776
                    ccu.column_name AS referenced_column_name
777
                FROM
778
                    information_schema.table_constraints AS tc
779
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
780
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
781
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s'
782
                ORDER BY kcu.ordinal_position",
783
            $tableName
784
        ));
785
        foreach ($rows as $row) {
786
            $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
787
            $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
788
            $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
789
            $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
790
        }
791
792
        return $foreignKeys;
793
    }
794
795
    /**
796
     * {@inheritdoc}
797
     */
798 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...
799
    {
800
        $this->execute(
801
            sprintf(
802
                'ALTER TABLE %s ADD %s',
803
                $this->quoteTableName($table->getName()),
804
                $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
805
            )
806
        );
807
    }
808
809
    /**
810
     * {@inheritdoc}
811
     */
812 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...
813
    {
814
        if (is_string($columns)) {
815
            $columns = [$columns]; // str to array
816
        }
817
818
        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...
819
            $this->execute(
820
                sprintf(
821
                    'ALTER TABLE %s DROP CONSTRAINT %s',
822
                    $this->quoteTableName($tableName),
823
                    $constraint
824
                )
825
            );
826
827
            return;
828
        } else {
829
            foreach ($columns as $column) {
830
                $rows = $this->fetchAll(sprintf(
831
                    "SELECT
832
                    tc.constraint_name,
833
                    tc.table_name, kcu.column_name,
834
                    ccu.table_name AS referenced_table_name,
835
                    ccu.column_name AS referenced_column_name
836
                FROM
837
                    information_schema.table_constraints AS tc
838
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
839
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
840
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' and ccu.column_name='%s'
841
                ORDER BY kcu.ordinal_position",
842
                    $tableName,
843
                    $column
844
                ));
845
                foreach ($rows as $row) {
846
                    $this->dropForeignKey($tableName, $columns, $row['constraint_name']);
847
                }
848
            }
849
        }
850
    }
851
852
    /**
853
     * {@inheritdoc}
854
     */
855
    public function getSqlType($type, $limit = null)
856
    {
857
        switch ($type) {
858
            case static::PHINX_TYPE_STRING:
859
                return ['name' => 'nvarchar', 'limit' => 255];
860
            case static::PHINX_TYPE_CHAR:
861
                return ['name' => 'nchar', 'limit' => 255];
862
            case static::PHINX_TYPE_TEXT:
863
                return ['name' => 'ntext'];
864
            case static::PHINX_TYPE_INTEGER:
865
                if ($limit && $limit >= static::INT_TINY) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. 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 integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
866
                    $sizes = [
867
                        // Order matters! Size must always be tested from longest to shortest!
868
                        'bigint' => static::INT_BIG,
869
                        'int' => static::INT_REGULAR,
870
                        'mediumint' => static::INT_MEDIUM,
871
                        'smallint' => static::INT_SMALL,
872
                        'tinyint' => static::INT_TINY,
873
                    ];
874
                    foreach ($sizes as $name => $length) {
875
                        if ($limit >= $length) {
876
                            return ['name' => $name];
877
                        }
878
                    }
879
                }
880
881
                return ['name' => 'int'];
882
            case static::PHINX_TYPE_BIG_INTEGER:
883
                return ['name' => 'bigint'];
884
            case static::PHINX_TYPE_FLOAT:
885
                return ['name' => 'float'];
886
            case static::PHINX_TYPE_DECIMAL:
887
                return ['name' => 'decimal'];
888
            case static::PHINX_TYPE_DATETIME:
889
            case static::PHINX_TYPE_TIMESTAMP:
890
                return ['name' => 'datetime'];
891
            case static::PHINX_TYPE_TIME:
892
                return ['name' => 'time'];
893
            case static::PHINX_TYPE_DATE:
894
                return ['name' => 'date'];
895
            case static::PHINX_TYPE_BLOB:
896
            case static::PHINX_TYPE_BINARY:
897
                return ['name' => 'varbinary'];
898
            case static::PHINX_TYPE_BOOLEAN:
899
                return ['name' => 'bit'];
900
            case static::PHINX_TYPE_UUID:
901
                return ['name' => 'uniqueidentifier'];
902
            case static::PHINX_TYPE_FILESTREAM:
903
                return ['name' => 'varbinary', 'limit' => 'max'];
904
            // Geospatial database types
905
            case static::PHINX_TYPE_GEOMETRY:
906
            case static::PHINX_TYPE_POINT:
907
            case static::PHINX_TYPE_LINESTRING:
908
            case static::PHINX_TYPE_POLYGON:
909
                // SQL Server stores all spatial data using a single data type.
910
                // Specific types (point, polygon, etc) are set at insert time.
911
                return ['name' => 'geography'];
912
            default:
913
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
914
        }
915
    }
916
917
    /**
918
     * Returns Phinx type by SQL type
919
     *
920
     * @param string $sqlType SQL Type definition
921
     * @throws \RuntimeException
922
     * @internal param string $sqlType SQL type
923
     * @returns string Phinx type
924
     */
925
    public function getPhinxType($sqlType)
926
    {
927
        switch ($sqlType) {
928
            case 'nvarchar':
929
            case 'varchar':
930
                return static::PHINX_TYPE_STRING;
931
            case 'char':
932
            case 'nchar':
933
                return static::PHINX_TYPE_CHAR;
934
            case 'text':
935
            case 'ntext':
936
                return static::PHINX_TYPE_TEXT;
937
            case 'int':
938
            case 'integer':
939
                return static::PHINX_TYPE_INTEGER;
940
            case 'decimal':
941
            case 'numeric':
942
            case 'money':
943
                return static::PHINX_TYPE_DECIMAL;
944
            case 'bigint':
945
                return static::PHINX_TYPE_BIG_INTEGER;
946
            case 'real':
947
            case 'float':
948
                return static::PHINX_TYPE_FLOAT;
949
            case 'binary':
950
            case 'image':
951
            case 'varbinary':
952
                return static::PHINX_TYPE_BINARY;
953
            case 'time':
954
                return static::PHINX_TYPE_TIME;
955
            case 'date':
956
                return static::PHINX_TYPE_DATE;
957
            case 'datetime':
958
            case 'timestamp':
959
                return static::PHINX_TYPE_DATETIME;
960
            case 'bit':
961
                return static::PHINX_TYPE_BOOLEAN;
962
            case 'uniqueidentifier':
963
                return static::PHINX_TYPE_UUID;
964
            case 'filestream':
965
                return static::PHINX_TYPE_FILESTREAM;
966
            default:
967
                throw new \RuntimeException('The SqlServer type: "' . $sqlType . '" is not supported');
968
        }
969
    }
970
971
    /**
972
     * {@inheritdoc}
973
     */
974
    public function createDatabase($name, $options = [])
975
    {
976 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...
977
            $this->execute(sprintf('CREATE DATABASE [%s] COLLATE [%s]', $name, $options['collation']));
978
        } else {
979
            $this->execute(sprintf('CREATE DATABASE [%s]', $name));
980
        }
981
        $this->execute(sprintf('USE [%s]', $name));
982
    }
983
984
    /**
985
     * {@inheritdoc}
986
     */
987
    public function hasDatabase($name)
988
    {
989
        $result = $this->fetchRow(
990
            sprintf(
991
                'SELECT count(*) as [count] FROM master.dbo.sysdatabases WHERE [name] = \'%s\'',
992
                $name
993
            )
994
        );
995
996
        return $result['count'] > 0;
997
    }
998
999
    /**
1000
     * {@inheritdoc}
1001
     */
1002
    public function dropDatabase($name)
1003
    {
1004
        $sql = <<<SQL
1005
USE master;
1006
IF EXISTS(select * from sys.databases where name=N'$name')
1007
ALTER DATABASE [$name] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
1008
DROP DATABASE [$name];
1009
SQL;
1010
        $this->execute($sql);
1011
    }
1012
1013
    /**
1014
     * Get the defintion for a `DEFAULT` statement.
1015
     *
1016
     * @param  mixed $default
1017
     * @return string
1018
     */
1019 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...
1020
    {
1021
        if (is_string($default) && 'CURRENT_TIMESTAMP' !== $default) {
1022
            $default = $this->getConnection()->quote($default);
1023
        } elseif (is_bool($default)) {
1024
            $default = $this->castToBool($default);
1025
        }
1026
1027
        return isset($default) ? ' DEFAULT ' . $default : '';
1028
    }
1029
1030
    /**
1031
     * Gets the SqlServer Column Definition for a Column object.
1032
     *
1033
     * @param \Phinx\Db\Table\Column $column Column
1034
     * @return string
1035
     */
1036
    protected function getColumnSqlDefinition(Column $column, $create = true)
1037
    {
1038
        $buffer = [];
1039
1040
        $sqlType = $this->getSqlType($column->getType(), $column->getLimit());
1041
        $buffer[] = strtoupper($sqlType['name']);
1042
        // integers cant have limits in SQlServer
1043
        $noLimits = [
1044
            'bigint',
1045
            'int',
1046
            'smallint',
1047
            'tinyint'
1048
        ];
1049
        if (!in_array($sqlType['name'], $noLimits) && ($column->getLimit() || isset($sqlType['limit']))) {
1050
            $buffer[] = sprintf('(%s)', $column->getLimit() ?: $sqlType['limit']);
1051
        }
1052 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...
1053
            $buffer[] = '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1054
        }
1055
1056
        $properties = $column->getProperties();
1057
        $buffer[] = $column->getType() === 'filestream' ? 'FILESTREAM' : '';
1058
        $buffer[] = isset($properties['rowguidcol']) ? 'ROWGUIDCOL' : '';
1059
1060
        $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';
1061
1062
        if ($create === true) {
1063
            if ($column->getDefault() === null && $column->isNull()) {
1064
                $buffer[] = ' DEFAULT NULL';
1065
            } else {
1066
                $buffer[] = $this->getDefaultValueDefinition($column->getDefault());
1067
            }
1068
        }
1069
1070
        if ($column->isIdentity()) {
1071
            $buffer[] = 'IDENTITY(1, 1)';
1072
        }
1073
1074
        return implode(' ', $buffer);
1075
    }
1076
1077
    /**
1078
     * Gets the SqlServer Index Definition for an Index object.
1079
     *
1080
     * @param \Phinx\Db\Table\Index $index Index
1081
     * @return string
1082
     */
1083
    protected function getIndexSqlDefinition(Index $index, $tableName)
1084
    {
1085 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...
1086
            $indexName = $index->getName();
1087
        } else {
1088
            $columnNames = $index->getColumns();
1089
            if (is_string($columnNames)) {
1090
                $columnNames = [$columnNames];
1091
            }
1092
            $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames));
1093
        }
1094
        $def = sprintf(
1095
            "CREATE %s INDEX %s ON %s (%s);",
1096
            ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
1097
            $indexName,
1098
            $this->quoteTableName($tableName),
1099
            '[' . implode('],[', $index->getColumns()) . ']'
1100
        );
1101
1102
        return $def;
1103
    }
1104
1105
    /**
1106
     * Gets the SqlServer Foreign Key Definition for an ForeignKey object.
1107
     *
1108
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1109
     * @return string
1110
     */
1111 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...
1112
    {
1113
        $constraintName = $foreignKey->getConstraint() ?: $tableName . '_' . implode('_', $foreignKey->getColumns());
1114
        $def = ' CONSTRAINT ' . $this->quoteColumnName($constraintName);
0 ignored issues
show
Bug introduced by
It seems like $constraintName defined by $foreignKey->getConstrai...reignKey->getColumns()) on line 1113 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...
1115
        $def .= ' FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")';
1116
        $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")';
1117
        if ($foreignKey->getOnDelete()) {
1118
            $def .= " ON DELETE {$foreignKey->getOnDelete()}";
1119
        }
1120
        if ($foreignKey->getOnUpdate()) {
1121
            $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
1122
        }
1123
1124
        return $def;
1125
    }
1126
1127
    /**
1128
     * {@inheritdoc}
1129
     */
1130
    public function getColumnTypes()
1131
    {
1132
        return array_merge(parent::getColumnTypes(), ['filestream']);
1133
    }
1134
1135
    /**
1136
     * Records a migration being run.
1137
     *
1138
     * @param \Phinx\Migration\MigrationInterface $migration Migration
1139
     * @param string $direction Direction
1140
     * @param int $startTime Start Time
1141
     * @param int $endTime End Time
1142
     * @return \Phinx\Db\Adapter\AdapterInterface
1143
     */
1144
    public function migrated(\Phinx\Migration\MigrationInterface $migration, $direction, $startTime, $endTime)
1145
    {
1146
        $startTime = str_replace(' ', 'T', $startTime);
1147
        $endTime = str_replace(' ', 'T', $endTime);
1148
1149
        return parent::migrated($migration, $direction, $startTime, $endTime);
1150
    }
1151
}
1152