Completed
Pull Request — master (#1357)
by José
11:07
created

SqlServerAdapter::quoteColumnName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
239
            $sqlBuffer[] = $this->quoteColumnName($column->getName()) . ' ' . $this->getColumnSqlDefinition($column);
240
241
            // set column comments, if needed
242
            if ($column->getComment()) {
243
                $columnsWithComments[] = $column;
244
            }
245
        }
246
247
        // set the primary key(s)
248
        if (isset($options['primary_key'])) {
249
            $pkSql = sprintf('CONSTRAINT PK_%s PRIMARY KEY (', $table->getName());
250
            if (is_string($options['primary_key'])) { // handle primary_key => 'id'
251
                $pkSql .= $this->quoteColumnName($options['primary_key']);
252
            } elseif (is_array($options['primary_key'])) { // handle primary_key => array('tag_id', 'resource_id')
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
253
                $pkSql .= implode(',', array_map([$this, 'quoteColumnName'], $options['primary_key']));
254
            }
255
            $pkSql .= ')';
256
            $sqlBuffer[] = $pkSql;
257
        }
258
259
        $sql .= implode(', ', $sqlBuffer);
260
        $sql .= ');';
261
262
        // process column comments
263
        foreach ($columnsWithComments as $column) {
264
            $sql .= $this->getColumnCommentSqlDefinition($column, $table->getName());
265
        }
266
267
        // set the indexes
268
        foreach ($indexes as $index) {
269
            $sql .= $this->getIndexSqlDefinition($index, $table->getName());
270
        }
271
272
        // execute the sql
273
        $this->execute($sql);
274
    }
275
276
    /**
277
     * Gets the SqlServer Column Comment Defininition for a column object.
278
     *
279
     * @param \Phinx\Db\Table\Column $column    Column
280
     * @param string $tableName Table name
281
     *
282
     * @return string
283
     */
284
    protected function getColumnCommentSqlDefinition(Column $column, $tableName)
285
    {
286
        // passing 'null' is to remove column comment
287
        $currentComment = $this->getColumnComment($tableName, $column->getName());
288
289
        $comment = (strcasecmp($column->getComment(), 'NULL') !== 0) ? $this->getConnection()->quote($column->getComment()) : '\'\'';
290
        $command = $currentComment === false ? 'sp_addextendedproperty' : 'sp_updateextendedproperty';
291
292
        return sprintf(
293
            "EXECUTE %s N'MS_Description', N%s, N'SCHEMA', N'%s', N'TABLE', N'%s', N'COLUMN', N'%s';",
294
            $command,
295
            $comment,
296
            $this->schema,
297
            $tableName,
298
            $column->getName()
299
        );
300
    }
301
302
    /**
303
     * {@inheritdoc}
304
     */
305
    protected function getRenameTableInstructions($tableName, $newTableName)
306
    {
307
        $sql = sprintf(
308
            'EXEC sp_rename \'%s\', \'%s\'',
309
            $tableName,
310
            $newTableName
311
        );
312
313
        return new AlterInstructions([], [$sql]);
314
    }
315
316
    /**
317
     * {@inheritdoc}
318
     */
319
    protected function getDropTableInstructions($tableName)
320
    {
321
        $sql = sprintf('DROP TABLE %s', $this->quoteTableName($tableName));
322
323
        return new AlterInstructions([], [$sql]);
324
    }
325
326
    /**
327
     * {@inheritdoc}
328
     */
329
    public function truncateTable($tableName)
330
    {
331
        $sql = sprintf(
332
            'TRUNCATE TABLE %s',
333
            $this->quoteTableName($tableName)
334
        );
335
336
        $this->execute($sql);
337
    }
338
339
    public function getColumnComment($tableName, $columnName)
340
    {
341
        $sql = sprintf("SELECT cast(extended_properties.[value] as nvarchar(4000)) comment
342
  FROM sys.schemas
343
 INNER JOIN sys.tables
344
    ON schemas.schema_id = tables.schema_id
345
 INNER JOIN sys.columns
346
    ON tables.object_id = columns.object_id
347
 INNER JOIN sys.extended_properties
348
    ON tables.object_id = extended_properties.major_id
349
   AND columns.column_id = extended_properties.minor_id
350
   AND extended_properties.name = 'MS_Description'
351
   WHERE schemas.[name] = '%s' AND tables.[name] = '%s' AND columns.[name] = '%s'", $this->schema, $tableName, $columnName);
352
        $row = $this->fetchRow($sql);
353
354
        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...
355
            return $row['comment'];
356
        }
357
358
        return false;
359
    }
360
361
    /**
362
     * {@inheritdoc}
363
     */
364
    public function getColumns($tableName)
365
    {
366
        $columns = [];
367
        $sql = sprintf(
368
            "SELECT DISTINCT TABLE_SCHEMA AS [schema], TABLE_NAME as [table_name], COLUMN_NAME AS [name], DATA_TYPE AS [type],
369
            IS_NULLABLE AS [null], COLUMN_DEFAULT AS [default],
370
            CHARACTER_MAXIMUM_LENGTH AS [char_length],
371
            NUMERIC_PRECISION AS [precision],
372
            NUMERIC_SCALE AS [scale], ORDINAL_POSITION AS [ordinal_position],
373
            COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as [identity]
374
        FROM INFORMATION_SCHEMA.COLUMNS
375
        WHERE TABLE_NAME = '%s'
376
        ORDER BY ordinal_position",
377
            $tableName
378
        );
379
        $rows = $this->fetchAll($sql);
380
        foreach ($rows as $columnInfo) {
381
            $column = new Column();
382
            $column->setName($columnInfo['name'])
383
                   ->setType($this->getPhinxType($columnInfo['type']))
384
                   ->setNull($columnInfo['null'] !== 'NO')
385
                   ->setDefault($this->parseDefault($columnInfo['default']))
386
                   ->setIdentity($columnInfo['identity'] === '1')
387
                   ->setComment($this->getColumnComment($columnInfo['table_name'], $columnInfo['name']));
388
389
            if (!empty($columnInfo['char_length'])) {
390
                $column->setLimit($columnInfo['char_length']);
391
            }
392
393
            $columns[$columnInfo['name']] = $column;
394
        }
395
396
        return $columns;
397
    }
398
399
    protected function parseDefault($default)
400
    {
401
        $default = preg_replace(["/\('(.*)'\)/", "/\(\((.*)\)\)/", "/\((.*)\)/"], '$1', $default);
402
403
        if (strtoupper($default) === 'NULL') {
404
            $default = null;
405
        } elseif (is_numeric($default)) {
406
            $default = (int)$default;
407
        }
408
409
        return $default;
410
    }
411
412
    /**
413
     * {@inheritdoc}
414
     */
415
    public function hasColumn($tableName, $columnName)
416
    {
417
        $sql = sprintf(
418
            "SELECT count(*) as [count]
419
             FROM information_schema.columns
420
             WHERE table_name = '%s' AND column_name = '%s'",
421
            $tableName,
422
            $columnName
423
        );
424
        $result = $this->fetchRow($sql);
425
426
        return $result['count'] > 0;
427
    }
428
429
    /**
430
     * {@inheritdoc}
431
     */
432 View Code Duplication
    protected function getAddColumnInstructions(Table $table, Column $column)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
433
    {
434
        $alter = sprintf(
435
            'ALTER TABLE %s ADD %s %s',
436
            $table->getName(),
437
            $this->quoteColumnName($column->getName()),
438
            $this->getColumnSqlDefinition($column)
439
        );
440
441
        return new AlterInstructions([], [$alter]);
442
    }
443
444
    /**
445
     * {@inheritdoc}
446
     */
447
    protected function getRenameColumnInstructions($tableName, $columnName, $newColumnName)
448
    {
449
        if (!$this->hasColumn($tableName, $columnName)) {
450
            throw new \InvalidArgumentException("The specified column does not exist: $columnName");
451
        }
452
453
        $instructions = new AlterInstructions();
454
455
        $oldConstraintName = "DF_{$tableName}_{$columnName}";
456
        $newConstraintName = "DF_{$tableName}_{$newColumnName}";
457
        $sql = <<<SQL
458
IF (OBJECT_ID('$oldConstraintName', 'D') IS NOT NULL)
459
BEGIN
460
     EXECUTE sp_rename N'%s', N'%s', N'OBJECT'
461
END
462
SQL;
463
        $instructions->addPostStep(sprintf(
464
            $sql,
465
            $oldConstraintName,
466
            $newConstraintName
467
        ));
468
469
        $instructions->addPostStep(sprintf(
470
            "EXECUTE sp_rename N'%s.%s', N'%s', 'COLUMN' ",
471
            $tableName,
472
            $columnName,
473
            $newColumnName
474
        ));
475
476
        return $instructions;
477
    }
478
479
    /**
480
     * Returns the instructions to change a column default value
481
     *
482
     * @param string $tableName The table where the column is
483
     * @param Column $newColumn The column to alter
484
     * @return AlterInstructions
485
     */
486
    protected function getChangeDefault($tableName, Column $newColumn)
487
    {
488
        $constraintName = "DF_{$tableName}_{$newColumn->getName()}";
489
        $default = $newColumn->getDefault();
490
        $instructions = new AlterInstructions();
491
492
        if ($default === null) {
493
            $default = 'DEFAULT NULL';
494
        } else {
495
            $default = ltrim($this->getDefaultValueDefinition($default));
496
        }
497
498
        if (empty($default)) {
499
            return $instructions;
500
        }
501
502
        $instructions->addPostStep(sprintf(
503
            'ALTER TABLE %s ADD CONSTRAINT %s %s FOR %s',
504
            $this->quoteTableName($tableName),
505
            $constraintName,
506
            $default,
507
            $this->quoteColumnName($newColumn->getName())
508
        ));
509
510
        return $instructions;
511
    }
512
513
    /**
514
     * {@inheritdoc}
515
     */
516
    protected function getChangeColumnInstructions($tableName, $columnName, Column $newColumn)
517
    {
518
        $columns = $this->getColumns($tableName);
519
        $changeDefault =
520
            $newColumn->getDefault() !== $columns[$columnName]->getDefault() ||
521
            $newColumn->getType() !== $columns[$columnName]->getType();
522
523
        $instructions = new AlterInstructions();
524
525
        if ($columnName !== $newColumn->getName()) {
526
            $instructions->merge(
527
                $this->getRenameColumnInstructions($tableName, $columnName, $newColumn->getName())
528
            );
529
        }
530
531
        if ($changeDefault) {
532
            $instructions->merge($this->getDropDefaultConstraint($tableName, $newColumn->getName()));
533
        }
534
535
        $instructions->addPostStep(sprintf(
536
            'ALTER TABLE %s ALTER COLUMN %s %s',
537
            $this->quoteTableName($tableName),
538
            $this->quoteColumnName($newColumn->getName()),
539
            $this->getColumnSqlDefinition($newColumn, false)
540
        ));
541
        // change column comment if needed
542
        if ($newColumn->getComment()) {
543
            $instructions->merge($this->getColumnCommentSqlDefinition($newColumn, $tableName));
0 ignored issues
show
Documentation introduced by
$this->getColumnCommentS...$newColumn, $tableName) is of type string, but the function expects a object<Phinx\Db\Util\AlterInstructions>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
544
        }
545
546
        if ($changeDefault) {
547
            $instructions->merge($this->getChangeDefault($tableName, $newColumn));
548
        }
549
550
        return $instructions;
551
    }
552
553
    /**
554
     * {@inheritdoc}
555
     */
556
    protected function getDropColumnInstructions($tableName, $columnName)
557
    {
558
        $instructions = $this->getDropDefaultConstraint($tableName, $columnName);
559
560
        $instructions->addPostStep(sprintf(
561
            'ALTER TABLE %s DROP COLUMN %s',
562
            $this->quoteTableName($tableName),
563
            $this->quoteColumnName($columnName)
564
        ));
565
566
        return $instructions;
567
    }
568
569
    /**
570
     * {@inheritdoc}
571
     */
572
    protected function getDropDefaultConstraint($tableName, $columnName)
573
    {
574
        $defaultConstraint = $this->getDefaultConstraint($tableName, $columnName);
575
576
        if (!$defaultConstraint) {
577
            return new AlterInstructions();
578
        }
579
580
        return $this->getDropForeignKeyInstructions($tableName, $defaultConstraint);
581
    }
582
583
    protected function getDefaultConstraint($tableName, $columnName)
584
    {
585
        $sql = "SELECT
586
    default_constraints.name
587
FROM
588
    sys.all_columns
589
590
        INNER JOIN
591
    sys.tables
592
        ON all_columns.object_id = tables.object_id
593
594
        INNER JOIN
595
    sys.schemas
596
        ON tables.schema_id = schemas.schema_id
597
598
        INNER JOIN
599
    sys.default_constraints
600
        ON all_columns.default_object_id = default_constraints.object_id
601
602
WHERE
603
        schemas.name = 'dbo'
604
    AND tables.name = '{$tableName}'
605
    AND all_columns.name = '{$columnName}'";
606
607
        $rows = $this->fetchAll($sql);
608
609
        return empty($rows) ? false : $rows[0]['name'];
610
    }
611
612
    protected function getIndexColums($tableId, $indexId)
613
    {
614
        $sql = "SELECT AC.[name] AS [column_name]
615
FROM sys.[index_columns] IC
616
  INNER JOIN sys.[all_columns] AC ON IC.[column_id] = AC.[column_id]
617
WHERE AC.[object_id] = {$tableId} AND IC.[index_id] = {$indexId}  AND IC.[object_id] = {$tableId}
618
ORDER BY IC.[key_ordinal];";
619
620
        $rows = $this->fetchAll($sql);
621
        $columns = [];
622
        foreach ($rows as $row) {
623
            $columns[] = strtolower($row['column_name']);
624
        }
625
626
        return $columns;
627
    }
628
629
    /**
630
     * Get an array of indexes from a particular table.
631
     *
632
     * @param string $tableName Table Name
633
     * @return array
634
     */
635
    public function getIndexes($tableName)
636
    {
637
        $indexes = [];
638
        $sql = "SELECT I.[name] AS [index_name], I.[index_id] as [index_id], T.[object_id] as [table_id]
639
FROM sys.[tables] AS T
640
  INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]
641
WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP'  AND T.[name] = '{$tableName}'
642
ORDER BY T.[name], I.[index_id];";
643
644
        $rows = $this->fetchAll($sql);
645
        foreach ($rows as $row) {
646
            $columns = $this->getIndexColums($row['table_id'], $row['index_id']);
647
            $indexes[$row['index_name']] = ['columns' => $columns];
648
        }
649
650
        return $indexes;
651
    }
652
653
    /**
654
     * {@inheritdoc}
655
     */
656 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...
657
    {
658
        if (is_string($columns)) {
659
            $columns = [$columns]; // str to array
660
        }
661
662
        $columns = array_map('strtolower', $columns);
663
        $indexes = $this->getIndexes($tableName);
664
665
        foreach ($indexes as $index) {
666
            $a = array_diff($columns, $index['columns']);
667
668
            if (empty($a)) {
669
                return true;
670
            }
671
        }
672
673
        return false;
674
    }
675
676
    /**
677
     * {@inheritdoc}
678
     */
679 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...
680
    {
681
        $indexes = $this->getIndexes($tableName);
682
683
        foreach ($indexes as $name => $index) {
684
            if ($name === $indexName) {
685
                 return true;
686
            }
687
        }
688
689
        return false;
690
    }
691
692
    /**
693
     * {@inheritdoc}
694
     */
695
    protected function getAddIndexInstructions(Table $table, Index $index)
696
    {
697
        $sql = $this->getIndexSqlDefinition($index, $table->getName());
698
699
        return new AlterInstructions([], [$sql]);
700
    }
701
702
    /**
703
     * {@inheritdoc}
704
     */
705
    protected function getDropIndexByColumnsInstructions($tableName, $columns)
706
    {
707
        if (is_string($columns)) {
708
            $columns = [$columns]; // str to array
709
        }
710
711
        $indexes = $this->getIndexes($tableName);
712
        $columns = array_map('strtolower', $columns);
713
        $instructions = new AlterInstructions();
714
715
        foreach ($indexes as $indexName => $index) {
716
            $a = array_diff($columns, $index['columns']);
717 View Code Duplication
            if (empty($a)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
718
                $instructions->addPostStep(sprintf(
719
                    'DROP INDEX %s ON %s',
720
                    $this->quoteColumnName($indexName),
721
                    $this->quoteTableName($tableName)
722
                ));
723
724
                return $instructions;
725
            }
726
        }
727
728
        throw new \InvalidArgumentException(sprintf(
729
            "The specified index on columns '%s' does not exist",
730
            implode(',', $columns)
731
        ));
732
    }
733
734
    /**
735
     * {@inheritdoc}
736
     */
737
    protected function getDropIndexByNameInstructions($tableName, $indexName)
738
    {
739
        $indexes = $this->getIndexes($tableName);
740
        $instructions = new AlterInstructions();
741
742
        foreach ($indexes as $name => $index) {
743 View Code Duplication
            if ($name === $indexName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
744
                $instructions->addPostStep(sprintf(
745
                    'DROP INDEX %s ON %s',
746
                    $this->quoteColumnName($indexName),
747
                    $this->quoteTableName($tableName)
748
                ));
749
750
                return $instructions;
751
            }
752
        }
753
754
        throw new \InvalidArgumentException(sprintf(
755
            "The specified index name '%s' does not exist",
756
            $indexName
757
        ));
758
    }
759
760
    /**
761
     * {@inheritdoc}
762
     */
763 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...
764
    {
765
        if (is_string($columns)) {
766
            $columns = [$columns]; // str to array
767
        }
768
        $foreignKeys = $this->getForeignKeys($tableName);
769
        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...
770
            if (isset($foreignKeys[$constraint])) {
771
                return !empty($foreignKeys[$constraint]);
772
            }
773
774
            return false;
775
        } else {
776
            foreach ($foreignKeys as $key) {
777
                $a = array_diff($columns, $key['columns']);
778
                if (empty($a)) {
779
                    return true;
780
                }
781
            }
782
783
            return false;
784
        }
785
    }
786
787
    /**
788
     * Get an array of foreign keys from a particular table.
789
     *
790
     * @param string $tableName Table Name
791
     * @return array
792
     */
793 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...
794
    {
795
        $foreignKeys = [];
796
        $rows = $this->fetchAll(sprintf(
797
            "SELECT
798
                    tc.constraint_name,
799
                    tc.table_name, kcu.column_name,
800
                    ccu.table_name AS referenced_table_name,
801
                    ccu.column_name AS referenced_column_name
802
                FROM
803
                    information_schema.table_constraints AS tc
804
                    JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
805
                    JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
806
                WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s'
807
                ORDER BY kcu.ordinal_position",
808
            $tableName
809
        ));
810
        foreach ($rows as $row) {
811
            $foreignKeys[$row['constraint_name']]['table'] = $row['table_name'];
812
            $foreignKeys[$row['constraint_name']]['columns'][] = $row['column_name'];
813
            $foreignKeys[$row['constraint_name']]['referenced_table'] = $row['referenced_table_name'];
814
            $foreignKeys[$row['constraint_name']]['referenced_columns'][] = $row['referenced_column_name'];
815
        }
816
817
        return $foreignKeys;
818
    }
819
820
    /**
821
     * {@inheritdoc}
822
     */
823
    protected function getAddForeignKeyInstructions(Table $table, ForeignKey $foreignKey)
824
    {
825
        $instructions = new AlterInstructions();
826
        $instructions->addPostStep(sprintf(
827
            'ALTER TABLE %s ADD %s',
828
            $this->quoteTableName($table->getName()),
829
            $this->getForeignKeySqlDefinition($foreignKey, $table->getName())
830
        ));
831
832
        return $instructions;
833
    }
834
835
    /**
836
     * {@inheritdoc}
837
     */
838
    protected function getDropForeignKeyInstructions($tableName, $constraint)
839
    {
840
        $instructions = new AlterInstructions();
841
        $instructions->addPostStep(sprintf(
842
            'ALTER TABLE %s DROP CONSTRAINT %s',
843
            $this->quoteTableName($tableName),
844
            $constraint
845
        ));
846
847
        return $instructions;
848
    }
849
850
    /**
851
     * {@inheritdoc}
852
     */
853
    protected function getDropForeignKeyByColumnsInstructions($tableName, $columns)
854
    {
855
        $instructions = new AlterInstructions();
856
857 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...
858
            $rows = $this->fetchAll(sprintf(
859
                "SELECT
860
                tc.constraint_name,
861
                tc.table_name, kcu.column_name,
862
                ccu.table_name AS referenced_table_name,
863
                ccu.column_name AS referenced_column_name
864
            FROM
865
                information_schema.table_constraints AS tc
866
                JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
867
                JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
868
            WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '%s' and ccu.column_name='%s'
869
            ORDER BY kcu.ordinal_position",
870
                $tableName,
871
                $column
872
            ));
873
            foreach ($rows as $row) {
874
                $instructions->merge(
875
                    $this->getDropForeignKeyInstructions($tableName, $row['constraint_name'])
876
                );
877
            }
878
        }
879
880
        return $instructions;
881
    }
882
883
    /**
884
     * {@inheritdoc}
885
     */
886
    public function getSqlType($type, $limit = null)
887
    {
888
        switch ($type) {
889
            case static::PHINX_TYPE_FLOAT:
890
            case static::PHINX_TYPE_DECIMAL:
891
            case static::PHINX_TYPE_DATETIME:
892
            case static::PHINX_TYPE_TIME:
893
            case static::PHINX_TYPE_DATE:
894
                return ['name' => $type];
895
            case static::PHINX_TYPE_STRING:
896
                return ['name' => 'nvarchar', 'limit' => 255];
897
            case static::PHINX_TYPE_CHAR:
898
                return ['name' => 'nchar', 'limit' => 255];
899
            case static::PHINX_TYPE_TEXT:
900
                return ['name' => 'ntext'];
901
            case static::PHINX_TYPE_INTEGER:
902
                return ['name' => 'int'];
903
            case static::PHINX_TYPE_BIG_INTEGER:
904
                return ['name' => 'bigint'];
905
            case static::PHINX_TYPE_TIMESTAMP:
906
                return ['name' => 'datetime'];
907
            case static::PHINX_TYPE_BLOB:
908
            case static::PHINX_TYPE_BINARY:
909
                return ['name' => 'varbinary'];
910
            case static::PHINX_TYPE_BOOLEAN:
911
                return ['name' => 'bit'];
912
            case static::PHINX_TYPE_UUID:
913
                return ['name' => 'uniqueidentifier'];
914
            case static::PHINX_TYPE_FILESTREAM:
915
                return ['name' => 'varbinary', 'limit' => 'max'];
916
            // Geospatial database types
917
            case static::PHINX_TYPE_GEOMETRY:
918
            case static::PHINX_TYPE_POINT:
919
            case static::PHINX_TYPE_LINESTRING:
920
            case static::PHINX_TYPE_POLYGON:
921
                // SQL Server stores all spatial data using a single data type.
922
                // Specific types (point, polygon, etc) are set at insert time.
923
                return ['name' => 'geography'];
924
            default:
925
                throw new \RuntimeException('The type: "' . $type . '" is not supported.');
926
        }
927
    }
928
929
    /**
930
     * Returns Phinx type by SQL type
931
     *
932
     * @param string $sqlType SQL Type definition
933
     * @throws \RuntimeException
934
     * @internal param string $sqlType SQL type
935
     * @returns string Phinx type
936
     */
937
    public function getPhinxType($sqlType)
938
    {
939
        switch ($sqlType) {
940
            case 'nvarchar':
941
            case 'varchar':
942
                return static::PHINX_TYPE_STRING;
943
            case 'char':
944
            case 'nchar':
945
                return static::PHINX_TYPE_CHAR;
946
            case 'text':
947
            case 'ntext':
948
                return static::PHINX_TYPE_TEXT;
949
            case 'int':
950
            case 'integer':
951
                return static::PHINX_TYPE_INTEGER;
952
            case 'decimal':
953
            case 'numeric':
954
            case 'money':
955
                return static::PHINX_TYPE_DECIMAL;
956
            case 'bigint':
957
                return static::PHINX_TYPE_BIG_INTEGER;
958
            case 'real':
959
            case 'float':
960
                return static::PHINX_TYPE_FLOAT;
961
            case 'binary':
962
            case 'image':
963
            case 'varbinary':
964
                return static::PHINX_TYPE_BINARY;
965
            case 'time':
966
                return static::PHINX_TYPE_TIME;
967
            case 'date':
968
                return static::PHINX_TYPE_DATE;
969
            case 'datetime':
970
            case 'timestamp':
971
                return static::PHINX_TYPE_DATETIME;
972
            case 'bit':
973
                return static::PHINX_TYPE_BOOLEAN;
974
            case 'uniqueidentifier':
975
                return static::PHINX_TYPE_UUID;
976
            case 'filestream':
977
                return static::PHINX_TYPE_FILESTREAM;
978
            default:
979
                throw new \RuntimeException('The SqlServer type: "' . $sqlType . '" is not supported');
980
        }
981
    }
982
983
    /**
984
     * {@inheritdoc}
985
     */
986
    public function createDatabase($name, $options = [])
987
    {
988 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...
989
            $this->execute(sprintf('CREATE DATABASE [%s] COLLATE [%s]', $name, $options['collation']));
990
        } else {
991
            $this->execute(sprintf('CREATE DATABASE [%s]', $name));
992
        }
993
        $this->execute(sprintf('USE [%s]', $name));
994
    }
995
996
    /**
997
     * {@inheritdoc}
998
     */
999
    public function hasDatabase($name)
1000
    {
1001
        $result = $this->fetchRow(
1002
            sprintf(
1003
                'SELECT count(*) as [count] FROM master.dbo.sysdatabases WHERE [name] = \'%s\'',
1004
                $name
1005
            )
1006
        );
1007
1008
        return $result['count'] > 0;
1009
    }
1010
1011
    /**
1012
     * {@inheritdoc}
1013
     */
1014
    public function dropDatabase($name)
1015
    {
1016
        $sql = <<<SQL
1017
USE master;
1018
IF EXISTS(select * from sys.databases where name=N'$name')
1019
ALTER DATABASE [$name] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
1020
DROP DATABASE [$name];
1021
SQL;
1022
        $this->execute($sql);
1023
    }
1024
1025
    /**
1026
     * Gets the SqlServer Column Definition for a Column object.
1027
     *
1028
     * @param \Phinx\Db\Table\Column $column Column
1029
     * @return string
1030
     */
1031
    protected function getColumnSqlDefinition(Column $column, $create = true)
1032
    {
1033
        $buffer = [];
1034
        if ($column->getType() instanceof Literal) {
1035
            $buffer[] = (string)$column->getType();
1036
        } else {
1037
            $sqlType = $this->getSqlType($column->getType());
1038
            $buffer[] = strtoupper($sqlType['name']);
1039
            // integers cant have limits in SQlServer
1040
            $noLimits = [
1041
                'bigint',
1042
                'int',
1043
                'tinyint'
1044
            ];
1045
            if (!in_array($sqlType['name'], $noLimits) && ($column->getLimit() || isset($sqlType['limit']))) {
1046
                $buffer[] = sprintf('(%s)', $column->getLimit() ? $column->getLimit() : $sqlType['limit']);
1047
            }
1048
        }
1049 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...
1050
            $buffer[] = '(' . $column->getPrecision() . ',' . $column->getScale() . ')';
1051
        }
1052
1053
        $properties = $column->getProperties();
1054
        $buffer[] = $column->getType() === 'filestream' ? 'FILESTREAM' : '';
1055
        $buffer[] = isset($properties['rowguidcol']) ? 'ROWGUIDCOL' : '';
1056
1057
        $buffer[] = $column->isNull() ? 'NULL' : 'NOT NULL';
1058
1059
        if ($create === true) {
1060
            if ($column->getDefault() === null && $column->isNull()) {
1061
                $buffer[] = ' DEFAULT NULL';
1062
            } else {
1063
                $buffer[] = $this->getDefaultValueDefinition($column->getDefault());
1064
            }
1065
        }
1066
1067
        if ($column->isIdentity()) {
1068
            $buffer[] = 'IDENTITY(1, 1)';
1069
        }
1070
1071
        return implode(' ', $buffer);
1072
    }
1073
1074
    /**
1075
     * Gets the SqlServer Index Definition for an Index object.
1076
     *
1077
     * @param \Phinx\Db\Table\Index $index Index
1078
     * @return string
1079
     */
1080
    protected function getIndexSqlDefinition(Index $index, $tableName)
1081
    {
1082 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...
1083
            $indexName = $index->getName();
1084
        } else {
1085
            $columnNames = $index->getColumns();
1086
            $indexName = sprintf('%s_%s', $tableName, implode('_', $columnNames));
1087
        }
1088
        $def = sprintf(
1089
            "CREATE %s INDEX %s ON %s (%s);",
1090
            ($index->getType() === Index::UNIQUE ? 'UNIQUE' : ''),
1091
            $indexName,
1092
            $this->quoteTableName($tableName),
1093
            '[' . implode('],[', $index->getColumns()) . ']'
1094
        );
1095
1096
        return $def;
1097
    }
1098
1099
    /**
1100
     * Gets the SqlServer Foreign Key Definition for an ForeignKey object.
1101
     *
1102
     * @param \Phinx\Db\Table\ForeignKey $foreignKey
1103
     * @return string
1104
     */
1105
    protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, $tableName)
1106
    {
1107
        $constraintName = $foreignKey->getConstraint() ?: $tableName . '_' . implode('_', $foreignKey->getColumns());
1108
        $def = ' CONSTRAINT ' . $this->quoteColumnName($constraintName);
0 ignored issues
show
Bug introduced by
It seems like $constraintName defined by $foreignKey->getConstrai...reignKey->getColumns()) on line 1107 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...
1109
        $def .= ' FOREIGN KEY ("' . implode('", "', $foreignKey->getColumns()) . '")';
1110
        $def .= " REFERENCES {$this->quoteTableName($foreignKey->getReferencedTable()->getName())} (\"" . implode('", "', $foreignKey->getReferencedColumns()) . '")';
1111
        if ($foreignKey->getOnDelete()) {
1112
            $def .= " ON DELETE {$foreignKey->getOnDelete()}";
1113
        }
1114
        if ($foreignKey->getOnUpdate()) {
1115
            $def .= " ON UPDATE {$foreignKey->getOnUpdate()}";
1116
        }
1117
1118
        return $def;
1119
    }
1120
1121
    /**
1122
     * {@inheritdoc}
1123
     */
1124
    public function getColumnTypes()
1125
    {
1126
        return array_merge(parent::getColumnTypes(), ['filestream']);
1127
    }
1128
1129
    /**
1130
     * Records a migration being run.
1131
     *
1132
     * @param \Phinx\Migration\MigrationInterface $migration Migration
1133
     * @param string $direction Direction
1134
     * @param int $startTime Start Time
1135
     * @param int $endTime End Time
1136
     * @return \Phinx\Db\Adapter\AdapterInterface
1137
     */
1138
    public function migrated(\Phinx\Migration\MigrationInterface $migration, $direction, $startTime, $endTime)
1139
    {
1140
        $startTime = str_replace(' ', 'T', $startTime);
1141
        $endTime = str_replace(' ', 'T', $endTime);
1142
1143
        return parent::migrated($migration, $direction, $startTime, $endTime);
1144
    }
1145
1146
    /**
1147
     * {@inheritDoc}
1148
     *
1149
     */
1150 View Code Duplication
    public function getDecoratedConnection()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1151
    {
1152
        $options = $this->getOptions();
1153
        $options = [
1154
            'username' => $options['user'],
1155
            'password' => $options['pass'],
1156
            'database' => $options['name'],
1157
            'quoteIdentifiers' => true,
1158
        ] + $options;
1159
1160
        $driver = new SqlServerDriver($options);
1161
1162
        if (method_exists($driver, 'setConnection')) {
1163
            $driver->setConnection($this->connection);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can be null; however, setConnection() does not accept null, maybe add an additional type check?

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

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

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

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

Loading history...
1166
        }
1167
1168
        return new Connection(['driver' => $driver] + $options);
1169
    }
1170
}
1171