Failed Conditions
Pull Request — master (#2762)
by Evgenij
04:45
created

SQLServerPlatform::getLocateExpression()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 8
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 8
loc 8
ccs 0
cts 4
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 3
crap 6
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Platforms;
21
22
use Doctrine\DBAL\LockMode;
23
use Doctrine\DBAL\Schema\Column;
24
use Doctrine\DBAL\Schema\ColumnDiff;
25
use Doctrine\DBAL\Schema\Identifier;
26
use Doctrine\DBAL\Schema\TableDiff;
27
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
28
use Doctrine\DBAL\Schema\Index;
29
use Doctrine\DBAL\Schema\Table;
30
31
/**
32
 * The SQLServerPlatform provides the behavior, features and SQL dialect of the
33
 * Microsoft SQL Server database platform.
34
 *
35
 * @since 2.0
36
 * @author Roman Borschel <[email protected]>
37
 * @author Jonathan H. Wage <[email protected]>
38
 * @author Benjamin Eberlei <[email protected]>
39
 * @author Steve Müller <[email protected]>
40
 */
41
class SQLServerPlatform extends AbstractPlatform
42
{
43
    /**
44
     * {@inheritdoc}
45
     */
46 3
    public function getCurrentDateSQL()
47
    {
48 3
        return $this->getConvertExpression('date', 'GETDATE()');
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     */
54 3
    public function getCurrentTimeSQL()
55
    {
56 3
        return $this->getConvertExpression('time', 'GETDATE()');
57
    }
58
59
    /**
60
     * Returns an expression that converts an expression of one data type to another.
61
     *
62
     * @param string $dataType   The target native data type. Alias data types cannot be used.
63
     * @param string $expression The SQL expression to convert.
64
     *
65
     * @return string
66
     */
67 3
    private function getConvertExpression($dataType, $expression)
68
    {
69 3
        return sprintf('CONVERT(%s, %s)', $dataType, $expression);
70
    }
71
72
    /**
73
     * {@inheritdoc}
74
     */
75 View Code Duplication
    protected function getDateArithmeticIntervalExpression($date, $operator, $interval, $unit)
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...
76
    {
77
        $factorClause = '';
78
79
        if ('-' === $operator) {
80
            $factorClause = '-1 * ';
81
        }
82
83
        return 'DATEADD(' . $unit . ', ' . $factorClause . $interval . ', ' . $date . ')';
84
    }
85
86
    /**
87
     * {@inheritDoc}
88
     */
89
    public function getDateDiffExpression($date1, $date2)
90
    {
91
        return 'DATEDIFF(day, ' . $date2 . ',' . $date1 . ')';
92
    }
93
94
    /**
95
     * {@inheritDoc}
96
     *
97
     * Microsoft SQL Server prefers "autoincrement" identity columns
98
     * since sequences can only be emulated with a table.
99
     */
100 3
    public function prefersIdentityColumns()
101
    {
102 3
        return true;
103
    }
104
105
    /**
106
     * {@inheritDoc}
107
     *
108
     * Microsoft SQL Server supports this through AUTO_INCREMENT columns.
109
     */
110 3
    public function supportsIdentityColumns()
111
    {
112 3
        return true;
113
    }
114
115
    /**
116
     * {@inheritDoc}
117
     */
118
    public function supportsReleaseSavepoints()
119
    {
120
        return false;
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126 3
    public function supportsSchemas()
127
    {
128 3
        return true;
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134
    public function getDefaultSchemaName()
135
    {
136
        return 'dbo';
137
    }
138
139
    /**
140
     * {@inheritDoc}
141
     */
142
    public function supportsColumnCollation()
143
    {
144
        return true;
145
    }
146
147
    /**
148
     * {@inheritDoc}
149
     */
150 160
    public function hasNativeGuidType()
151
    {
152 160
        return true;
153
    }
154
155
    /**
156
     * {@inheritDoc}
157
     */
158 3
    public function getCreateDatabaseSQL($name)
159
    {
160 3
        return 'CREATE DATABASE ' . $name;
161
    }
162
163
    /**
164
     * {@inheritDoc}
165
     */
166 3
    public function getDropDatabaseSQL($name)
167
    {
168 3
        return 'DROP DATABASE ' . $name;
169
    }
170
171
    /**
172
     * {@inheritDoc}
173
     */
174 3
    public function supportsCreateDropDatabase()
175
    {
176 3
        return true;
177
    }
178
179
    /**
180
     * {@inheritDoc}
181
     */
182 3
    public function getCreateSchemaSQL($schemaName)
183
    {
184 3
        return 'CREATE SCHEMA ' . $schemaName;
185
    }
186
187
    /**
188
     * {@inheritDoc}
189
     */
190 6 View Code Duplication
    public function getDropForeignKeySQL($foreignKey, $table)
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...
191
    {
192 6
        if (! $foreignKey instanceof ForeignKeyConstraint) {
193 3
            $foreignKey = new Identifier($foreignKey);
194
        }
195
196 6
        if (! $table instanceof Table) {
197 6
            $table = new Identifier($table);
198
        }
199
200 6
        $foreignKey = $foreignKey->getQuotedName($this);
201 6
        $table = $table->getQuotedName($this);
202
203 6
        return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey;
204
    }
205
206
    /**
207
     * {@inheritDoc}
208
     */
209
    public function getDropIndexSQL($index, $table = null)
210
    {
211
        if ($index instanceof Index) {
212
            $index = $index->getQuotedName($this);
213
        } elseif (!is_string($index)) {
214
            throw new \InvalidArgumentException('AbstractPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.');
215
        }
216
217
        if (!isset($table)) {
218
            return 'DROP INDEX ' . $index;
219
        }
220
221
        if ($table instanceof Table) {
222
            $table = $table->getQuotedName($this);
223
        }
224
225
        return "IF EXISTS (SELECT * FROM sysobjects WHERE name = '$index')
226
                    ALTER TABLE " . $table . " DROP CONSTRAINT " . $index . "
227
                ELSE
228
                    DROP INDEX " . $index . " ON " . $table;
229
    }
230
231
    /**
232
     * {@inheritDoc}
233
     */
234 46
    protected function _getCreateTableSQL($tableName, array $columns, array $options = array())
235
    {
236 46
        $defaultConstraintsSql = array();
237 46
        $commentsSql           = array();
238
239
        // @todo does other code breaks because of this?
240
        // force primary keys to be not null
241 46
        foreach ($columns as &$column) {
242 46
            if (isset($column['primary']) && $column['primary']) {
243 15
                $column['notnull'] = true;
244
            }
245
246
            // Build default constraints SQL statements.
247 46
            if (isset($column['default'])) {
248 12
                $defaultConstraintsSql[] = 'ALTER TABLE ' . $tableName .
249 12
                    ' ADD' . $this->getDefaultConstraintDeclarationSQL($tableName, $column);
250
            }
251
252 46
            if ( ! empty($column['comment']) || is_numeric($column['comment'])) {
253 9
                $commentsSql[] = $this->getCreateColumnCommentSQL($tableName, $column['name'], $column['comment']);
254
            }
255
        }
256
257 46
        $columnListSql = $this->getColumnDeclarationListSQL($columns);
258
259 46
        if (isset($options['uniqueConstraints']) && !empty($options['uniqueConstraints'])) {
260
            foreach ($options['uniqueConstraints'] as $name => $definition) {
261
                $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
262
            }
263
        }
264
265 46 View Code Duplication
        if (isset($options['primary']) && !empty($options['primary'])) {
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...
266 18
            $flags = '';
267 18
            if (isset($options['primary_index']) && $options['primary_index']->hasFlag('nonclustered')) {
268 3
                $flags = ' NONCLUSTERED';
269
            }
270 18
            $columnListSql .= ', PRIMARY KEY' . $flags . ' (' . implode(', ', array_unique(array_values($options['primary']))) . ')';
271
        }
272
273 46
        $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql;
274
275 46
        $check = $this->getCheckDeclarationSQL($columns);
276 46
        if (!empty($check)) {
277
            $query .= ', ' . $check;
278
        }
279 46
        $query .= ')';
280
281 46
        $sql[] = $query;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$sql was never initialized. Although not strictly required by PHP, it is generally a good practice to add $sql = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
282
283 46 View Code Duplication
        if (isset($options['indexes']) && !empty($options['indexes'])) {
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...
284 9
            foreach ($options['indexes'] as $index) {
285 9
                $sql[] = $this->getCreateIndexSQL($index, $tableName);
286
            }
287
        }
288
289 46 View Code Duplication
        if (isset($options['foreignKeys'])) {
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...
290 3
            foreach ((array) $options['foreignKeys'] as $definition) {
291 3
                $sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
292
            }
293
        }
294
295 46
        return array_merge($sql, $commentsSql, $defaultConstraintsSql);
296
    }
297
298
    /**
299
     * {@inheritDoc}
300
     */
301 6
    public function getCreatePrimaryKeySQL(Index $index, $table)
302
    {
303 6
        $flags = '';
304 6
        if ($index->hasFlag('nonclustered')) {
305 3
            $flags = ' NONCLUSTERED';
306
        }
307
308 6
        return 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY' . $flags . ' (' . $this->getIndexFieldDeclarationListSQL($index->getQuotedColumns($this)) . ')';
309
    }
310
311
    /**
312
     * Returns the SQL statement for creating a column comment.
313
     *
314
     * SQL Server does not support native column comments,
315
     * therefore the extended properties functionality is used
316
     * as a workaround to store them.
317
     * The property name used to store column comments is "MS_Description"
318
     * which provides compatibility with SQL Server Management Studio,
319
     * as column comments are stored in the same property there when
320
     * specifying a column's "Description" attribute.
321
     *
322
     * @param string $tableName  The quoted table name to which the column belongs.
323
     * @param string $columnName The quoted column name to create the comment for.
324
     * @param string $comment    The column's comment.
325
     *
326
     * @return string
327
     */
328 15
    protected function getCreateColumnCommentSQL($tableName, $columnName, $comment)
329
    {
330 15
        return $this->getAddExtendedPropertySQL(
331 15
            'MS_Description',
332 15
            $comment,
333 15
            'SCHEMA',
334 15
            'dbo',
335 15
            'TABLE',
336 15
            $tableName,
337 15
            'COLUMN',
338 15
            $columnName
339
        );
340
    }
341
342
    /**
343
     * Returns the SQL snippet for declaring a default constraint.
344
     *
345
     * @param string $table  Name of the table to return the default constraint declaration for.
346
     * @param array  $column Column definition.
347
     *
348
     * @return string
349
     *
350
     * @throws \InvalidArgumentException
351
     */
352 45
    public function getDefaultConstraintDeclarationSQL($table, array $column)
353
    {
354 45
        if ( ! isset($column['default'])) {
355
            throw new \InvalidArgumentException("Incomplete column definition. 'default' required.");
356
        }
357
358 45
        $columnName = new Identifier($column['name']);
359
360
        return
361
            ' CONSTRAINT ' .
362 45
            $this->generateDefaultConstraintName($table, $column['name']) .
363 45
            $this->getDefaultValueDeclarationSQL($column) .
364 45
            ' FOR ' . $columnName->getQuotedName($this);
365
    }
366
367
    /**
368
     * {@inheritDoc}
369
     */
370 6
    public function getUniqueConstraintDeclarationSQL($name, Index $index)
371
    {
372 6
        $constraint = parent::getUniqueConstraintDeclarationSQL($name, $index);
373
374 6
        $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index);
375
376 6
        return $constraint;
377
    }
378
379
    /**
380
     * {@inheritDoc}
381
     */
382 24
    public function getCreateIndexSQL(Index $index, $table)
383
    {
384 24
        $constraint = parent::getCreateIndexSQL($index, $table);
385
386 24
        if ($index->isUnique() && !$index->isPrimary()) {
387 6
            $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index);
388
        }
389
390 24
        return $constraint;
391
    }
392
393
    /**
394
     * {@inheritDoc}
395
     */
396 24 View Code Duplication
    protected function getCreateIndexSQLFlags(Index $index)
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...
397
    {
398 24
        $type = '';
399 24
        if ($index->isUnique()) {
400 6
            $type .= 'UNIQUE ';
401
        }
402
403 24
        if ($index->hasFlag('clustered')) {
404 3
            $type .= 'CLUSTERED ';
405 21
        } elseif ($index->hasFlag('nonclustered')) {
406
            $type .= 'NONCLUSTERED ';
407
        }
408
409 24
        return $type;
410
    }
411
412
    /**
413
     * Extend unique key constraint with required filters
414
     *
415
     * @param string                      $sql
416
     * @param \Doctrine\DBAL\Schema\Index $index
417
     *
418
     * @return string
419
     */
420 12
    private function _appendUniqueConstraintDefinition($sql, Index $index)
421
    {
422 12
        $fields = array();
423
424 12
        foreach ($index->getQuotedColumns($this) as $field) {
425 12
            $fields[] = $field . ' IS NOT NULL';
426
        }
427
428 12
        return $sql . ' WHERE ' . implode(' AND ', $fields);
429
    }
430
431
    /**
432
     * {@inheritDoc}
433
     */
434 57
    public function getAlterTableSQL(TableDiff $diff)
435
    {
436 57
        $queryParts  = array();
437 57
        $sql         = array();
438 57
        $columnSql   = array();
439 57
        $commentsSql = array();
440
441
        /** @var \Doctrine\DBAL\Schema\Column $column */
442 57
        foreach ($diff->addedColumns as $column) {
443 27
            if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
444
                continue;
445
            }
446
447 27
            $columnDef = $column->toArray();
448 27
            $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef);
449
450 27
            if (isset($columnDef['default'])) {
451 12
                $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($diff->name, $column);
452
            }
453
454 27
            $comment = $this->getColumnComment($column);
455
456 27
            if ( ! empty($comment) || is_numeric($comment)) {
457 6
                $commentsSql[] = $this->getCreateColumnCommentSQL(
458 6
                    $diff->name,
459 6
                    $column->getQuotedName($this),
460 6
                    $comment
461
                );
462
            }
463
        }
464
465 57 View Code Duplication
        foreach ($diff->removedColumns 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...
466 24
            if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
467
                continue;
468
            }
469
470 24
            $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this);
471
        }
472
473
        /* @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */
474 57
        foreach ($diff->changedColumns as $columnDiff) {
475 36
            if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
476
                continue;
477
            }
478
479 36
            $column     = $columnDiff->column;
480 36
            $comment    = $this->getColumnComment($column);
481 36
            $hasComment = ! empty ($comment) || is_numeric($comment);
482
483 36
            if ($columnDiff->fromColumn instanceof Column) {
484 21
                $fromComment    = $this->getColumnComment($columnDiff->fromColumn);
485 21
                $hasFromComment = ! empty ($fromComment) || is_numeric($fromComment);
486
487 21
                if ($hasFromComment && $hasComment && $fromComment != $comment) {
488 3
                    $commentsSql[] = $this->getAlterColumnCommentSQL(
489 3
                        $diff->name,
490 3
                        $column->getQuotedName($this),
491 3
                        $comment
492
                    );
493 3
                } elseif ($hasFromComment && ! $hasComment) {
494 3
                    $commentsSql[] = $this->getDropColumnCommentSQL($diff->name, $column->getQuotedName($this));
495 21
                } elseif ($hasComment) {
496 3
                    $commentsSql[] = $this->getCreateColumnCommentSQL(
497 3
                        $diff->name,
498 3
                        $column->getQuotedName($this),
499 3
                        $comment
500
                    );
501
                }
502
            } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
503
                // todo: Original comment cannot be determined. What to do? Add, update, drop or skip?
504
            }
505
506
            // Do not add query part if only comment has changed.
507 36
            if ($columnDiff->hasChanged('comment') && count($columnDiff->changedProperties) === 1) {
508 6
                continue;
509
            }
510
511 33
            $requireDropDefaultConstraint = $this->alterColumnRequiresDropDefaultConstraint($columnDiff);
512
513 33
            if ($requireDropDefaultConstraint) {
514 15
                $queryParts[] = $this->getAlterTableDropDefaultConstraintClause(
515 15
                    $diff->name,
516 15
                    $columnDiff->oldColumnName
517
                );
518
            }
519
520 33
            $columnDef = $column->toArray();
521
522 33
            $queryParts[] = 'ALTER COLUMN ' .
523 33
                    $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef);
524
525 33
            if (isset($columnDef['default']) && ($requireDropDefaultConstraint || $columnDiff->hasChanged('default'))) {
526 18
                $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($diff->name, $column);
527
            }
528
        }
529
530 57
        foreach ($diff->renamedColumns as $oldColumnName => $column) {
531 15
            if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
532
                continue;
533
            }
534
535 15
            $oldColumnName = new Identifier($oldColumnName);
536
537 15
            $sql[] = "sp_RENAME '" .
538 15
                $diff->getName($this)->getQuotedName($this) . "." . $oldColumnName->getQuotedName($this) .
539 15
                "', '" . $column->getQuotedName($this) . "', 'COLUMN'";
540
541
            // Recreate default constraint with new column name if necessary (for future reference).
542 15
            if ($column->getDefault() !== null) {
543 3
                $queryParts[] = $this->getAlterTableDropDefaultConstraintClause(
544 3
                    $diff->name,
545 3
                    $oldColumnName->getQuotedName($this)
546
                );
547 3
                $queryParts[] = $this->getAlterTableAddDefaultConstraintClause($diff->name, $column);
548
            }
549
        }
550
551 57
        $tableSql = array();
552
553 57
        if ($this->onSchemaAlterTable($diff, $tableSql)) {
554
            return array_merge($tableSql, $columnSql);
555
        }
556
557 57
        foreach ($queryParts as $query) {
558 39
            $sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' ' . $query;
559
        }
560
561 57
        $sql = array_merge($sql, $commentsSql);
562
563 57
        if ($diff->newName !== false) {
564 6
            $sql[] = "sp_RENAME '" . $diff->getName($this)->getQuotedName($this) . "', '" . $diff->getNewName()->getName() . "'";
565
566
            /**
567
             * Rename table's default constraints names
568
             * to match the new table name.
569
             * This is necessary to ensure that the default
570
             * constraints can be referenced in future table
571
             * alterations as the table name is encoded in
572
             * default constraints' names.
573
             */
574 6
            $sql[] = "DECLARE @sql NVARCHAR(MAX) = N''; " .
575
                "SELECT @sql += N'EXEC sp_rename N''' + dc.name + ''', N''' " .
576 6
                "+ REPLACE(dc.name, '" . $this->generateIdentifierName($diff->name) . "', " .
577 6
                "'" . $this->generateIdentifierName($diff->newName) . "') + ''', ''OBJECT'';' " .
0 ignored issues
show
Bug introduced by
It seems like $diff->newName can also be of type boolean; however, Doctrine\DBAL\Platforms\...enerateIdentifierName() 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...
578
                "FROM sys.default_constraints dc " .
579
                "JOIN sys.tables tbl ON dc.parent_object_id = tbl.object_id " .
580 6
                "WHERE tbl.name = '" . $diff->getNewName()->getName() . "';" .
581 6
                "EXEC sp_executesql @sql";
582
        }
583
584 57
        $sql = array_merge(
585 57
            $this->getPreAlterTableIndexForeignKeySQL($diff),
586 57
            $sql,
587 57
            $this->getPostAlterTableIndexForeignKeySQL($diff)
588
        );
589
590 57
        return array_merge($sql, $tableSql, $columnSql);
591
    }
592
593
    /**
594
     * Returns the SQL clause for adding a default constraint in an ALTER TABLE statement.
595
     *
596
     * @param string $tableName The name of the table to generate the clause for.
597
     * @param Column $column    The column to generate the clause for.
598
     *
599
     * @return string
600
     */
601 21
    private function getAlterTableAddDefaultConstraintClause($tableName, Column $column)
602
    {
603 21
        $columnDef = $column->toArray();
604 21
        $columnDef['name'] = $column->getQuotedName($this);
605
606 21
        return 'ADD' . $this->getDefaultConstraintDeclarationSQL($tableName, $columnDef);
607
    }
608
609
    /**
610
     * Returns the SQL clause for dropping an existing default constraint in an ALTER TABLE statement.
611
     *
612
     * @param string $tableName  The name of the table to generate the clause for.
613
     * @param string $columnName The name of the column to generate the clause for.
614
     *
615
     * @return string
616
     */
617 18
    private function getAlterTableDropDefaultConstraintClause($tableName, $columnName)
618
    {
619 18
        return 'DROP CONSTRAINT ' . $this->generateDefaultConstraintName($tableName, $columnName);
620
    }
621
622
    /**
623
     * Checks whether a column alteration requires dropping its default constraint first.
624
     *
625
     * Different to other database vendors SQL Server implements column default values
626
     * as constraints and therefore changes in a column's default value as well as changes
627
     * in a column's type require dropping the default constraint first before being to
628
     * alter the particular column to the new definition.
629
     *
630
     * @param ColumnDiff $columnDiff The column diff to evaluate.
631
     *
632
     * @return boolean True if the column alteration requires dropping its default constraint first, false otherwise.
633
     */
634 33
    private function alterColumnRequiresDropDefaultConstraint(ColumnDiff $columnDiff)
635
    {
636
        // We can only decide whether to drop an existing default constraint
637
        // if we know the original default value.
638 33
        if ( ! $columnDiff->fromColumn instanceof Column) {
639 12
            return false;
640
        }
641
642
        // We only need to drop an existing default constraint if we know the
643
        // column was defined with a default value before.
644 21
        if ($columnDiff->fromColumn->getDefault() === null) {
645 6
            return false;
646
        }
647
648
        // We need to drop an existing default constraint if the column was
649
        // defined with a default value before and it has changed.
650 15
        if ($columnDiff->hasChanged('default')) {
651 12
            return true;
652
        }
653
654
        // We need to drop an existing default constraint if the column was
655
        // defined with a default value before and the native column type has changed.
656 3
        if ($columnDiff->hasChanged('type') || $columnDiff->hasChanged('fixed')) {
657 3
            return true;
658
        }
659
660
        return false;
661
    }
662
663
    /**
664
     * Returns the SQL statement for altering a column comment.
665
     *
666
     * SQL Server does not support native column comments,
667
     * therefore the extended properties functionality is used
668
     * as a workaround to store them.
669
     * The property name used to store column comments is "MS_Description"
670
     * which provides compatibility with SQL Server Management Studio,
671
     * as column comments are stored in the same property there when
672
     * specifying a column's "Description" attribute.
673
     *
674
     * @param string $tableName  The quoted table name to which the column belongs.
675
     * @param string $columnName The quoted column name to alter the comment for.
676
     * @param string $comment    The column's comment.
677
     *
678
     * @return string
679
     */
680 3
    protected function getAlterColumnCommentSQL($tableName, $columnName, $comment)
681
    {
682 3
        return $this->getUpdateExtendedPropertySQL(
683 3
            'MS_Description',
684 3
            $comment,
685 3
            'SCHEMA',
686 3
            'dbo',
687 3
            'TABLE',
688 3
            $tableName,
689 3
            'COLUMN',
690 3
            $columnName
691
        );
692
    }
693
694
    /**
695
     * Returns the SQL statement for dropping a column comment.
696
     *
697
     * SQL Server does not support native column comments,
698
     * therefore the extended properties functionality is used
699
     * as a workaround to store them.
700
     * The property name used to store column comments is "MS_Description"
701
     * which provides compatibility with SQL Server Management Studio,
702
     * as column comments are stored in the same property there when
703
     * specifying a column's "Description" attribute.
704
     *
705
     * @param string $tableName  The quoted table name to which the column belongs.
706
     * @param string $columnName The quoted column name to drop the comment for.
707
     *
708
     * @return string
709
     */
710 3
    protected function getDropColumnCommentSQL($tableName, $columnName)
711
    {
712 3
        return $this->getDropExtendedPropertySQL(
713 3
            'MS_Description',
714 3
            'SCHEMA',
715 3
            'dbo',
716 3
            'TABLE',
717 3
            $tableName,
718 3
            'COLUMN',
719 3
            $columnName
720
        );
721
    }
722
723
    /**
724
     * {@inheritdoc}
725
     */
726 15
    protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
727
    {
728
        return array(
729 15
            sprintf(
730 15
                "EXEC sp_RENAME N'%s.%s', N'%s', N'INDEX'",
731 15
                $tableName,
732 15
                $oldIndexName,
733 15
                $index->getQuotedName($this)
734
            )
735
        );
736
    }
737
738
    /**
739
     * Returns the SQL statement for adding an extended property to a database object.
740
     *
741
     * @param string      $name       The name of the property to add.
742
     * @param string|null $value      The value of the property to add.
743
     * @param string|null $level0Type The type of the object at level 0 the property belongs to.
744
     * @param string|null $level0Name The name of the object at level 0 the property belongs to.
745
     * @param string|null $level1Type The type of the object at level 1 the property belongs to.
746
     * @param string|null $level1Name The name of the object at level 1 the property belongs to.
747
     * @param string|null $level2Type The type of the object at level 2 the property belongs to.
748
     * @param string|null $level2Name The name of the object at level 2 the property belongs to.
749
     *
750
     * @return string
751
     *
752
     * @link http://msdn.microsoft.com/en-us/library/ms180047%28v=sql.90%29.aspx
753
     */
754 15 View Code Duplication
    public function getAddExtendedPropertySQL(
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...
755
        $name,
756
        $value = null,
757
        $level0Type = null,
758
        $level0Name = null,
759
        $level1Type = null,
760
        $level1Name = null,
761
        $level2Type = null,
762
        $level2Name = null
763
    ) {
764
        return "EXEC sp_addextendedproperty " .
765 15
            "N" . $this->quoteStringLiteral($name) . ", N" . $this->quoteStringLiteral($value) . ", " .
766 15
            "N" . $this->quoteStringLiteral($level0Type) . ", " . $level0Name . ', ' .
767 15
            "N" . $this->quoteStringLiteral($level1Type) . ", " . $level1Name . ', ' .
768 15
            "N" . $this->quoteStringLiteral($level2Type) . ", " . $level2Name;
769
    }
770
771
    /**
772
     * Returns the SQL statement for dropping an extended property from a database object.
773
     *
774
     * @param string      $name       The name of the property to drop.
775
     * @param string|null $level0Type The type of the object at level 0 the property belongs to.
776
     * @param string|null $level0Name The name of the object at level 0 the property belongs to.
777
     * @param string|null $level1Type The type of the object at level 1 the property belongs to.
778
     * @param string|null $level1Name The name of the object at level 1 the property belongs to.
779
     * @param string|null $level2Type The type of the object at level 2 the property belongs to.
780
     * @param string|null $level2Name The name of the object at level 2 the property belongs to.
781
     *
782
     * @return string
783
     *
784
     * @link http://technet.microsoft.com/en-gb/library/ms178595%28v=sql.90%29.aspx
785
     */
786 3
    public function getDropExtendedPropertySQL(
787
        $name,
788
        $level0Type = null,
789
        $level0Name = null,
790
        $level1Type = null,
791
        $level1Name = null,
792
        $level2Type = null,
793
        $level2Name = null
794
    ) {
795
        return "EXEC sp_dropextendedproperty " .
796 3
            "N" . $this->quoteStringLiteral($name) . ", " .
797 3
            "N" . $this->quoteStringLiteral($level0Type) . ", " . $level0Name . ', ' .
798 3
            "N" . $this->quoteStringLiteral($level1Type) . ", " . $level1Name . ', ' .
799 3
            "N" . $this->quoteStringLiteral($level2Type) . ", " . $level2Name;
800
    }
801
802
    /**
803
     * Returns the SQL statement for updating an extended property of a database object.
804
     *
805
     * @param string      $name       The name of the property to update.
806
     * @param string|null $value      The value of the property to update.
807
     * @param string|null $level0Type The type of the object at level 0 the property belongs to.
808
     * @param string|null $level0Name The name of the object at level 0 the property belongs to.
809
     * @param string|null $level1Type The type of the object at level 1 the property belongs to.
810
     * @param string|null $level1Name The name of the object at level 1 the property belongs to.
811
     * @param string|null $level2Type The type of the object at level 2 the property belongs to.
812
     * @param string|null $level2Name The name of the object at level 2 the property belongs to.
813
     *
814
     * @return string
815
     *
816
     * @link http://msdn.microsoft.com/en-us/library/ms186885%28v=sql.90%29.aspx
817
     */
818 3 View Code Duplication
    public function getUpdateExtendedPropertySQL(
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...
819
        $name,
820
        $value = null,
821
        $level0Type = null,
822
        $level0Name = null,
823
        $level1Type = null,
824
        $level1Name = null,
825
        $level2Type = null,
826
        $level2Name = null
827
    ) {
828
        return "EXEC sp_updateextendedproperty " .
829 3
        "N" . $this->quoteStringLiteral($name) . ", N" . $this->quoteStringLiteral($value) . ", " .
830 3
        "N" . $this->quoteStringLiteral($level0Type) . ", " . $level0Name . ', ' .
831 3
        "N" . $this->quoteStringLiteral($level1Type) . ", " . $level1Name . ', ' .
832 3
        "N" . $this->quoteStringLiteral($level2Type) . ", " . $level2Name;
833
    }
834
835
    /**
836
     * {@inheritDoc}
837
     */
838
    public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName)
839
    {
840
        return 'INSERT INTO ' . $quotedTableName . ' DEFAULT VALUES';
841
    }
842
843
    /**
844
     * {@inheritDoc}
845
     */
846
    public function getListTablesSQL()
847
    {
848
        // "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams
849
        // Category 2 must be ignored as it is "MS SQL Server 'pseudo-system' object[s]" for replication
850
        return "SELECT name FROM sysobjects WHERE type = 'U' AND name != 'sysdiagrams' AND category != 2 ORDER BY name";
851
    }
852
853
    /**
854
     * {@inheritDoc}
855
     */
856 6
    public function getListTableColumnsSQL($table, $database = null)
857
    {
858
        return "SELECT    col.name,
859
                          type.name AS type,
860
                          col.max_length AS length,
861
                          ~col.is_nullable AS notnull,
862
                          def.definition AS [default],
863
                          col.scale,
864
                          col.precision,
865
                          col.is_identity AS autoincrement,
866
                          col.collation_name AS collation,
867
                          CAST(prop.value AS NVARCHAR(MAX)) AS comment -- CAST avoids driver error for sql_variant type
868
                FROM      sys.columns AS col
869
                JOIN      sys.types AS type
870
                ON        col.user_type_id = type.user_type_id
871
                JOIN      sys.objects AS obj
872
                ON        col.object_id = obj.object_id
873
                JOIN      sys.schemas AS scm
874
                ON        obj.schema_id = scm.schema_id
875
                LEFT JOIN sys.default_constraints def
876
                ON        col.default_object_id = def.object_id
877
                AND       col.object_id = def.parent_object_id
878
                LEFT JOIN sys.extended_properties AS prop
879
                ON        obj.object_id = prop.major_id
880
                AND       col.column_id = prop.minor_id
881
                AND       prop.name = 'MS_Description'
882
                WHERE     obj.type = 'U'
883 6
                AND       " . $this->getTableWhereClause($table, 'scm.name', 'obj.name');
884
    }
885
886
    /**
887
     * {@inheritDoc}
888
     */
889 6
    public function getListTableForeignKeysSQL($table, $database = null)
0 ignored issues
show
Unused Code introduced by
The parameter $database is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
890
    {
891
        return "SELECT f.name AS ForeignKey,
892
                SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName,
893
                OBJECT_NAME (f.parent_object_id) AS TableName,
894
                COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName,
895
                SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName,
896
                OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName,
897
                COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName,
898
                f.delete_referential_action_desc,
899
                f.update_referential_action_desc
900
                FROM sys.foreign_keys AS f
901
                INNER JOIN sys.foreign_key_columns AS fc
902
                INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id
903
                ON f.OBJECT_ID = fc.constraint_object_id
904
                WHERE " .
905 6
                $this->getTableWhereClause($table, 'SCHEMA_NAME (f.schema_id)', 'OBJECT_NAME (f.parent_object_id)');
906
    }
907
908
    /**
909
     * {@inheritDoc}
910
     */
911 6
    public function getListTableIndexesSQL($table, $currentDatabase = null)
912
    {
913
        return "SELECT idx.name AS key_name,
914
                       col.name AS column_name,
915
                       ~idx.is_unique AS non_unique,
916
                       idx.is_primary_key AS [primary],
917
                       CASE idx.type
918
                           WHEN '1' THEN 'clustered'
919
                           WHEN '2' THEN 'nonclustered'
920
                           ELSE NULL
921
                       END AS flags
922
                FROM sys.tables AS tbl
923
                JOIN sys.schemas AS scm ON tbl.schema_id = scm.schema_id
924
                JOIN sys.indexes AS idx ON tbl.object_id = idx.object_id
925
                JOIN sys.index_columns AS idxcol ON idx.object_id = idxcol.object_id AND idx.index_id = idxcol.index_id
926
                JOIN sys.columns AS col ON idxcol.object_id = col.object_id AND idxcol.column_id = col.column_id
927 6
                WHERE " . $this->getTableWhereClause($table, 'scm.name', 'tbl.name') . "
928
                ORDER BY idx.index_id ASC, idxcol.key_ordinal ASC";
929
    }
930
931
    /**
932
     * {@inheritDoc}
933
     */
934
    public function getCreateViewSQL($name, $sql)
935
    {
936
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
937
    }
938
939
    /**
940
     * {@inheritDoc}
941
     */
942
    public function getListViewsSQL($database)
943
    {
944
        return "SELECT name FROM sysobjects WHERE type = 'V' ORDER BY name";
945
    }
946
947
    /**
948
     * Returns the where clause to filter schema and table name in a query.
949
     *
950
     * @param string $table        The full qualified name of the table.
951
     * @param string $schemaColumn The name of the column to compare the schema to in the where clause.
952
     * @param string $tableColumn  The name of the column to compare the table to in the where clause.
953
     *
954
     * @return string
955
     */
956 18 View Code Duplication
    private function getTableWhereClause($table, $schemaColumn, $tableColumn)
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...
957
    {
958 18
        if (strpos($table, ".") !== false) {
959 9
            list($schema, $table) = explode(".", $table);
960 9
            $schema = $this->quoteStringLiteral($schema);
961 9
            $table = $this->quoteStringLiteral($table);
962
        } else {
963 9
            $schema = "SCHEMA_NAME()";
964 9
            $table = $this->quoteStringLiteral($table);
965
        }
966
967 18
        return "({$tableColumn} = {$table} AND {$schemaColumn} = {$schema})";
968
    }
969
970
    /**
971
     * {@inheritDoc}
972
     */
973
    public function getDropViewSQL($name)
974
    {
975
        return 'DROP VIEW ' . $name;
976
    }
977
978
    /**
979
     * {@inheritDoc}
980
     */
981
    public function getGuidExpression()
982
    {
983
        return 'NEWID()';
984
    }
985
986
    /**
987
     * {@inheritDoc}
988
     */
989 View Code Duplication
    public function getLocateExpression($str, $substr, $startPos = false)
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...
990
    {
991
        if ($startPos == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
992
            return 'CHARINDEX(' . $substr . ', ' . $str . ')';
993
        }
994
995
        return 'CHARINDEX(' . $substr . ', ' . $str . ', ' . $startPos . ')';
996
    }
997
998
    /**
999
     * {@inheritDoc}
1000
     */
1001
    public function getModExpression($expression1, $expression2)
1002
    {
1003
        return $expression1 . ' % ' . $expression2;
1004
    }
1005
1006
    /**
1007
     * {@inheritDoc}
1008
     */
1009
    public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false)
1010
    {
1011
        if ( ! $char) {
1012 View Code Duplication
            switch ($pos) {
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...
1013
                case self::TRIM_LEADING:
1014
                    $trimFn = 'LTRIM';
1015
                    break;
1016
1017
                case self::TRIM_TRAILING:
1018
                    $trimFn = 'RTRIM';
1019
                    break;
1020
1021
                default:
1022
                    return 'LTRIM(RTRIM(' . $str . '))';
1023
            }
1024
1025
            return $trimFn . '(' . $str . ')';
1026
        }
1027
1028
        /** Original query used to get those expressions
1029
          declare @c varchar(100) = 'xxxBarxxx', @trim_char char(1) = 'x';
1030
          declare @pat varchar(10) = '%[^' + @trim_char + ']%';
1031
          select @c as string
1032
          , @trim_char as trim_char
1033
          , stuff(@c, 1, patindex(@pat, @c) - 1, null) as trim_leading
1034
          , reverse(stuff(reverse(@c), 1, patindex(@pat, reverse(@c)) - 1, null)) as trim_trailing
1035
          , reverse(stuff(reverse(stuff(@c, 1, patindex(@pat, @c) - 1, null)), 1, patindex(@pat, reverse(stuff(@c, 1, patindex(@pat, @c) - 1, null))) - 1, null)) as trim_both;
1036
         */
1037
        $pattern = "'%[^' + $char + ']%'";
1038
1039 View Code Duplication
        if ($pos == self::TRIM_LEADING) {
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...
1040
            return 'stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)';
1041
        }
1042
1043 View Code Duplication
        if ($pos == self::TRIM_TRAILING) {
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...
1044
            return 'reverse(stuff(reverse(' . $str . '), 1, patindex(' . $pattern . ', reverse(' . $str . ')) - 1, null))';
1045
        }
1046
1047
        return 'reverse(stuff(reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)), 1, patindex(' . $pattern . ', reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null))) - 1, null))';
1048
    }
1049
1050
    /**
1051
     * {@inheritDoc}
1052
     */
1053 3
    public function getConcatExpression()
1054
    {
1055 3
        $args = func_get_args();
1056
1057 3
        return '(' . implode(' + ', $args) . ')';
1058
    }
1059
1060
    /**
1061
     * {@inheritDoc}
1062
     */
1063 3
    public function getListDatabasesSQL()
1064
    {
1065 3
        return 'SELECT * FROM sys.databases';
1066
    }
1067
1068
    /**
1069
     * {@inheritDoc}
1070
     */
1071
    public function getListNamespacesSQL()
1072
    {
1073
        return "SELECT name FROM sys.schemas WHERE name NOT IN('guest', 'INFORMATION_SCHEMA', 'sys')";
1074
    }
1075
1076
    /**
1077
     * {@inheritDoc}
1078
     */
1079
    public function getSubstringExpression($value, $from, $length = null)
1080
    {
1081
        if (!is_null($length)) {
1082
            return 'SUBSTRING(' . $value . ', ' . $from . ', ' . $length . ')';
1083
        }
1084
1085
        return 'SUBSTRING(' . $value . ', ' . $from . ', LEN(' . $value . ') - ' . $from . ' + 1)';
1086
    }
1087
1088
    /**
1089
     * {@inheritDoc}
1090
     */
1091
    public function getLengthExpression($column)
1092
    {
1093
        return 'LEN(' . $column . ')';
1094
    }
1095
1096
    /**
1097
     * {@inheritDoc}
1098
     */
1099 3
    public function getSetTransactionIsolationSQL($level)
1100
    {
1101 3
        return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
1102
    }
1103
1104
    /**
1105
     * {@inheritDoc}
1106
     */
1107 37
    public function getIntegerTypeDeclarationSQL(array $field)
1108
    {
1109 37
        return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
1110
    }
1111
1112
    /**
1113
     * {@inheritDoc}
1114
     */
1115
    public function getBigIntTypeDeclarationSQL(array $field)
1116
    {
1117
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
1118
    }
1119
1120
    /**
1121
     * {@inheritDoc}
1122
     */
1123
    public function getSmallIntTypeDeclarationSQL(array $field)
1124
    {
1125
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
1126
    }
1127
1128
    /**
1129
     * {@inheritDoc}
1130
     */
1131 3
    public function getGuidTypeDeclarationSQL(array $field)
1132
    {
1133 3
        return 'UNIQUEIDENTIFIER';
1134
    }
1135
1136
    /**
1137
     * {@inheritDoc}
1138
     */
1139 69
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
1140
    {
1141 69
        return $fixed ? ($length ? 'NCHAR(' . $length . ')' : 'CHAR(255)') : ($length ? 'NVARCHAR(' . $length . ')' : 'NVARCHAR(255)');
1142
    }
1143
1144
    /**
1145
     * {@inheritdoc}
1146
     */
1147 3
    protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
1148
    {
1149 3
        return $fixed ? 'BINARY(' . ($length ?: 255) . ')' : 'VARBINARY(' . ($length ?: 255) . ')';
1150
    }
1151
1152
    /**
1153
     * {@inheritdoc}
1154
     */
1155 6
    public function getBinaryMaxLength()
1156
    {
1157 6
        return 8000;
1158
    }
1159
1160
    /**
1161
     * {@inheritdoc}
1162
     */
1163
    public function getInsertMaxRows()
1164
    {
1165
        return 1000;
1166
    }
1167
1168
    /**
1169
     * {@inheritDoc}
1170
     */
1171 5
    public function getClobTypeDeclarationSQL(array $field)
1172
    {
1173 5
        return 'VARCHAR(MAX)';
1174
    }
1175
1176
    /**
1177
     * {@inheritDoc}
1178
     */
1179 37
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
1180
    {
1181 37
        return (!empty($columnDef['autoincrement'])) ? ' IDENTITY' : '';
1182
    }
1183
1184
    /**
1185
     * {@inheritDoc}
1186
     */
1187
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
1188
    {
1189
        return 'DATETIME';
1190
    }
1191
1192
    /**
1193
     * {@inheritDoc}
1194
     */
1195
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
1196
    {
1197
        return 'DATETIME';
1198
    }
1199
1200
    /**
1201
     * {@inheritDoc}
1202
     */
1203
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
1204
    {
1205
        return 'DATETIME';
1206
    }
1207
1208
    /**
1209
     * {@inheritDoc}
1210
     */
1211 3
    public function getBooleanTypeDeclarationSQL(array $field)
1212
    {
1213 3
        return 'BIT';
1214
    }
1215
1216
    /**
1217
     * {@inheritDoc}
1218
     */
1219 40
    protected function doModifyLimitQuery($query, $limit, $offset = null)
1220
    {
1221 40
        if ($limit === null) {
1222
            return $query;
1223
        }
1224
1225 40
        $start   = $offset + 1;
1226 40
        $end     = $offset + $limit;
1227
1228
        // We'll find a SELECT or SELECT distinct and prepend TOP n to it
1229
        // Even if the TOP n is very large, the use of a CTE will
1230
        // allow the SQL Server query planner to optimize it so it doesn't
1231
        // actually scan the entire range covered by the TOP clause.
1232 40
        $selectPattern = '/^(\s*SELECT\s+(?:DISTINCT\s+)?)(.*)$/im';
1233 40
        $replacePattern = sprintf('$1%s $2', "TOP $end");
1234 40
        $query = preg_replace($selectPattern, $replacePattern, $query);
1235
1236 40
        if (stristr($query, "ORDER BY")) {
1237
            // Inner order by is not valid in SQL Server for our purposes
1238
            // unless it's in a TOP N subquery.
1239 26
            $query = $this->scrubInnerOrderBy($query);
1240
        }
1241
1242
        // Build a new limited query around the original, using a CTE
1243 40
        return sprintf(
1244
            "WITH dctrn_cte AS (%s) "
1245
            . "SELECT * FROM ("
1246
            . "SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS doctrine_rownum FROM dctrn_cte"
1247
            . ") AS doctrine_tbl "
1248 40
            . "WHERE doctrine_rownum BETWEEN %d AND %d ORDER BY doctrine_rownum ASC",
1249 40
            $query,
1250 40
            $start,
1251 40
            $end
1252
        );
1253
    }
1254
1255
    /**
1256
     * Remove ORDER BY clauses in subqueries - they're not supported by SQL Server.
1257
     * Caveat: will leave ORDER BY in TOP N subqueries.
1258
     *
1259
     * @param $query
1260
     * @return string
1261
     */
1262 26
    private function scrubInnerOrderBy($query)
1263
    {
1264 26
        $count = substr_count(strtoupper($query), 'ORDER BY');
1265 26
        $offset = 0;
1266
1267 26
        while ($count-- > 0) {
1268 26
            $orderByPos = stripos($query, ' ORDER BY', $offset);
1269 26
            if ($orderByPos === false) {
1270 2
                break;
1271
            }
1272
1273 26
            $qLen = strlen($query);
1274 26
            $parenCount = 0;
1275 26
            $currentPosition = $orderByPos;
1276
1277 26
            while ($parenCount >= 0 && $currentPosition < $qLen) {
1278 26 View Code Duplication
                if ($query[$currentPosition] === '(') {
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...
1279 2
                    $parenCount++;
1280 26
                } elseif ($query[$currentPosition] === ')') {
1281 14
                    $parenCount--;
1282
                }
1283
1284 26
                $currentPosition++;
1285
            }
1286
1287 26
            if ($this->isOrderByInTopNSubquery($query, $orderByPos)) {
1288
                // If the order by clause is in a TOP N subquery, do not remove
1289
                // it and continue iteration from the current position.
1290 23
                $offset = $currentPosition;
1291 23
                continue;
1292
            }
1293
1294 10
            if ($currentPosition < $qLen - 1) {
1295 10
                $query = substr($query, 0, $orderByPos) . substr($query, $currentPosition - 1);
1296 10
                $offset = $orderByPos;
1297
            }
1298
        }
1299 26
        return $query;
1300
    }
1301
1302
    /**
1303
     * Check an ORDER BY clause to see if it is in a TOP N query or subquery.
1304
     *
1305
     * @param string $query The query
1306
     * @param int $currentPosition Start position of ORDER BY clause
1307
     * @return bool true if ORDER BY is in a TOP N query, false otherwise
1308
     */
1309 26
    private function isOrderByInTopNSubquery($query, $currentPosition)
1310
    {
1311
        // Grab query text on the same nesting level as the ORDER BY clause we're examining.
1312 26
        $subQueryBuffer = '';
1313 26
        $parenCount = 0;
1314
1315
        // If $parenCount goes negative, we've exited the subquery we're examining.
1316
        // If $currentPosition goes negative, we've reached the beginning of the query.
1317 26
        while ($parenCount >= 0 && $currentPosition >= 0) {
1318 26 View Code Duplication
            if ($query[$currentPosition] === '(') {
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...
1319 17
                $parenCount--;
1320 26
            } elseif ($query[$currentPosition] === ')') {
1321 14
                $parenCount++;
1322
            }
1323
1324
            // Only yank query text on the same nesting level as the ORDER BY clause.
1325 26
            $subQueryBuffer = ($parenCount === 0 ? $query[$currentPosition] : ' ') . $subQueryBuffer;
1326
1327 26
            $currentPosition--;
1328
        }
1329
1330 26
        if (preg_match('/SELECT\s+(DISTINCT\s+)?TOP\s/i', $subQueryBuffer)) {
1331 23
            return true;
1332
        }
1333
1334 10
        return false;
1335
    }
1336
1337
    /**
1338
     * {@inheritDoc}
1339
     */
1340
    public function supportsLimitOffset()
1341
    {
1342
        return false;
1343
    }
1344
1345
    /**
1346
     * {@inheritDoc}
1347
     */
1348 3 View Code Duplication
    public function convertBooleans($item)
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...
1349
    {
1350 3
        if (is_array($item)) {
1351
            foreach ($item as $key => $value) {
1352
                if (is_bool($value) || is_numeric($item)) {
1353
                    $item[$key] = ($value) ? 1 : 0;
1354
                }
1355
            }
1356 3
        } elseif (is_bool($item) || is_numeric($item)) {
1357 3
            $item = ($item) ? 1 : 0;
1358
        }
1359
1360 3
        return $item;
1361
    }
1362
1363
    /**
1364
     * {@inheritDoc}
1365
     */
1366
    public function getCreateTemporaryTableSnippetSQL()
1367
    {
1368
        return "CREATE TABLE";
1369
    }
1370
1371
    /**
1372
     * {@inheritDoc}
1373
     */
1374
    public function getTemporaryTableName($tableName)
1375
    {
1376
        return '#' . $tableName;
1377
    }
1378
1379
    /**
1380
     * {@inheritDoc}
1381
     */
1382
    public function getDateTimeFormatString()
1383
    {
1384
        return 'Y-m-d H:i:s.000';
1385
    }
1386
1387
    /**
1388
     * {@inheritDoc}
1389
     */
1390
    public function getDateFormatString()
1391
    {
1392
        return 'Y-m-d H:i:s.000';
1393
    }
1394
1395
    /**
1396
     * {@inheritDoc}
1397
     */
1398
    public function getTimeFormatString()
1399
    {
1400
        return 'Y-m-d H:i:s.000';
1401
    }
1402
1403
    /**
1404
     * {@inheritDoc}
1405
     */
1406
    public function getDateTimeTzFormatString()
1407
    {
1408
        return $this->getDateTimeFormatString();
1409
    }
1410
1411
    /**
1412
     * {@inheritDoc}
1413
     */
1414 1
    public function getName()
1415
    {
1416 1
        return 'mssql';
1417
    }
1418
1419
    /**
1420
     * {@inheritDoc}
1421
     */
1422 15
    protected function initializeDoctrineTypeMappings()
1423
    {
1424 15
        $this->doctrineTypeMapping = array(
1425
            'bigint' => 'bigint',
1426
            'numeric' => 'decimal',
1427
            'bit' => 'boolean',
1428
            'smallint' => 'smallint',
1429
            'decimal' => 'decimal',
1430
            'smallmoney' => 'integer',
1431
            'int' => 'integer',
1432
            'tinyint' => 'smallint',
1433
            'money' => 'integer',
1434
            'float' => 'float',
1435
            'real' => 'float',
1436
            'double' => 'float',
1437
            'double precision' => 'float',
1438
            'smalldatetime' => 'datetime',
1439
            'datetime' => 'datetime',
1440
            'char' => 'string',
1441
            'varchar' => 'string',
1442
            'text' => 'text',
1443
            'nchar' => 'string',
1444
            'nvarchar' => 'string',
1445
            'ntext' => 'text',
1446
            'binary' => 'binary',
1447
            'varbinary' => 'binary',
1448
            'image' => 'blob',
1449
            'uniqueidentifier' => 'guid',
1450
        );
1451 15
    }
1452
1453
    /**
1454
     * {@inheritDoc}
1455
     */
1456
    public function createSavePoint($savepoint)
1457
    {
1458
        return 'SAVE TRANSACTION ' . $savepoint;
1459
    }
1460
1461
    /**
1462
     * {@inheritDoc}
1463
     */
1464
    public function releaseSavePoint($savepoint)
1465
    {
1466
        return '';
1467
    }
1468
1469
    /**
1470
     * {@inheritDoc}
1471
     */
1472
    public function rollbackSavePoint($savepoint)
1473
    {
1474
        return 'ROLLBACK TRANSACTION ' . $savepoint;
1475
    }
1476
1477
    /**
1478
     * {@inheritdoc}
1479
     */
1480 21
    public function getForeignKeyReferentialActionSQL($action)
1481
    {
1482
        // RESTRICT is not supported, therefore falling back to NO ACTION.
1483 21
        if (strtoupper($action) === 'RESTRICT') {
1484 3
            return 'NO ACTION';
1485
        }
1486
1487 18
        return parent::getForeignKeyReferentialActionSQL($action);
1488
    }
1489
1490
    /**
1491
     * {@inheritDoc}
1492
     */
1493 7 View Code Duplication
    public function appendLockHint($fromClause, $lockMode)
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...
1494
    {
1495
        switch (true) {
1496 7
            case LockMode::NONE === $lockMode:
1497 1
                return $fromClause . ' WITH (NOLOCK)';
1498
1499 6
            case LockMode::PESSIMISTIC_READ === $lockMode:
1500 1
                return $fromClause . ' WITH (HOLDLOCK, ROWLOCK)';
1501
1502 5
            case LockMode::PESSIMISTIC_WRITE === $lockMode:
1503 1
                return $fromClause . ' WITH (UPDLOCK, ROWLOCK)';
1504
1505
            default:
1506 4
                return $fromClause;
1507
        }
1508
    }
1509
1510
    /**
1511
     * {@inheritDoc}
1512
     */
1513
    public function getForUpdateSQL()
1514
    {
1515
        return ' ';
1516
    }
1517
1518
    /**
1519
     * {@inheritDoc}
1520
     */
1521 57
    protected function getReservedKeywordsClass()
1522
    {
1523 57
        return 'Doctrine\DBAL\Platforms\Keywords\SQLServerKeywords';
1524
    }
1525
1526
    /**
1527
     * {@inheritDoc}
1528
     */
1529 91
    public function quoteSingleIdentifier($str)
1530
    {
1531 91
        return "[" . str_replace("]", "][", $str) . "]";
1532
    }
1533
1534
    /**
1535
     * {@inheritDoc}
1536
     */
1537 3
    public function getTruncateTableSQL($tableName, $cascade = false)
1538
    {
1539 3
        $tableIdentifier = new Identifier($tableName);
1540
1541 3
        return 'TRUNCATE TABLE ' . $tableIdentifier->getQuotedName($this);
1542
    }
1543
1544
    /**
1545
     * {@inheritDoc}
1546
     */
1547 3
    public function getBlobTypeDeclarationSQL(array $field)
1548
    {
1549 3
        return 'VARBINARY(MAX)';
1550
    }
1551
1552
    /**
1553
     * {@inheritDoc}
1554
     */
1555 54
    public function getDefaultValueDeclarationSQL($field)
1556
    {
1557 54
        if ( ! isset($field['default'])) {
1558
            return empty($field['notnull']) ? ' NULL' : '';
1559
        }
1560
1561 54
        if ( ! isset($field['type'])) {
1562 12
            return " DEFAULT '" . $field['default'] . "'";
1563
        }
1564
1565 42
        if (in_array((string) $field['type'], array('Integer', 'BigInt', 'SmallInt'))) {
1566 9
            return " DEFAULT " . $field['default'];
1567
        }
1568
1569 36 View Code Duplication
        if (in_array((string) $field['type'], array('DateTime', 'DateTimeTz')) && $field['default'] == $this->getCurrentTimestampSQL()) {
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...
1570 3
            return " DEFAULT " . $this->getCurrentTimestampSQL();
1571
        }
1572
1573 33 View Code Duplication
        if ((string) $field['type'] == 'Boolean') {
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...
1574 3
            return " DEFAULT '" . $this->convertBooleans($field['default']) . "'";
1575
        }
1576
1577 33
        return " DEFAULT '" . $field['default'] . "'";
1578
    }
1579
1580
    /**
1581
     * {@inheritdoc}
1582
     *
1583
     * Modifies column declaration order as it differs in Microsoft SQL Server.
1584
     */
1585 85
    public function getColumnDeclarationSQL($name, array $field)
1586
    {
1587 85
        if (isset($field['columnDefinition'])) {
1588 3
            $columnDef = $this->getCustomTypeDeclarationSQL($field);
1589
        } else {
1590 82
            $collation = (isset($field['collation']) && $field['collation']) ?
1591 82
                ' ' . $this->getColumnCollationDeclarationSQL($field['collation']) : '';
1592
1593 82
            $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : '';
1594
1595 82
            $unique = (isset($field['unique']) && $field['unique']) ?
1596 82
                ' ' . $this->getUniqueFieldDeclarationSQL() : '';
1597
1598 82
            $check = (isset($field['check']) && $field['check']) ?
1599 82
                ' ' . $field['check'] : '';
1600
1601 82
            $typeDecl = $field['type']->getSQLDeclaration($field, $this);
1602 82
            $columnDef = $typeDecl . $collation . $notnull . $unique . $check;
1603
        }
1604
1605 85
        return $name . ' ' . $columnDef;
1606
    }
1607
1608
    /**
1609
     * Returns a unique default constraint name for a table and column.
1610
     *
1611
     * @param string $table  Name of the table to generate the unique default constraint name for.
1612
     * @param string $column Name of the column in the table to generate the unique default constraint name for.
1613
     *
1614
     * @return string
1615
     */
1616 45
    private function generateDefaultConstraintName($table, $column)
1617
    {
1618 45
        return 'DF_' . $this->generateIdentifierName($table) . '_' . $this->generateIdentifierName($column);
1619
    }
1620
1621
    /**
1622
     * Returns a hash value for a given identifier.
1623
     *
1624
     * @param string $identifier Identifier to generate a hash value for.
1625
     *
1626
     * @return string
1627
     */
1628 48
    private function generateIdentifierName($identifier)
1629
    {
1630
        // Always generate name for unquoted identifiers to ensure consistency.
1631 48
        $identifier = new Identifier($identifier);
1632
1633 48
        return strtoupper(dechex(crc32($identifier->getName())));
1634
    }
1635
}
1636