OraclePlatformTest.php 31 KB
Newer Older
1 2 3 4
<?php

namespace Doctrine\Tests\DBAL\Platforms;

Luís Cobucci's avatar
Luís Cobucci committed
5
use Doctrine\DBAL\DBALException;
6
use Doctrine\DBAL\Platforms\AbstractPlatform;
7
use Doctrine\DBAL\Platforms\OraclePlatform;
8
use Doctrine\DBAL\Schema\Column;
Sergei Morozov's avatar
Sergei Morozov committed
9
use Doctrine\DBAL\Schema\ColumnDiff;
Steve Müller's avatar
Steve Müller committed
10
use Doctrine\DBAL\Schema\Comparator;
11
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
Sergei Morozov's avatar
Sergei Morozov committed
12
use Doctrine\DBAL\Schema\Sequence;
Steve Müller's avatar
Steve Müller committed
13
use Doctrine\DBAL\Schema\Table;
Sergei Morozov's avatar
Sergei Morozov committed
14
use Doctrine\DBAL\Schema\TableDiff;
15
use Doctrine\DBAL\TransactionIsolationLevel;
16
use Doctrine\DBAL\Types\Type;
17 18
use function array_walk;
use function preg_replace;
Sergei Morozov's avatar
Sergei Morozov committed
19
use function sprintf;
20 21
use function strtoupper;
use function uniqid;
22

23
class OraclePlatformTest extends AbstractPlatformTestCase
24
{
25 26 27 28
    /**
     * @return mixed[][]
     */
    public static function dataValidIdentifiers() : iterable
29
    {
Luís Cobucci's avatar
Luís Cobucci committed
30 31 32 33 34 35 36 37 38 39 40
        return [
            ['a'],
            ['foo'],
            ['Foo'],
            ['Foo123'],
            ['Foo#bar_baz$'],
            ['"a"'],
            ['"1"'],
            ['"foo_bar"'],
            ['"@$%&!"'],
        ];
41 42 43 44 45
    }

    /**
     * @dataProvider dataValidIdentifiers
     */
46
    public function testValidIdentifiers(string $identifier) : void
47 48 49
    {
        $platform = $this->createPlatform();
        $platform->assertValidIdentifier($identifier);
Luís Cobucci's avatar
Luís Cobucci committed
50 51

        $this->addToAssertionCount(1);
52 53
    }

54 55 56 57
    /**
     * @return mixed[][]
     */
    public static function dataInvalidIdentifiers() : iterable
58
    {
Luís Cobucci's avatar
Luís Cobucci committed
59 60 61 62 63 64 65
        return [
            ['1'],
            ['abc&'],
            ['abc-def'],
            ['"'],
            ['"foo"bar"'],
        ];
66 67 68 69 70
    }

    /**
     * @dataProvider dataInvalidIdentifiers
     */
71
    public function testInvalidIdentifiers(string $identifier) : void
72
    {
Luís Cobucci's avatar
Luís Cobucci committed
73 74
        $this->expectException(DBALException::class);

75 76 77 78
        $platform = $this->createPlatform();
        $platform->assertValidIdentifier($identifier);
    }

79
    public function createPlatform() : AbstractPlatform
80
    {
Sergei Morozov's avatar
Sergei Morozov committed
81
        return new OraclePlatform();
82 83
    }

84
    public function getGenerateTableSql() : string
85
    {
86
        return 'CREATE TABLE test (id NUMBER(10) NOT NULL, test VARCHAR2(255) DEFAULT NULL NULL, PRIMARY KEY(id))';
87 88
    }

89 90 91 92
    /**
     * @return mixed[]
     */
    public function getGenerateTableWithMultiColumnUniqueIndexSql() : array
93
    {
Sergei Morozov's avatar
Sergei Morozov committed
94
        return [
95
            'CREATE TABLE test (foo VARCHAR2(255) DEFAULT NULL NULL, bar VARCHAR2(255) DEFAULT NULL NULL)',
96
            'CREATE UNIQUE INDEX UNIQ_D87F7E0C8C73652176FF8CAA ON test (foo, bar)',
Sergei Morozov's avatar
Sergei Morozov committed
97
        ];
98 99
    }

100 101 102 103
    /**
     * {@inheritDoc}
     */
    public function getGenerateAlterTableSql() : array
104
    {
Sergei Morozov's avatar
Sergei Morozov committed
105
        return [
106 107
            'ALTER TABLE mytable ADD (quota NUMBER(10) DEFAULT NULL NULL)',
            "ALTER TABLE mytable MODIFY (baz VARCHAR2(255) DEFAULT 'def' NOT NULL, bloo NUMBER(1) DEFAULT '0' NOT NULL)",
Sergei Morozov's avatar
Sergei Morozov committed
108 109 110
            'ALTER TABLE mytable DROP (foo)',
            'ALTER TABLE mytable RENAME TO userlist',
        ];
111 112
    }

113
    public function testRLike() : void
114
    {
115 116
        $this->expectException(DBALException::class);

Sergei Morozov's avatar
Sergei Morozov committed
117
        self::assertEquals('RLIKE', $this->platform->getRegexpExpression(), 'Regular expression operator is not correct');
118 119
    }

120
    public function testGeneratesSqlSnippets() : void
121
    {
Sergei Morozov's avatar
Sergei Morozov committed
122 123
        self::assertEquals('"', $this->platform->getIdentifierQuoteCharacter(), 'Identifier quote character is not correct');
        self::assertEquals('column1 || column2 || column3', $this->platform->getConcatExpression('column1', 'column2', 'column3'), 'Concatenation expression is not correct');
124 125
    }

126
    public function testGeneratesTransactionsCommands() : void
127
    {
128
        self::assertEquals(
129
            'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED',
Sergei Morozov's avatar
Sergei Morozov committed
130
            $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::READ_UNCOMMITTED)
131
        );
132
        self::assertEquals(
133
            'SET TRANSACTION ISOLATION LEVEL READ COMMITTED',
Sergei Morozov's avatar
Sergei Morozov committed
134
            $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::READ_COMMITTED)
135
        );
136
        self::assertEquals(
137
            'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE',
Sergei Morozov's avatar
Sergei Morozov committed
138
            $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::REPEATABLE_READ)
139
        );
140
        self::assertEquals(
141
            'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE',
Sergei Morozov's avatar
Sergei Morozov committed
142
            $this->platform->getSetTransactionIsolationSQL(TransactionIsolationLevel::SERIALIZABLE)
143 144 145
        );
    }

146
    public function testCreateDatabaseThrowsException() : void
147
    {
148 149
        $this->expectException(DBALException::class);

Sergei Morozov's avatar
Sergei Morozov committed
150
        self::assertEquals('CREATE DATABASE foobar', $this->platform->getCreateDatabaseSQL('foobar'));
151 152
    }

153
    public function testDropDatabaseThrowsException() : void
154
    {
Sergei Morozov's avatar
Sergei Morozov committed
155
        self::assertEquals('DROP USER foobar CASCADE', $this->platform->getDropDatabaseSQL('foobar'));
156 157
    }

158
    public function testDropTable() : void
159
    {
Sergei Morozov's avatar
Sergei Morozov committed
160
        self::assertEquals('DROP TABLE foobar', $this->platform->getDropTableSQL('foobar'));
161 162
    }

163
    public function testGeneratesTypeDeclarationForIntegers() : void
164
    {
165
        self::assertEquals(
166
            'NUMBER(10)',
Sergei Morozov's avatar
Sergei Morozov committed
167
            $this->platform->getIntegerTypeDeclarationSQL([])
168
        );
169
        self::assertEquals(
170
            'NUMBER(10)',
Sergei Morozov's avatar
Sergei Morozov committed
171
            $this->platform->getIntegerTypeDeclarationSQL(['autoincrement' => true])
Sergei Morozov's avatar
Sergei Morozov committed
172
        );
173
        self::assertEquals(
174
            'NUMBER(10)',
Sergei Morozov's avatar
Sergei Morozov committed
175
            $this->platform->getIntegerTypeDeclarationSQL(
Sergei Morozov's avatar
Sergei Morozov committed
176 177 178
                ['autoincrement' => true, 'primary' => true]
            )
        );
179 180
    }

181
    public function testGeneratesTypeDeclarationsForStrings() : void
182
    {
183
        self::assertEquals(
184
            'CHAR(10)',
Sergei Morozov's avatar
Sergei Morozov committed
185
            $this->platform->getVarcharTypeDeclarationSQL(
Sergei Morozov's avatar
Sergei Morozov committed
186 187 188
                ['length' => 10, 'fixed' => true]
            )
        );
189
        self::assertEquals(
190
            'VARCHAR2(50)',
Sergei Morozov's avatar
Sergei Morozov committed
191
            $this->platform->getVarcharTypeDeclarationSQL(['length' => 50]),
192 193
            'Variable string declaration is not correct'
        );
194
        self::assertEquals(
195
            'VARCHAR2(255)',
Sergei Morozov's avatar
Sergei Morozov committed
196
            $this->platform->getVarcharTypeDeclarationSQL([]),
197 198 199 200
            'Long string declaration is not correct'
        );
    }

201
    public function testPrefersIdentityColumns() : void
202
    {
Sergei Morozov's avatar
Sergei Morozov committed
203
        self::assertFalse($this->platform->prefersIdentityColumns());
204 205
    }

206
    public function testSupportsIdentityColumns() : void
207
    {
Sergei Morozov's avatar
Sergei Morozov committed
208
        self::assertFalse($this->platform->supportsIdentityColumns());
209 210
    }

211
    public function testSupportsSavePoints() : void
212
    {
Sergei Morozov's avatar
Sergei Morozov committed
213
        self::assertTrue($this->platform->supportsSavepoints());
214
    }
215

216
    protected function supportsCommentOnStatement() : bool
217 218 219 220
    {
        return true;
    }

221
    public function getGenerateIndexSql() : string
222
    {
223
        return 'CREATE INDEX my_idx ON mytable (user_name, last_login)';
224 225
    }

226
    public function getGenerateUniqueIndexSql() : string
227
    {
228
        return 'CREATE UNIQUE INDEX index_name ON test (test, test2)';
229 230
    }

231
    public function getGenerateForeignKeySql() : string
232
    {
233
        return 'ALTER TABLE test ADD FOREIGN KEY (fk_name_id) REFERENCES other_table (id)';
234 235
    }

236
    /**
Sergei Morozov's avatar
Sergei Morozov committed
237 238
     * @param mixed[] $options
     *
239 240 241
     * @group DBAL-1097
     * @dataProvider getGeneratesAdvancedForeignKeyOptionsSQLData
     */
242
    public function testGeneratesAdvancedForeignKeyOptionsSQL(array $options, string $expectedSql) : void
243
    {
Sergei Morozov's avatar
Sergei Morozov committed
244
        $foreignKey = new ForeignKeyConstraint(['foo'], 'foreign_table', ['bar'], null, $options);
245

Sergei Morozov's avatar
Sergei Morozov committed
246
        self::assertSame($expectedSql, $this->platform->getAdvancedForeignKeyOptionsSQL($foreignKey));
247 248 249
    }

    /**
250
     * @return mixed[][]
251
     */
252
    public static function getGeneratesAdvancedForeignKeyOptionsSQLData() : iterable
253
    {
Sergei Morozov's avatar
Sergei Morozov committed
254 255 256 257 258 259 260 261
        return [
            [[], ''],
            [['onUpdate' => 'CASCADE'], ''],
            [['onDelete' => 'CASCADE'], ' ON DELETE CASCADE'],
            [['onDelete' => 'NO ACTION'], ''],
            [['onDelete' => 'RESTRICT'], ''],
            [['onUpdate' => 'SET NULL', 'onDelete' => 'SET NULL'], ' ON DELETE SET NULL'],
        ];
262 263 264 265 266
    }

    /**
     * {@inheritdoc}
     */
267
    public static function getReturnsForeignKeyReferentialActionSQL() : iterable
268
    {
Sergei Morozov's avatar
Sergei Morozov committed
269 270 271 272 273 274 275
        return [
            ['CASCADE', 'CASCADE'],
            ['SET NULL', 'SET NULL'],
            ['NO ACTION', ''],
            ['RESTRICT', ''],
            ['CaScAdE', 'CASCADE'],
        ];
276 277
    }

278
    public function testModifyLimitQuery() : void
279
    {
Sergei Morozov's avatar
Sergei Morozov committed
280
        $sql = $this->platform->modifyLimitQuery('SELECT * FROM user', 10, 0);
281
        self::assertEquals('SELECT a.* FROM (SELECT * FROM user) a WHERE ROWNUM <= 10', $sql);
282 283
    }

284
    public function testModifyLimitQueryWithEmptyOffset() : void
285
    {
Sergei Morozov's avatar
Sergei Morozov committed
286
        $sql = $this->platform->modifyLimitQuery('SELECT * FROM user', 10);
287
        self::assertEquals('SELECT a.* FROM (SELECT * FROM user) a WHERE ROWNUM <= 10', $sql);
288 289
    }

290
    public function testModifyLimitQueryWithNonEmptyOffset() : void
291
    {
Sergei Morozov's avatar
Sergei Morozov committed
292
        $sql = $this->platform->modifyLimitQuery('SELECT * FROM user', 10, 10);
293
        self::assertEquals('SELECT * FROM (SELECT a.*, ROWNUM AS doctrine_rownum FROM (SELECT * FROM user) a WHERE ROWNUM <= 20) WHERE doctrine_rownum >= 11', $sql);
294 295
    }

296
    public function testModifyLimitQueryWithEmptyLimit() : void
297
    {
Sergei Morozov's avatar
Sergei Morozov committed
298
        $sql = $this->platform->modifyLimitQuery('SELECT * FROM user', null, 10);
299
        self::assertEquals('SELECT * FROM (SELECT a.*, ROWNUM AS doctrine_rownum FROM (SELECT * FROM user) a) WHERE doctrine_rownum >= 11', $sql);
300 301
    }

302
    public function testModifyLimitQueryWithAscOrderBy() : void
303
    {
Sergei Morozov's avatar
Sergei Morozov committed
304
        $sql = $this->platform->modifyLimitQuery('SELECT * FROM user ORDER BY username ASC', 10);
305
        self::assertEquals('SELECT a.* FROM (SELECT * FROM user ORDER BY username ASC) a WHERE ROWNUM <= 10', $sql);
306 307
    }

308
    public function testModifyLimitQueryWithDescOrderBy() : void
309
    {
Sergei Morozov's avatar
Sergei Morozov committed
310
        $sql = $this->platform->modifyLimitQuery('SELECT * FROM user ORDER BY username DESC', 10);
311
        self::assertEquals('SELECT a.* FROM (SELECT * FROM user ORDER BY username DESC) a WHERE ROWNUM <= 10', $sql);
312
    }
313

314
    public function testGenerateTableWithAutoincrement() : void
315
    {
316
        $columnName = strtoupper('id' . uniqid());
Sergei Morozov's avatar
Sergei Morozov committed
317 318 319
        $tableName  = strtoupper('table' . uniqid());
        $table      = new Table($tableName);
        $column     = $table->addColumn($columnName, 'integer');
320
        $column->setAutoincrement(true);
Sergei Morozov's avatar
Sergei Morozov committed
321
        $targets    = [
Sergei Morozov's avatar
Sergei Morozov committed
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
            sprintf('CREATE TABLE %s (%s NUMBER(10) NOT NULL)', $tableName, $columnName),
            sprintf(
                "DECLARE constraints_Count NUMBER; BEGIN SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = '%s' AND CONSTRAINT_TYPE = 'P'; IF constraints_Count = 0 OR constraints_Count = '' THEN EXECUTE IMMEDIATE 'ALTER TABLE %s ADD CONSTRAINT %s_AI_PK PRIMARY KEY (%s)'; END IF; END;",
                $tableName,
                $tableName,
                $tableName,
                $columnName
            ),
            sprintf('CREATE SEQUENCE %s_SEQ START WITH 1 MINVALUE 1 INCREMENT BY 1', $tableName),
            sprintf(
                "CREATE TRIGGER %s_AI_PK BEFORE INSERT ON %s FOR EACH ROW DECLARE last_Sequence NUMBER; last_InsertID NUMBER; BEGIN SELECT %s_SEQ.NEXTVAL INTO :NEW.%s FROM DUAL; IF (:NEW.%s IS NULL OR :NEW.%s = 0) THEN SELECT %s_SEQ.NEXTVAL INTO :NEW.%s FROM DUAL; ELSE SELECT NVL(Last_Number, 0) INTO last_Sequence FROM User_Sequences WHERE Sequence_Name = '%s_SEQ'; SELECT :NEW.%s INTO last_InsertID FROM DUAL; WHILE (last_InsertID > last_Sequence) LOOP SELECT %s_SEQ.NEXTVAL INTO last_Sequence FROM DUAL; END LOOP; END IF; END;",
                $tableName,
                $tableName,
                $tableName,
                $columnName,
                $columnName,
                $columnName,
                $tableName,
                $columnName,
                $tableName,
                $columnName,
                $tableName
            ),
Sergei Morozov's avatar
Sergei Morozov committed
345
        ];
Sergei Morozov's avatar
Sergei Morozov committed
346
        $statements = $this->platform->getCreateTableSQL($table);
347
        //strip all the whitespace from the statements
348
        array_walk($statements, static function (&$value) : void {
Sergei Morozov's avatar
Sergei Morozov committed
349
            $value = preg_replace('/\s+/', ' ', $value);
350
        });
Sergei Morozov's avatar
Sergei Morozov committed
351 352
        foreach ($targets as $key => $sql) {
            self::assertArrayHasKey($key, $statements);
353
            self::assertEquals($sql, $statements[$key]);
354 355 356
        }
    }

357 358 359 360
    /**
     * {@inheritDoc}
     */
    public function getCreateTableColumnCommentsSQL() : array
361
    {
Sergei Morozov's avatar
Sergei Morozov committed
362 363
        return [
            'CREATE TABLE test (id NUMBER(10) NOT NULL, PRIMARY KEY(id))',
364
            "COMMENT ON COLUMN test.id IS 'This is a comment'",
Sergei Morozov's avatar
Sergei Morozov committed
365
        ];
366 367
    }

368 369 370 371
    /**
     * {@inheritDoc}
     */
    public function getCreateTableColumnTypeCommentsSQL() : array
372
    {
Sergei Morozov's avatar
Sergei Morozov committed
373 374 375 376
        return [
            'CREATE TABLE test (id NUMBER(10) NOT NULL, data CLOB NOT NULL, PRIMARY KEY(id))',
            "COMMENT ON COLUMN test.data IS '(DC2Type:array)'",
        ];
377 378
    }

379 380 381 382
    /**
     * {@inheritDoc}
     */
    public function getAlterTableColumnCommentsSQL() : array
383
    {
Sergei Morozov's avatar
Sergei Morozov committed
384 385
        return [
            'ALTER TABLE mytable ADD (quota NUMBER(10) NOT NULL)',
386
            "COMMENT ON COLUMN mytable.quota IS 'A comment'",
387
            "COMMENT ON COLUMN mytable.foo IS ''",
388
            "COMMENT ON COLUMN mytable.baz IS 'B comment'",
Sergei Morozov's avatar
Sergei Morozov committed
389
        ];
390
    }
391

392
    public function getBitAndComparisonExpressionSql(string $value1, string $value2) : string
393
    {
Sergei Morozov's avatar
Sergei Morozov committed
394
        return 'BITAND(' . $value1 . ', ' . $value2 . ')';
395 396
    }

397
    public function getBitOrComparisonExpressionSql(string $value1, string $value2) : string
398
    {
399
        return '(' . $value1 . '-' .
400 401
        $this->getBitAndComparisonExpressionSql($value1, $value2)
        . '+' . $value2 . ')';
402
    }
403

404 405 406 407
    /**
     * @return mixed[]
     */
    protected function getQuotedColumnInPrimaryKeySQL() : array
408
    {
Sergei Morozov's avatar
Sergei Morozov committed
409
        return ['CREATE TABLE "quoted" ("create" VARCHAR2(255) NOT NULL, PRIMARY KEY("create"))'];
410 411
    }

412 413 414 415
    /**
     * @return mixed[]
     */
    protected function getQuotedColumnInIndexSQL() : array
416
    {
Sergei Morozov's avatar
Sergei Morozov committed
417
        return [
418 419
            'CREATE TABLE "quoted" ("create" VARCHAR2(255) NOT NULL)',
            'CREATE INDEX IDX_22660D028FD6E0FB ON "quoted" ("create")',
Sergei Morozov's avatar
Sergei Morozov committed
420
        ];
421 422
    }

423 424 425 426
    /**
     * @return mixed[]
     */
    protected function getQuotedNameInIndexSQL() : array
Markus Fasselt's avatar
Markus Fasselt committed
427
    {
Sergei Morozov's avatar
Sergei Morozov committed
428
        return [
Markus Fasselt's avatar
Markus Fasselt committed
429 430
            'CREATE TABLE test (column1 VARCHAR2(255) NOT NULL)',
            'CREATE INDEX "key" ON test (column1)',
Sergei Morozov's avatar
Sergei Morozov committed
431
        ];
Markus Fasselt's avatar
Markus Fasselt committed
432 433
    }

434 435 436 437
    /**
     * @return mixed[]
     */
    protected function getQuotedColumnInForeignKeySQL() : array
438
    {
Sergei Morozov's avatar
Sergei Morozov committed
439
        return [
440 441 442 443
            'CREATE TABLE "quoted" ("create" VARCHAR2(255) NOT NULL, foo VARCHAR2(255) NOT NULL, "bar" VARCHAR2(255) NOT NULL)',
            'ALTER TABLE "quoted" ADD CONSTRAINT FK_WITH_RESERVED_KEYWORD FOREIGN KEY ("create", foo, "bar") REFERENCES foreign ("create", bar, "foo-bar")',
            'ALTER TABLE "quoted" ADD CONSTRAINT FK_WITH_NON_RESERVED_KEYWORD FOREIGN KEY ("create", foo, "bar") REFERENCES foo ("create", bar, "foo-bar")',
            'ALTER TABLE "quoted" ADD CONSTRAINT FK_WITH_INTENDED_QUOTATION FOREIGN KEY ("create", foo, "bar") REFERENCES "foo-bar" ("create", bar, "foo-bar")',
Sergei Morozov's avatar
Sergei Morozov committed
444
        ];
445
    }
446

447 448 449 450
    /**
     * @group DBAL-472
     * @group DBAL-1001
     */
451
    public function testAlterTableNotNULL() : void
452
    {
Sergei Morozov's avatar
Sergei Morozov committed
453 454 455 456 457 458 459
        $tableDiff                          = new TableDiff('mytable');
        $tableDiff->changedColumns['foo']   = new ColumnDiff(
            'foo',
            new Column(
                'foo',
                Type::getType('string'),
                ['default' => 'bla', 'notnull' => true]
460
            ),
Sergei Morozov's avatar
Sergei Morozov committed
461 462 463 464 465 466 467 468
            ['type']
        );
        $tableDiff->changedColumns['bar']   = new ColumnDiff(
            'bar',
            new Column(
                'baz',
                Type::getType('string'),
                ['default' => 'bla', 'notnull' => true]
469
            ),
Sergei Morozov's avatar
Sergei Morozov committed
470 471 472 473 474 475 476 477
            ['type', 'notnull']
        );
        $tableDiff->changedColumns['metar'] = new ColumnDiff(
            'metar',
            new Column(
                'metar',
                Type::getType('string'),
                ['length' => 2000, 'notnull' => false]
478
            ),
Sergei Morozov's avatar
Sergei Morozov committed
479
            ['notnull']
480
        );
481

Sergei Morozov's avatar
Sergei Morozov committed
482
        $expectedSql = ["ALTER TABLE mytable MODIFY (foo VARCHAR2(255) DEFAULT 'bla', baz VARCHAR2(255) DEFAULT 'bla' NOT NULL, metar VARCHAR2(2000) DEFAULT NULL NULL)"];
Sergei Morozov's avatar
Sergei Morozov committed
483
        self::assertEquals($expectedSql, $this->platform->getAlterTableSQL($tableDiff));
484
    }
485

486
    /**
Steve Müller's avatar
Steve Müller committed
487
     * @group DBAL-2555
488
     */
489
    public function testInitializesDoctrineTypeMappings() : void
Steve Müller's avatar
Steve Müller committed
490
    {
Sergei Morozov's avatar
Sergei Morozov committed
491 492
        self::assertTrue($this->platform->hasDoctrineTypeMappingFor('long raw'));
        self::assertSame('blob', $this->platform->getDoctrineTypeMapping('long raw'));
Steve Müller's avatar
Steve Müller committed
493

Sergei Morozov's avatar
Sergei Morozov committed
494 495
        self::assertTrue($this->platform->hasDoctrineTypeMappingFor('raw'));
        self::assertSame('binary', $this->platform->getDoctrineTypeMapping('raw'));
496

Sergei Morozov's avatar
Sergei Morozov committed
497 498
        self::assertTrue($this->platform->hasDoctrineTypeMappingFor('date'));
        self::assertSame('date', $this->platform->getDoctrineTypeMapping('date'));
Steve Müller's avatar
Steve Müller committed
499 500
    }

501
    protected function getBinaryMaxLength() : int
Steve Müller's avatar
Steve Müller committed
502 503 504 505
    {
        return 2000;
    }

506
    public function testReturnsBinaryTypeDeclarationSQL() : void
Steve Müller's avatar
Steve Müller committed
507
    {
Sergei Morozov's avatar
Sergei Morozov committed
508 509 510
        self::assertSame('RAW(255)', $this->platform->getBinaryTypeDeclarationSQL([]));
        self::assertSame('RAW(2000)', $this->platform->getBinaryTypeDeclarationSQL(['length' => 0]));
        self::assertSame('RAW(2000)', $this->platform->getBinaryTypeDeclarationSQL(['length' => 2000]));
Steve Müller's avatar
Steve Müller committed
511

Sergei Morozov's avatar
Sergei Morozov committed
512 513 514
        self::assertSame('RAW(255)', $this->platform->getBinaryTypeDeclarationSQL(['fixed' => true]));
        self::assertSame('RAW(2000)', $this->platform->getBinaryTypeDeclarationSQL(['fixed' => true, 'length' => 0]));
        self::assertSame('RAW(2000)', $this->platform->getBinaryTypeDeclarationSQL(['fixed' => true, 'length' => 2000]));
515 516 517 518
    }

    /**
     * @group legacy
519
     * @expectedDeprecation Binary field length 2001 is greater than supported by the platform (2000). Reduce the field length or use a BLOB field instead.
520
     */
521
    public function testReturnsBinaryTypeLongerThanMaxDeclarationSQL() : void
522
    {
Sergei Morozov's avatar
Sergei Morozov committed
523 524
        self::assertSame('BLOB', $this->platform->getBinaryTypeDeclarationSQL(['length' => 2001]));
        self::assertSame('BLOB', $this->platform->getBinaryTypeDeclarationSQL(['fixed' => true, 'length' => 2001]));
Steve Müller's avatar
Steve Müller committed
525 526
    }

527
    public function testDoesNotPropagateUnnecessaryTableAlterationOnBinaryType() : void
Steve Müller's avatar
Steve Müller committed
528 529 530
    {
        $table1 = new Table('mytable');
        $table1->addColumn('column_varbinary', 'binary');
Sergei Morozov's avatar
Sergei Morozov committed
531
        $table1->addColumn('column_binary', 'binary', ['fixed' => true]);
Steve Müller's avatar
Steve Müller committed
532 533

        $table2 = new Table('mytable');
Sergei Morozov's avatar
Sergei Morozov committed
534
        $table2->addColumn('column_varbinary', 'binary', ['fixed' => true]);
Steve Müller's avatar
Steve Müller committed
535 536 537 538 539 540
        $table2->addColumn('column_binary', 'binary');

        $comparator = new Comparator();

        // VARBINARY -> BINARY
        // BINARY    -> VARBINARY
Sergei Morozov's avatar
Sergei Morozov committed
541
        self::assertEmpty($this->platform->getAlterTableSQL($comparator->diffTable($table1, $table2)));
Steve Müller's avatar
Steve Müller committed
542 543
    }

544 545 546
    /**
     * @group DBAL-563
     */
547
    public function testUsesSequenceEmulatedIdentityColumns() : void
548
    {
Sergei Morozov's avatar
Sergei Morozov committed
549
        self::assertTrue($this->platform->usesSequenceEmulatedIdentityColumns());
550 551 552 553
    }

    /**
     * @group DBAL-563
554
     * @group DBAL-831
555
     */
556
    public function testReturnsIdentitySequenceName() : void
557
    {
Sergei Morozov's avatar
Sergei Morozov committed
558 559 560 561
        self::assertSame('MYTABLE_SEQ', $this->platform->getIdentitySequenceName('mytable', 'mycolumn'));
        self::assertSame('"mytable_SEQ"', $this->platform->getIdentitySequenceName('"mytable"', 'mycolumn'));
        self::assertSame('MYTABLE_SEQ', $this->platform->getIdentitySequenceName('mytable', '"mycolumn"'));
        self::assertSame('"mytable_SEQ"', $this->platform->getIdentitySequenceName('"mytable"', '"mycolumn"'));
562
    }
563 564 565 566 567

    /**
     * @dataProvider dataCreateSequenceWithCache
     * @group DBAL-139
     */
568
    public function testCreateSequenceWithCache(int $cacheSize, string $expectedSql) : void
569
    {
Sergei Morozov's avatar
Sergei Morozov committed
570
        $sequence = new Sequence('foo', 1, 1, $cacheSize);
571
        self::assertStringContainsString($expectedSql, $this->platform->getCreateSequenceSQL($sequence));
572 573
    }

574 575 576 577
    /**
     * @return mixed[][]
     */
    public static function dataCreateSequenceWithCache() : iterable
578
    {
Sergei Morozov's avatar
Sergei Morozov committed
579 580 581 582 583
        return [
            [1, 'NOCACHE'],
            [0, 'NOCACHE'],
            [3, 'CACHE 3'],
        ];
584
    }
585 586

    /**
587 588
     * {@inheritDoc}
     *
589 590
     * @group DBAL-234
     */
591
    protected function getAlterTableRenameIndexSQL() : array
592
    {
Sergei Morozov's avatar
Sergei Morozov committed
593
        return ['ALTER INDEX idx_foo RENAME TO idx_bar'];
594 595 596
    }

    /**
597 598
     * {@inheritDoc}
     *
599 600
     * @group DBAL-234
     */
601
    protected function getQuotedAlterTableRenameIndexSQL() : array
602
    {
Sergei Morozov's avatar
Sergei Morozov committed
603
        return [
604 605
            'ALTER INDEX "create" RENAME TO "select"',
            'ALTER INDEX "foo" RENAME TO "bar"',
Sergei Morozov's avatar
Sergei Morozov committed
606
        ];
607
    }
608 609 610 611

    /**
     * {@inheritdoc}
     */
612
    protected function getQuotedAlterTableRenameColumnSQL() : array
613
    {
Sergei Morozov's avatar
Sergei Morozov committed
614
        return [
615 616 617 618 619 620 621 622 623
            'ALTER TABLE mytable RENAME COLUMN unquoted1 TO unquoted',
            'ALTER TABLE mytable RENAME COLUMN unquoted2 TO "where"',
            'ALTER TABLE mytable RENAME COLUMN unquoted3 TO "foo"',
            'ALTER TABLE mytable RENAME COLUMN "create" TO reserved_keyword',
            'ALTER TABLE mytable RENAME COLUMN "table" TO "from"',
            'ALTER TABLE mytable RENAME COLUMN "select" TO "bar"',
            'ALTER TABLE mytable RENAME COLUMN quoted1 TO quoted',
            'ALTER TABLE mytable RENAME COLUMN quoted2 TO "and"',
            'ALTER TABLE mytable RENAME COLUMN quoted3 TO "baz"',
Sergei Morozov's avatar
Sergei Morozov committed
624
        ];
625
    }
626

627 628 629
    /**
     * {@inheritdoc}
     */
630
    protected function getQuotedAlterTableChangeColumnLengthSQL() : array
631 632 633 634
    {
        $this->markTestIncomplete('Not implemented yet');
    }

635
    /**
636 637
     * {@inheritDoc}
     *
638 639
     * @group DBAL-807
     */
640
    protected function getAlterTableRenameIndexInSchemaSQL() : array
641
    {
Sergei Morozov's avatar
Sergei Morozov committed
642
        return ['ALTER INDEX myschema.idx_foo RENAME TO idx_bar'];
643 644 645
    }

    /**
646 647
     * {@inheritDoc}
     *
648 649
     * @group DBAL-807
     */
650
    protected function getQuotedAlterTableRenameIndexInSchemaSQL() : array
651
    {
Sergei Morozov's avatar
Sergei Morozov committed
652
        return [
653 654
            'ALTER INDEX "schema"."create" RENAME TO "select"',
            'ALTER INDEX "schema"."foo" RENAME TO "bar"',
Sergei Morozov's avatar
Sergei Morozov committed
655
        ];
656
    }
657

658
    protected function getQuotesDropForeignKeySQL() : string
659 660 661 662
    {
        return 'ALTER TABLE "table" DROP CONSTRAINT "select"';
    }

663 664 665
    /**
     * @group DBAL-423
     */
666
    public function testReturnsGuidTypeDeclarationSQL() : void
667
    {
Sergei Morozov's avatar
Sergei Morozov committed
668
        self::assertSame('CHAR(36)', $this->platform->getGuidTypeDeclarationSQL([]));
669
    }
670 671 672 673

    /**
     * {@inheritdoc}
     */
674
    public function getAlterTableRenameColumnSQL() : array
675
    {
Sergei Morozov's avatar
Sergei Morozov committed
676
        return ['ALTER TABLE foo RENAME COLUMN bar TO baz'];
677
    }
678 679

    /**
680 681
     * @param string[] $expectedSql
     *
682 683 684
     * @dataProvider getReturnsDropAutoincrementSQL
     * @group DBAL-831
     */
685
    public function testReturnsDropAutoincrementSQL(string $table, array $expectedSql) : void
686
    {
Sergei Morozov's avatar
Sergei Morozov committed
687
        self::assertSame($expectedSql, $this->platform->getDropAutoincrementSql($table));
688 689
    }

690 691 692 693
    /**
     * @return mixed[][]
     */
    public static function getReturnsDropAutoincrementSQL() : iterable
694
    {
Sergei Morozov's avatar
Sergei Morozov committed
695 696
        return [
            [
697
                'myTable',
Sergei Morozov's avatar
Sergei Morozov committed
698
                [
699 700 701
                    'DROP TRIGGER MYTABLE_AI_PK',
                    'DROP SEQUENCE MYTABLE_SEQ',
                    'ALTER TABLE MYTABLE DROP CONSTRAINT MYTABLE_AI_PK',
Sergei Morozov's avatar
Sergei Morozov committed
702 703 704
                ],
            ],
            [
705
                '"myTable"',
Sergei Morozov's avatar
Sergei Morozov committed
706
                [
707 708 709
                    'DROP TRIGGER "myTable_AI_PK"',
                    'DROP SEQUENCE "myTable_SEQ"',
                    'ALTER TABLE "myTable" DROP CONSTRAINT "myTable_AI_PK"',
Sergei Morozov's avatar
Sergei Morozov committed
710 711 712
                ],
            ],
            [
713
                'table',
Sergei Morozov's avatar
Sergei Morozov committed
714
                [
715 716 717
                    'DROP TRIGGER TABLE_AI_PK',
                    'DROP SEQUENCE TABLE_SEQ',
                    'ALTER TABLE "TABLE" DROP CONSTRAINT TABLE_AI_PK',
Sergei Morozov's avatar
Sergei Morozov committed
718 719 720
                ],
            ],
        ];
721
    }
722 723 724 725

    /**
     * {@inheritdoc}
     */
726
    protected function getQuotesTableIdentifiersInAlterTableSQL() : array
727
    {
Sergei Morozov's avatar
Sergei Morozov committed
728
        return [
729 730 731 732 733 734 735 736 737
            'ALTER TABLE "foo" DROP CONSTRAINT fk1',
            'ALTER TABLE "foo" DROP CONSTRAINT fk2',
            'ALTER TABLE "foo" ADD (bloo NUMBER(10) NOT NULL)',
            'ALTER TABLE "foo" MODIFY (bar NUMBER(10) DEFAULT NULL NULL)',
            'ALTER TABLE "foo" RENAME COLUMN id TO war',
            'ALTER TABLE "foo" DROP (baz)',
            'ALTER TABLE "foo" RENAME TO "table"',
            'ALTER TABLE "table" ADD CONSTRAINT fk_add FOREIGN KEY (fk3) REFERENCES fk_table (id)',
            'ALTER TABLE "table" ADD CONSTRAINT fk2 FOREIGN KEY (fk2) REFERENCES fk_table2 (id)',
Sergei Morozov's avatar
Sergei Morozov committed
738
        ];
739
    }
740 741 742 743

    /**
     * {@inheritdoc}
     */
744
    protected function getCommentOnColumnSQL() : array
745
    {
Sergei Morozov's avatar
Sergei Morozov committed
746
        return [
747 748 749
            'COMMENT ON COLUMN foo.bar IS \'comment\'',
            'COMMENT ON COLUMN "Foo"."BAR" IS \'comment\'',
            'COMMENT ON COLUMN "select"."from" IS \'comment\'',
Sergei Morozov's avatar
Sergei Morozov committed
750
        ];
751 752 753 754 755
    }

    /**
     * @group DBAL-1004
     */
756
    public function testAltersTableColumnCommentWithExplicitlyQuotedIdentifiers() : void
757
    {
Sergei Morozov's avatar
Sergei Morozov committed
758 759
        $table1 = new Table('"foo"', [new Column('"bar"', Type::getType('integer'))]);
        $table2 = new Table('"foo"', [new Column('"bar"', Type::getType('integer'), ['comment' => 'baz'])]);
760 761 762 763 764

        $comparator = new Comparator();

        $tableDiff = $comparator->diffTable($table1, $table2);

Sergei Morozov's avatar
Sergei Morozov committed
765
        self::assertInstanceOf(TableDiff::class, $tableDiff);
766
        self::assertSame(
Sergei Morozov's avatar
Sergei Morozov committed
767
            ['COMMENT ON COLUMN "foo"."bar" IS \'baz\''],
Sergei Morozov's avatar
Sergei Morozov committed
768
            $this->platform->getAlterTableSQL($tableDiff)
769 770
        );
    }
771

772
    public function testQuotedTableNames() : void
773 774
    {
        $table = new Table('"test"');
Sergei Morozov's avatar
Sergei Morozov committed
775
        $table->addColumn('"id"', 'integer', ['autoincrement' => true]);
776 777

        // assert tabel
778 779
        self::assertTrue($table->isQuoted());
        self::assertEquals('test', $table->getName());
Sergei Morozov's avatar
Sergei Morozov committed
780
        self::assertEquals('"test"', $table->getQuotedName($this->platform));
781

Sergei Morozov's avatar
Sergei Morozov committed
782
        $sql = $this->platform->getCreateTableSQL($table);
783 784
        self::assertEquals('CREATE TABLE "test" ("id" NUMBER(10) NOT NULL)', $sql[0]);
        self::assertEquals('CREATE SEQUENCE "test_SEQ" START WITH 1 MINVALUE 1 INCREMENT BY 1', $sql[2]);
785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808
        $createTriggerStatement = <<<EOD
CREATE TRIGGER "test_AI_PK"
   BEFORE INSERT
   ON "test"
   FOR EACH ROW
DECLARE
   last_Sequence NUMBER;
   last_InsertID NUMBER;
BEGIN
   SELECT "test_SEQ".NEXTVAL INTO :NEW."id" FROM DUAL;
   IF (:NEW."id" IS NULL OR :NEW."id" = 0) THEN
      SELECT "test_SEQ".NEXTVAL INTO :NEW."id" FROM DUAL;
   ELSE
      SELECT NVL(Last_Number, 0) INTO last_Sequence
        FROM User_Sequences
       WHERE Sequence_Name = 'test_SEQ';
      SELECT :NEW."id" INTO last_InsertID FROM DUAL;
      WHILE (last_InsertID > last_Sequence) LOOP
         SELECT "test_SEQ".NEXTVAL INTO last_Sequence FROM DUAL;
      END LOOP;
   END IF;
END;
EOD;

809
        self::assertEquals($createTriggerStatement, $sql[3]);
810
    }
811

812 813 814 815
    /**
     * @dataProvider getReturnsGetListTableColumnsSQL
     * @group DBAL-831
     */
816
    public function testReturnsGetListTableColumnsSQL(?string $database, string $expectedSql) : void
817
    {
818 819 820
        // note: this assertion is a bit strict, as it compares a full SQL string.
        // Should this break in future, then please try to reduce the matching to substring matching while reworking
        // the tests
Sergei Morozov's avatar
Sergei Morozov committed
821
        self::assertEquals($expectedSql, $this->platform->getListTableColumnsSQL('"test"', $database));
822 823
    }

824 825 826 827
    /**
     * @return mixed[][]
     */
    public static function getReturnsGetListTableColumnsSQL() : iterable
828
    {
Sergei Morozov's avatar
Sergei Morozov committed
829 830
        return [
            [
831
                null,
832 833 834 835 836 837 838 839 840 841 842 843
                <<<'SQL'
SELECT   c.*,
         (
             SELECT d.comments
             FROM   user_col_comments d
             WHERE  d.TABLE_NAME = c.TABLE_NAME
             AND    d.COLUMN_NAME = c.COLUMN_NAME
         ) AS comments
FROM     user_tab_columns c
WHERE    c.table_name = 'test'
ORDER BY c.column_id
SQL
Grégoire Paris's avatar
Grégoire Paris committed
844
,
Sergei Morozov's avatar
Sergei Morozov committed
845 846
            ],
            [
847
                '/',
848 849 850 851 852 853 854 855 856 857 858 859
                <<<'SQL'
SELECT   c.*,
         (
             SELECT d.comments
             FROM   user_col_comments d
             WHERE  d.TABLE_NAME = c.TABLE_NAME
             AND    d.COLUMN_NAME = c.COLUMN_NAME
         ) AS comments
FROM     user_tab_columns c
WHERE    c.table_name = 'test'
ORDER BY c.column_id
SQL
Grégoire Paris's avatar
Grégoire Paris committed
860
,
Sergei Morozov's avatar
Sergei Morozov committed
861 862
            ],
            [
863
                'scott',
864 865 866 867 868 869 870 871 872 873 874 875
                <<<'SQL'
SELECT   c.*,
         (
             SELECT d.comments
             FROM   all_col_comments d
             WHERE  d.TABLE_NAME = c.TABLE_NAME AND d.OWNER = c.OWNER
             AND    d.COLUMN_NAME = c.COLUMN_NAME
         ) AS comments
FROM     all_tab_columns c
WHERE    c.table_name = 'test' AND c.owner = 'SCOTT'
ORDER BY c.column_id
SQL
Grégoire Paris's avatar
Grégoire Paris committed
876
,
Sergei Morozov's avatar
Sergei Morozov committed
877 878
            ],
        ];
879 880
    }

881
    protected function getQuotesReservedKeywordInUniqueConstraintDeclarationSQL() : string
882 883 884 885
    {
        return 'CONSTRAINT "select" UNIQUE (foo)';
    }

886
    protected function getQuotesReservedKeywordInIndexDeclarationSQL() : string
887 888 889
    {
        return 'INDEX "select" (foo)';
    }
890

891
    protected function getQuotesReservedKeywordInTruncateTableSQL() : string
892 893 894 895
    {
        return 'TRUNCATE TABLE "select"';
    }

896 897 898
    /**
     * {@inheritdoc}
     */
899
    protected function getAlterStringToFixedStringSQL() : array
900
    {
Sergei Morozov's avatar
Sergei Morozov committed
901
        return ['ALTER TABLE mytable MODIFY (name CHAR(2) DEFAULT NULL)'];
902
    }
903 904 905 906

    /**
     * {@inheritdoc}
     */
907
    protected function getGeneratesAlterTableRenameIndexUsedByForeignKeySQL() : array
908
    {
Sergei Morozov's avatar
Sergei Morozov committed
909
        return ['ALTER INDEX idx_foo RENAME TO idx_foo_renamed'];
910
    }
911 912 913 914

    /**
     * @group DBAL-2436
     */
915
    public function testQuotesDatabaseNameInListSequencesSQL() : void
916
    {
917 918 919 920
        self::assertStringContainsStringIgnoringCase(
            "'Foo''Bar\\'",
            $this->platform->getListSequencesSQL("Foo'Bar\\")
        );
921 922 923 924 925
    }

    /**
     * @group DBAL-2436
     */
926
    public function testQuotesTableNameInListTableIndexesSQL() : void
927
    {
928 929 930 931
        self::assertStringContainsStringIgnoringCase(
            "'Foo''Bar\\'",
            $this->platform->getListTableIndexesSQL("Foo'Bar\\")
        );
932 933 934 935 936
    }

    /**
     * @group DBAL-2436
     */
937
    public function testQuotesTableNameInListTableForeignKeysSQL() : void
938
    {
939 940 941 942
        self::assertStringContainsStringIgnoringCase(
            "'Foo''Bar\\'",
            $this->platform->getListTableForeignKeysSQL("Foo'Bar\\")
        );
943 944 945 946 947
    }

    /**
     * @group DBAL-2436
     */
948
    public function testQuotesTableNameInListTableConstraintsSQL() : void
949
    {
950 951 952 953
        self::assertStringContainsStringIgnoringCase(
            "'Foo''Bar\\'",
            $this->platform->getListTableConstraintsSQL("Foo'Bar\\")
        );
954 955 956 957 958
    }

    /**
     * @group DBAL-2436
     */
959
    public function testQuotesTableNameInListTableColumnsSQL() : void
960
    {
961 962 963 964
        self::assertStringContainsStringIgnoringCase(
            "'Foo''Bar\\'",
            $this->platform->getListTableColumnsSQL("Foo'Bar\\")
        );
965 966 967 968 969
    }

    /**
     * @group DBAL-2436
     */
970
    public function testQuotesDatabaseNameInListTableColumnsSQL() : void
971
    {
972 973 974 975
        self::assertStringContainsStringIgnoringCase(
            "'Foo''Bar\\'",
            $this->platform->getListTableColumnsSQL('foo_table', "Foo'Bar\\")
        );
976
    }
977
}