MsSqlPlatform.php 21.9 KB
Newer Older
1
<?php
2

3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information, see
 * <http://www.doctrine-project.org>.
 */
20

21
namespace Doctrine\DBAL\Platforms;
22

23 24
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\DBALException;
25 26
use Doctrine\DBAL\Schema\Index,
    Doctrine\DBAL\Schema\Table;
27 28 29 30 31 32 33 34

/**
 * The MsSqlPlatform provides the behavior, features and SQL dialect of the
 * MySQL database platform.
 *
 * @since 2.0
 * @author Roman Borschel <roman@code-factory.org>
 * @author Jonathan H. Wage <jonwage@gmail.com>
35
 * @author Benjamin Eberlei <kontakt@beberlei.de>
36
 * @todo Rename: MsSQLPlatform
37
 */
38
class MsSqlPlatform extends AbstractPlatform
39
{
40

41
    /**
42 43 44
     * Whether the platform prefers identity columns for ID generation.
     * MsSql prefers "autoincrement" identity columns since sequences can only
     * be emulated with a table.
45
     *
46
     * @return boolean
47 48
     * @override
     */
49
    public function prefersIdentityColumns()
50
    {
51 52
        return true;
    }
53

54 55 56 57 58 59 60 61 62 63 64 65 66
    /**
     * Whether the platform supports identity columns.
     * MsSql supports this through AUTO_INCREMENT columns.
     *
     * @return boolean
     * @override
     */
    public function supportsIdentityColumns()
    {
        return true;
    }

    /**
67
     * Whether the platform supports releasing savepoints.
68 69 70
     *
     * @return boolean
     */
71
    public function supportsReleaseSavepoints()
72 73 74
    {
        return false;
    }
75

76 77 78 79 80 81 82 83 84 85 86
    /**
     * create a new database
     *
     * @param string $name name of the database that should be created
     * @return string
     * @override
     */
    public function getCreateDatabaseSQL($name)
    {
        return 'CREATE DATABASE ' . $name;
    }
87

88 89 90 91 92 93 94 95 96
    /**
     * drop an existing database
     *
     * @param string $name name of the database that should be dropped
     * @return string
     * @override
     */
    public function getDropDatabaseSQL($name)
    {
97 98 99 100 101 102
        return 'DROP DATABASE ' . $name;
    }

    /**
     * @override
     */
103
    public function supportsCreateDropDatabase()
104 105
    {
        return false;
106 107 108 109 110 111 112 113
    }

    /**
     * @override
     */
    public function getDropForeignKeySQL($foreignKey, $table)
    {
        if ($foreignKey instanceof \Doctrine\DBAL\Schema\ForeignKeyConstraint) {
114
            $foreignKey = $foreignKey->getQuotedName($this);
115 116
        }

117
        if ($table instanceof \Doctrine\DBAL\Schema\Table) {
118
            $table = $table->getQuotedName($this);
119 120 121
        }

        return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey;
122
    }
123 124

    /**
125 126
     * @override
     */
127
    public function getDropIndexSQL($index, $table=null)
128
    {
129 130
        if ($index instanceof \Doctrine\DBAL\Schema\Index) {
            $index_ = $index;
131
            $index = $index->getQuotedName($this);
132
        } else if (!is_string($index)) {
133 134 135
            throw new \InvalidArgumentException('AbstractPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.');
        }

136 137 138 139
        if (!isset($table)) {
            return 'DROP INDEX ' . $index;
        } else {
            if ($table instanceof \Doctrine\DBAL\Schema\Table) {
140
                $table = $table->getQuotedName($this);
141 142 143
            }

            return "IF EXISTS (SELECT * FROM sysobjects WHERE name = '$index')
144
						ALTER TABLE " . $table . " DROP CONSTRAINT " . $index . "
Benjamin Eberlei's avatar
Benjamin Eberlei committed
145
					ELSE
146
						DROP INDEX " . $index . " ON " . $table;
147
        }
148
    }
149 150 151 152 153

    /**
     * @override
     */
    protected function _getCreateTableSQL($tableName, array $columns, array $options = array())
154
    {
155 156 157 158 159 160 161 162
        // @todo does other code breaks because of this?
        // foce primary keys to be not null
        foreach ($columns as &$column) {
            if (isset($column['primary']) && $column['primary']) {
                $column['notnull'] = true;
            }
        }

163
        $columnListSql = $this->getColumnDeclarationListSQL($columns);
164 165

        if (isset($options['uniqueConstraints']) && !empty($options['uniqueConstraints'])) {
166 167 168 169
            foreach ($options['uniqueConstraints'] as $name => $definition) {
                $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition);
            }
        }
170 171

        if (isset($options['primary']) && !empty($options['primary'])) {
172 173 174 175 176 177
            $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')';
        }

        $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql;

        $check = $this->getCheckDeclarationSQL($columns);
178
        if (!empty($check)) {
179 180 181 182 183
            $query .= ', ' . $check;
        }
        $query .= ')';

        $sql[] = $query;
184 185

        if (isset($options['indexes']) && !empty($options['indexes'])) {
186 187 188 189 190 191 192 193 194 195 196 197 198
            foreach ($options['indexes'] AS $index) {
                $sql[] = $this->getCreateIndexSQL($index, $tableName);
            }
        }

        if (isset($options['foreignKeys'])) {
            foreach ((array) $options['foreignKeys'] AS $definition) {
                $sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
            }
        }

        return $sql;
    }
199 200

    /**
201 202
     * @override
     */
203
    public function getUniqueConstraintDeclarationSQL($name, Index $index)
204 205
    {
        $constraint = parent::getUniqueConstraintDeclarationSQL($name, $index);
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226

        $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index);

        return $constraint;
    }

    /**
     * @override
     */
    public function getCreateIndexSQL(Index $index, $table)
    {
        $constraint = parent::getCreateIndexSQL($index, $table);

        if ($index->isUnique()) {
            $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index);
        }

        return $constraint;
    }

    /**
227
     * Extend unique key constraint with required filters
228 229 230 231 232 233 234 235
     *
     * @param string $sql
     * @param Index $index
     * @return string
     */
    private function _appendUniqueConstraintDefinition($sql, Index $index)
    {
        $fields = array();
236 237 238 239
        foreach ($index->getColumns() as $field => $definition) {
            if (!is_array($definition)) {
                $field = $definition;
            }
240 241

            $fields[] = $field . ' IS NOT NULL';
242
        }
243 244 245

        return $sql . ' WHERE ' . implode(' AND ', $fields);
    }
246

247
    /**
248
     * @override
249
     */
250
    public function getAlterTableSQL(TableDiff $diff)
251
    {
252 253
        $queryParts = array();
        if ($diff->newName !== false) {
254
            $queryParts[] = 'RENAME TO ' . $diff->newName;
255 256
        }

257
        foreach ($diff->addedColumns AS $fieldName => $column) {
258
            $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
259 260
        }

261
        foreach ($diff->removedColumns AS $column) {
262
            $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this);
263 264
        }

265 266 267
        foreach ($diff->changedColumns AS $columnDiff) {
            /* @var $columnDiff Doctrine\DBAL\Schema\ColumnDiff */
            $column = $columnDiff->column;
268
            $queryParts[] = 'CHANGE ' . ($columnDiff->oldColumnName) . ' '
269
                    . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
270 271
        }

272
        foreach ($diff->renamedColumns AS $oldColumnName => $column) {
273
            $queryParts[] = 'CHANGE ' . $oldColumnName . ' '
274
                    . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray());
275 276
        }

277
        $sql = array();
278

279 280 281
        foreach ($queryParts as $query) {
            $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query;
        }
282

283
        $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff));
Juozas Kaziukenas's avatar
Juozas Kaziukenas committed
284

285
        return $sql;
286
    }
287

288 289 290
    /**
     * @override
     */
291
    public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName)
292
    {
293
        return 'INSERT INTO ' . $quotedTableName . ' DEFAULT VALUES';
294 295
    }

296 297 298
    /**
     * @override
     */
299
    public function getShowDatabasesSQL()
300
    {
301
        return 'SHOW DATABASES';
302 303 304 305 306
    }

    /**
     * @override
     */
307
    public function getListTablesSQL()
308
    {
309 310
        // "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams
        return "SELECT name FROM sysobjects WHERE type = 'U' AND name != 'sysdiagrams' ORDER BY name";
311 312 313 314 315
    }

    /**
     * @override
     */
316
    public function getListTableColumnsSQL($table, $database = null)
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
    {
        return 'exec sp_columns @table_name = ' . $table;
    }

    /**
     * @override
     */
    public function getListTableForeignKeysSQL($table, $database = null)
    {
        return "SELECT f.name AS ForeignKey,
                SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName,
                OBJECT_NAME (f.parent_object_id) AS TableName,
                COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName,
                SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName,
                OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName,
                COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName,
                f.delete_referential_action_desc,
                f.update_referential_action_desc
                FROM sys.foreign_keys AS f
                INNER JOIN sys.foreign_key_columns AS fc
                INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id
                ON f.OBJECT_ID = fc.constraint_object_id
                WHERE OBJECT_NAME (f.parent_object_id) = '" . $table . "'";
    }

    /**
     * @override
     */
345
    public function getListTableIndexesSQL($table, $currentDatabase = null)
346 347
    {
        return "exec sp_helpindex '" . $table . "'";
348
    }
349 350

    /**
351 352
     * @override
     */
353
    public function getCreateViewSQL($name, $sql)
354 355 356
    {
        return 'CREATE VIEW ' . $name . ' AS ' . $sql;
    }
357 358

    /**
359 360
     * @override
     */
361
    public function getListViewsSQL($database)
362 363 364 365
    {
        return "SELECT name FROM sysobjects WHERE type = 'V' ORDER BY name";
    }

366
    /**
367 368 369 370
     * @override
     */
    public function getDropViewSQL($name)
    {
371
        return 'DROP VIEW ' . $name;
372
    }
373 374

    /**
375
     * Returns the regular expression operator.
376
     *
377
     * @return string
378 379
     * @override
     */
380
    public function getRegexpExpression()
381
    {
382
        return 'RLIKE';
383 384 385 386 387 388 389 390 391 392
    }

    /**
     * Returns global unique identifier
     *
     * @return string to get global unique identifier
     * @override
     */
    public function getGuidExpression()
    {
393
        return 'UUID()';
394
    }
395 396 397 398

    /**
     * @override
     */
399
    public function getLocateExpression($str, $substr, $startPos = false)
400
    {
401 402 403
        if ($startPos == false) {
            return 'CHARINDEX(' . $substr . ', ' . $str . ')';
        } else {
404
            return 'CHARINDEX(' . $substr . ', ' . $str . ', ' . $startPos . ')';
405
        }
406
    }
407

408 409 410
    /**
     * @override
     */
411
    public function getModExpression($expression1, $expression2)
412
    {
413
        return $expression1 . ' % ' . $expression2;
414
    }
415

416 417 418
    /**
     * @override
     */
419
    public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false)
420
    {
421
        $trimFn = '';
422

423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
        if (!$char) {
            if ($pos == self::TRIM_LEADING) {
                $trimFn = 'LTRIM';
            } else if ($pos == self::TRIM_TRAILING) {
                $trimFn = 'RTRIM';
            } else {
                return 'LTRIM(RTRIM(' . $str . '))';
            }

            return $trimFn . '(' . $str . ')';
        } else {
            /** Original query used to get those expressions
              declare @c varchar(100) = 'xxxBarxxx', @trim_char char(1) = 'x';
              declare @pat varchar(10) = '%[^' + @trim_char + ']%';
              select @c as string
              , @trim_char as trim_char
              , stuff(@c, 1, patindex(@pat, @c) - 1, null) as trim_leading
              , reverse(stuff(reverse(@c), 1, patindex(@pat, reverse(@c)) - 1, null)) as trim_trailing
              , 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;
             */
            $pattern = "'%[^' + $char + ']%'";

            if ($pos == self::TRIM_LEADING) {
                return 'stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)';
            } else if ($pos == self::TRIM_TRAILING) {
                return 'reverse(stuff(reverse(' . $str . '), 1, patindex(' . $pattern . ', reverse(' . $str . ')) - 1, null))';
            } else {
                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))';
            }
        }
453
    }
454

455 456 457 458
    /**
     * @override
     */
    public function getConcatExpression()
459
    {
460 461
        $args = func_get_args();
        return '(' . implode(' + ', $args) . ')';
462
    }
463 464

    public function getListDatabasesSQL()
465 466 467
    {
        return 'SELECT * FROM SYS.DATABASES';
    }
468

469
    /**
470 471
     * @override
     */
472
    public function getSubstringExpression($value, $from, $len = null)
473
    {
474
        if (!is_null($len)) {
475 476 477
            return 'SUBSTRING(' . $value . ', ' . $from . ', ' . $len . ')';
        }
        return 'SUBSTRING(' . $value . ', ' . $from . ', LEN(' . $value . ') - ' . $from . ' + 1)';
478
    }
479

480 481 482
    /**
     * @override
     */
483
    public function getLengthExpression($column)
484
    {
485
        return 'LEN(' . $column . ')';
486 487
    }

488 489 490
    /**
     * @override
     */
491
    public function getSetTransactionIsolationSQL($level)
romanb's avatar
romanb committed
492
    {
493
        return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level);
romanb's avatar
romanb committed
494
    }
495

496 497
    /**
     * @override
498
     */
499
    public function getIntegerTypeDeclarationSQL(array $field)
500
    {
501
        return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
502 503
    }

504
    /**
505
     * @override
506
     */
507
    public function getBigIntTypeDeclarationSQL(array $field)
508
    {
509
        return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
510 511
    }

512 513
    /**
     * @override
514
     */
515
    public function getSmallIntTypeDeclarationSQL(array $field)
516
    {
517
        return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field);
518 519
    }

520
    /** @override */
521
    protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed)
522
    {
523
        return $fixed ? ($length ? 'NCHAR(' . $length . ')' : 'CHAR(255)') : ($length ? 'NVARCHAR(' . $length . ')' : 'NVARCHAR(255)');
524
    }
525

526
    /** @override */
527
    public function getClobTypeDeclarationSQL(array $field)
528 529 530
    {
        return 'TEXT';
    }
531

532 533
    /**
     * @override
534
     */
535
    protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef)
536 537
    {
        $autoinc = '';
538
        if (!empty($columnDef['autoincrement'])) {
539
            $autoinc = ' IDENTITY';
540 541 542 543 544
        }
        $unsigned = (isset($columnDef['unsigned']) && $columnDef['unsigned']) ? ' UNSIGNED' : '';

        return $unsigned . $autoinc;
    }
545

546 547 548
    /**
     * @override
     */
549
    public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration)
550
    {
551 552
        // 6 - microseconds precision length
        return 'DATETIME2(6)';
553 554
    }

555 556 557
    /**
     * @override
     */
558
    public function getDateTypeDeclarationSQL(array $fieldDeclaration)
559
    {
560 561
        return 'DATE';
    }
562 563 564 565

    /**
     * @override
     */
566
    public function getTimeTypeDeclarationSQL(array $fieldDeclaration)
567
    {
568
        return 'TIME(0)';
569
    }
570

571 572 573
    /**
     * @override
     */
574
    public function getBooleanTypeDeclarationSQL(array $field)
575 576 577 578
    {
        return 'BIT';
    }

579 580 581 582 583 584 585 586 587
    /**
     * Adds an adapter-specific LIMIT clause to the SELECT statement.
     *
     * @param string $query
     * @param mixed $limit
     * @param mixed $offset
     * @link http://lists.bestpractical.com/pipermail/rt-devel/2005-June/007339.html
     * @return string
     */
588
    protected function doModifyLimitQuery($query, $limit, $offset = null)
589 590 591 592 593 594
    {
        if ($limit > 0) {
            $count = intval($limit);
            $offset = intval($offset);

            if ($offset < 0) {
595
                throw new DBALException("LIMIT argument offset=$offset is not valid");
596 597
            }

598
            if ($offset == 0) {
599
                $query = preg_replace('/^(SELECT\s(DISTINCT\s)?)/i', '\1TOP ' . $count . ' ', $query);
600 601
            } else {
                $orderby = stristr($query, 'ORDER BY');
602

603 604 605 606
                if (!$orderby) {
                    $over = 'ORDER BY (SELECT 0)';
                } else {
                    $over = preg_replace('/\"[^,]*\".\"([^,]*)\"/i', '"inner_tbl"."$1"', $orderby);
607 608
                }

609 610
                // Remove ORDER BY clause from $query
                $query = preg_replace('/\s+ORDER BY(.*)/', '', $query);
AaronDDM's avatar
AaronDDM committed
611
                $query = preg_replace('/^SELECT\s/', '', $query);
612

613 614
                $start = $offset + 1;
                $end = $offset + $count;
615

AaronDDM's avatar
AaronDDM committed
616
                $query = "SELECT * FROM (SELECT ROW_NUMBER() OVER ($over) AS \"doctrine_rownum\", $query) AS doctrine_tbl WHERE \"doctrine_rownum\" BETWEEN $start AND $end";
617 618 619 620 621
            }
        }

        return $query;
    }
622 623

    /**
624
     * @override
625
     */
626
    public function convertBooleans($item)
627
    {
628 629 630
        if (is_array($item)) {
            foreach ($item as $key => $value) {
                if (is_bool($value) || is_numeric($item)) {
631
                    $item[$key] = ($value) ? 1 : 0;
632 633 634
                }
            }
        } else {
635
            if (is_bool($item) || is_numeric($item)) {
636
                $item = ($item) ? 1 : 0;
637
            }
638 639
        }
        return $item;
640
    }
641 642

    /**
643
     * @override
644
     */
645
    public function getCreateTemporaryTableSnippetSQL()
646
    {
647
        return "CREATE TABLE";
648
    }
649

650 651 652 653 654 655 656 657
    /**
     * @override
     */
    public function getTemporaryTableName($tableName)
    {
        return '#' . $tableName;
    }

658
    /**
659 660 661 662 663 664
     * @override
     */
    public function getDateTimeFormatString()
    {
        return 'Y-m-d H:i:s.u';
    }
665

666 667 668 669 670 671 672
    /**
     * @override
     */
    public function getDateTimeTzFormatString()
    {
        return $this->getDateTimeFormatString();
    }
673

674
    /**
675 676
     * Get the platform name for this instance
     *
677 678
     * @return string
     */
679
    public function getName()
680
    {
681
        return 'mssql';
682
    }
683

Juozas Kaziukenas's avatar
Juozas Kaziukenas committed
684 685 686
    /**
     * @override
     */
687 688
    protected function initializeDoctrineTypeMappings()
    {
689
        $this->doctrineTypeMapping = array(
690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717
            'bigint' => 'bigint',
            'numeric' => 'decimal',
            'bit' => 'boolean',
            'smallint' => 'smallint',
            'decimal' => 'decimal',
            'smallmoney' => 'integer',
            'int' => 'integer',
            'tinyint' => 'smallint',
            'money' => 'integer',
            'float' => 'float',
            'real' => 'float',
            'double' => 'float',
            'double precision' => 'float',
            'date' => 'date',
            'datetimeoffset' => 'datetimetz',
            'datetime2' => 'datetime',
            'smalldatetime' => 'datetime',
            'datetime' => 'datetime',
            'time' => 'time',
            'char' => 'string',
            'varchar' => 'string',
            'text' => 'text',
            'nchar' => 'string',
            'nvarchar' => 'string',
            'ntext' => 'text',
            'binary' => 'text',
            'varbinary' => 'text',
            'image' => 'text',
718
        );
719
    }
720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751

    /**
     * Generate SQL to create a new savepoint
     *
     * @param string $savepoint
     * @return string
     */
    public function createSavePoint($savepoint)
    {
        return 'SAVE TRANSACTION ' . $savepoint;
    }

    /**
     * Generate SQL to release a savepoint
     *
     * @param string $savepoint
     * @return string
     */
    public function releaseSavePoint($savepoint)
    {
        return '';
    }

    /**
     * Generate SQL to rollback a savepoint
     *
     * @param string $savepoint
     * @return string
     */
    public function rollbackSavePoint($savepoint)
    {
        return 'ROLLBACK TRANSACTION ' . $savepoint;
752
    }
753 754

    /**
Juozas Kaziukenas's avatar
Juozas Kaziukenas committed
755 756
     * @override
     */
757
    public function appendLockHint($fromClause, $lockMode)
Juozas Kaziukenas's avatar
Juozas Kaziukenas committed
758
    {
759 760
        // @todo coorect
        if ($lockMode == \Doctrine\DBAL\LockMode::PESSIMISTIC_READ) {
Juozas Kaziukenas's avatar
Juozas Kaziukenas committed
761 762 763
            return $fromClause . ' WITH (tablockx)';
        } else if ($lockMode == \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE) {
            return $fromClause . ' WITH (tablockx)';
764 765
        } else {
            return $fromClause;
Juozas Kaziukenas's avatar
Juozas Kaziukenas committed
766 767 768 769 770 771 772 773 774 775
        }
    }

    /**
     * @override
     */
    public function getForUpdateSQL()
    {
        return ' ';
    }
776

777 778 779 780
    protected function getReservedKeywordsClass()
    {
        return 'Doctrine\DBAL\Platforms\Keywords\MsSQLKeywords';
    }
781 782 783 784 785 786 787 788 789 790 791 792 793 794

    /**
     * Quotes a string so that it can be safely used as a table or column name,
     * even if it is a reserved word of the platform.
     *
     * NOTE: Just because you CAN use quoted identifiers doesn't mean
     * you SHOULD use them.  In general, they end up causing way more
     * problems than they solve.
     *
     * @param string $str           identifier name to be quoted
     * @return string               quoted identifier string
     */
    public function quoteIdentifier($str)
    {
795
        return "[" . str_replace("]", "][", $str) . "]";
796
    }
797
}