diff --git a/classes/ETL/DbModel/ForeignKeyConstraint.php b/classes/ETL/DbModel/ForeignKeyConstraint.php index dac8bbba9a..6bfda37b9f 100644 --- a/classes/ETL/DbModel/ForeignKeyConstraint.php +++ b/classes/ETL/DbModel/ForeignKeyConstraint.php @@ -12,7 +12,7 @@ use Log; use stdClass; -class ForeignKeyConstraint extends NamedEntity implements iEntity +class ForeignKeyConstraint extends SchemaEntity implements iEntity { /** * Properties required by this class. These will be merged with other @@ -34,6 +34,7 @@ class ForeignKeyConstraint extends NamedEntity implements iEntity */ private $localProperties = array( 'columns' => array(), + 'referenced_schema' => null, 'referenced_table' => null, 'referenced_columns' => array(), 'on_delete' => null, @@ -90,6 +91,7 @@ protected function filterAndVerifyValue($property, $value) ); } break; + case 'referenced_schema': case 'referenced_table': if (!is_string($value)) { $this->logAndThrowException( @@ -182,6 +184,13 @@ public function compare(iEntity $cmp) return 1; } + // If the referenced schema is not set then use the schema. + if (($this->referenced_schema === null ? $this->schema : $this->referenced_schema) + != ($cmp->referenced_schema === null ? $cmp->schema : $cmp->referenced_schema) + ) { + return -4; + } + if ($this->name != $cmp->name || $this->columns != $cmp->columns || $this->referenced_table != $cmp->referenced_table @@ -218,7 +227,12 @@ public function getSql($includeSchema = false) . implode(', ', array_map(array($this, 'quote'), $this->columns)) . ')'; $parts[] = 'REFERENCES'; - $parts[] = $this->quote($this->referenced_table); + if ($this->referenced_schema !== null) { + $parts[] = $this->quote($this->referenced_schema) . '.' + . $this->quote($this->referenced_table); + } else { + $parts[] = $this->quote($this->referenced_table); + } $parts[] = '(' . implode( ', ', diff --git a/classes/ETL/DbModel/Table.php b/classes/ETL/DbModel/Table.php index 8060f40d35..18f8b761d8 100644 --- a/classes/ETL/DbModel/Table.php +++ b/classes/ETL/DbModel/Table.php @@ -359,6 +359,7 @@ public function discover($source) SELECT tc.constraint_name AS name, GROUP_CONCAT(kcu.column_name ORDER BY position_in_unique_constraint ASC) AS columns, + kcu.referenced_table_schema AS referenced_schema, kcu.referenced_table_name AS referenced_table, GROUP_CONCAT(kcu.referenced_column_name ORDER BY position_in_unique_constraint ASC) AS referenced_columns, rc.update_rule AS on_update, @@ -677,6 +678,12 @@ public function getSql($includeSchema = true) $foreignKeyConstraintCreateList = array(); foreach ( $this->foreign_key_constraints as $name => $constraint ) { + // The table schema may have been set after the table was initially + // created. If the foreign key constraint doesn't explicitly define + // a schema, default to the table's schema. + if ( null === $constraint->schema ) { + $constraint->schema = $this->schema; + } $foreignKeyConstraintCreateList[$name] = $constraint->getSql($includeSchema); } @@ -1105,10 +1112,18 @@ public function __set($property, $value) // Clear the array no matter what, that way NULL is handled properly. if ( null !== $value ) { foreach ( $value as $item ) { - $constraint = ( is_object($item) && $item instanceof ForeignKeyConstraint - ? $item - : new ForeignKeyConstraint($item, $this->systemQuoteChar, $this->logger) ); - $this->properties[$property][$constraint->name] = $constraint; + if ( is_object($item) && $item instanceof ForeignKeyConstraint ) { + $this->properties[$property][$item->name] = $item; + } else { + if ( $item instanceof stdClass ) { + // Default to the schema of the parent table. + if ( ! isset($item->schema) ) { + $item->schema = $this->schema; + } + } + $constraint = new ForeignKeyConstraint($item, $this->systemQuoteChar, $this->logger); + $this->properties[$property][$constraint->name] = $constraint; + } } } break; diff --git a/tests/artifacts/xdmod/unit/etl/db-model/foreign-key-constraint/alter-table.json b/tests/artifacts/xdmod/unit/etl/db-model/foreign-key-constraint/alter-table.json new file mode 100644 index 0000000000..67df65421e --- /dev/null +++ b/tests/artifacts/xdmod/unit/etl/db-model/foreign-key-constraint/alter-table.json @@ -0,0 +1,114 @@ +{ + "Alter ON DELETE from NO ACTION to CASCADE": [ + { + "table_definition": { + "name": "test_db_model", + "engine": "InnoDB", + "charset": "latin1", + "collation": "latin1_swedish_ci", + "comment": "Events on an instance", + "columns": [ + { + "name": "event_id", + "type": "bigint(20) unsigned", + "nullable": false, + "extra": "auto_increment", + "comment": "Generated during intermediate ingest" + }, + { + "name": "col1", + "type": "varchar(64)", + "charset": "latin1", + "collation": "latin1_swedish_ci", + "nullable": false, + "default": "mydefault" + }, + { + "name": "col2", + "type": "varchar(64)", + "charset": "utf8", + "collation": "utf8_unicode_ci", + "nullable": false, + "default": "mydefault" + }, + { + "name": "event_time", + "type": "timestamp", + "nullable": false, + "default": "CURRENT_TIMESTAMP" + }, + { + "name": "instance_id", + "type": "int(11) unsigned", + "nullable": true, + "default": 10 + }, + { + "name": "inferred", + "type": "int(1) unsigned", + "nullable": true, + "default": 0, + "comment": "Not explicitly provided by source but inferred from other data" + } + ], + "indexes": [ + { + "name": "fk_col", + "columns": [ + "col1" + ], + "type": "BTREE", + "is_unique": false + }, + { + "name": "fk_instance", + "columns": [ + "instance_id", + "inferred" + ], + "type": "BTREE", + "is_unique": true + } + ], + "foreign_key_constraints": [ + { + "name": "con_col1", + "columns": [ + "col1" + ], + "referenced_table": "db_test_model2", + "referenced_columns": [ + "col3" + ], + "on_delete": "CASCADE", + "on_update": "NO ACTION" + } + ] + } + }, + { + "columns": [ + "instance_id" + ], + "referenced_table": "other_table", + "referenced_columns": [ + "other_column" + ], + "on_delete": "NO ACTION" + }, + { + "columns": [ + "instance_id" + ], + "referenced_table": "other_table", + "referenced_columns": [ + "other_column" + ], + "on_delete": "CASCADE" + }, + [ + "ALTER TABLE `test_db_model`\nDROP FOREIGN KEY `fk_instance_id`;", + "ALTER TABLE `test_db_model`\nADD CONSTRAINT `fk_instance_id` FOREIGN KEY (`instance_id`) REFERENCES `other_table` (`other_column`) ON DELETE CASCADE;" + ] + ] +} diff --git a/tests/artifacts/xdmod/unit/etl/db-model/foreign-key-constraint/create-table.json b/tests/artifacts/xdmod/unit/etl/db-model/foreign-key-constraint/create-table.json new file mode 100644 index 0000000000..6ff9a5222b --- /dev/null +++ b/tests/artifacts/xdmod/unit/etl/db-model/foreign-key-constraint/create-table.json @@ -0,0 +1,105 @@ +{ + "Table with simplest foreign key constraint": [ + { + "table_definition": { + "name": "test", + "engine": "InnoDB", + "columns": [ + { + "name": "id", + "type": "int(11)", + "nullable": false, + "extra": "auto_increment" + }, + { + "name": "other_id", + "type": "int(11)", + "nullable": false + } + ], + "indexes": [ + { + "name": "PRIMARY", + "columns": [ + "id" + ] + }, + { + "name": "idx_other_id", + "columns": [ + "other_id" + ] + } + ], + "foreign_key_constraints": [ + { + "name": "fk_other", + "columns": [ + "other_id" + ], + "referenced_table": "other", + "referenced_columns": [ + "id" + ] + } + ] + } + }, + [ + "CREATE TABLE IF NOT EXISTS `test` (\n `id` int(11) NOT NULL auto_increment,\n `other_id` int(11) NOT NULL,\n PRIMARY KEY (`id`),\n INDEX `idx_other_id` (`other_id`),\n CONSTRAINT `fk_other` FOREIGN KEY (`other_id`) REFERENCES `other` (`id`)\n) ENGINE = innodb;" + ] + ], + "Table with complex foreign key constraint": [ + { + "table_definition": { + "name": "test", + "engine": "InnoDB", + "columns": [ + { + "name": "id", + "type": "int(11)", + "nullable": false, + "extra": "auto_increment" + }, + { + "name": "other_id", + "type": "int(11)", + "nullable": false + } + ], + "indexes": [ + { + "name": "PRIMARY", + "columns": [ + "id" + ] + }, + { + "name": "idx_other_id", + "columns": [ + "other_id" + ] + } + ], + "foreign_key_constraints": [ + { + "name": "fk_other", + "columns": [ + "other_id" + ], + "referenced_schema": "mod_other", + "referenced_table": "other", + "referenced_columns": [ + "id" + ], + "on_delete": "SET NULL", + "on_update": "CASCADE" + } + ] + } + }, + [ + "CREATE TABLE IF NOT EXISTS `test` (\n `id` int(11) NOT NULL auto_increment,\n `other_id` int(11) NOT NULL,\n PRIMARY KEY (`id`),\n INDEX `idx_other_id` (`other_id`),\n CONSTRAINT `fk_other` FOREIGN KEY (`other_id`) REFERENCES `mod_other`.`other` (`id`) ON DELETE SET NULL ON UPDATE CASCADE\n) ENGINE = innodb;" + ] + ] +} diff --git a/tests/artifacts/xdmod/unit/etl/db-model/foreign-key-constraint/verification-failure.json b/tests/artifacts/xdmod/unit/etl/db-model/foreign-key-constraint/verification-failure.json new file mode 100644 index 0000000000..cddbe11515 --- /dev/null +++ b/tests/artifacts/xdmod/unit/etl/db-model/foreign-key-constraint/verification-failure.json @@ -0,0 +1,134 @@ +{ + "Empty columns": [ + { + "table_definition": { + "name": "test_table", + "engine": "InnoDB", + "columns": [ + { + "name": "id", + "type": "int(11)", + "nullable": false, + "extra": "auto_increment" + }, + { + "name": "fk_id", + "type": "int(11)", + "nullable": false + } + ], + "indexes": [ + { + "name": "PRIMARY", + "columns": [ + "id" + ] + }, + { + "name": "idx_fk_id", + "columns": [ + "fk_id" + ] + } + ], + "foreign_key_constraints": [ + { + "columns": [], + "referenced_table": "ref_table", + "referenced_columns": [ + "id" + ] + } + ] + } + } + ], + "Missing referenced table": [ + { + "table_definition": { + "name": "test_table", + "engine": "InnoDB", + "columns": [ + { + "name": "id", + "type": "int(11)", + "nullable": false, + "extra": "auto_increment" + }, + { + "name": "fk_id", + "type": "int(11)", + "nullable": false + } + ], + "indexes": [ + { + "name": "PRIMARY", + "columns": [ + "id" + ] + }, + { + "name": "idx_fk_id", + "columns": [ + "fk_id" + ] + } + ], + "foreign_key_constraints": [ + { + "columns": [ + "fk_id" + ], + "referenced_columns": [ + "id" + ] + } + ] + } + } + ], + "Missing referenced columns": [ + { + "table_definition": { + "name": "test_table", + "engine": "InnoDB", + "columns": [ + { + "name": "id", + "type": "int(11)", + "nullable": false, + "extra": "auto_increment" + }, + { + "name": "fk_id", + "type": "int(11)", + "nullable": false + } + ], + "indexes": [ + { + "name": "PRIMARY", + "columns": [ + "id" + ] + }, + { + "name": "idx_fk_id", + "columns": [ + "fk_id" + ] + } + ], + "foreign_key_constraints": [ + { + "columns": [ + "fk_id" + ], + "referenced_table": "ref_table" + } + ] + } + } + ] +} diff --git a/tests/artifacts/xdmod/unit/etl/db-model/foreign-key-constraint/verification.json b/tests/artifacts/xdmod/unit/etl/db-model/foreign-key-constraint/verification.json new file mode 100644 index 0000000000..508e3b072d --- /dev/null +++ b/tests/artifacts/xdmod/unit/etl/db-model/foreign-key-constraint/verification.json @@ -0,0 +1,350 @@ +{ + "Foreign key constraint without schema": [ + { + "table_definition": { + "name": "test_table", + "engine": "InnoDB", + "columns": [ + { + "name": "id", + "type": "int(11)", + "nullable": false, + "extra": "auto_increment" + }, + { + "name": "fk_id", + "type": "int(11)", + "nullable": false + } + ], + "indexes": [ + { + "name": "PRIMARY", + "columns": [ + "id" + ] + }, + { + "name": "idx_fk_id", + "columns": [ + "fk_id" + ] + } + ], + "foreign_key_constraints": [ + { + "columns": [ + "fk_id" + ], + "referenced_table": "ref_table", + "referenced_columns": [ + "id" + ] + } + ] + } + } + ], + "Foreign key constraint with schema": [ + { + "table_definition": { + "name": "test_table", + "engine": "InnoDB", + "columns": [ + { + "name": "id", + "type": "int(11)", + "nullable": false, + "extra": "auto_increment" + }, + { + "name": "fk_id", + "type": "int(11)", + "nullable": false + } + ], + "indexes": [ + { + "name": "PRIMARY", + "columns": [ + "id" + ] + }, + { + "name": "idx_fk_id", + "columns": [ + "fk_id" + ] + } + ], + "foreign_key_constraints": [ + { + "columns": [ + "fk_id" + ], + "referenced_schema": "ref_schema", + "referenced_table": "ref_table", + "referenced_columns": [ + "id" + ] + } + ] + } + } + ], + "Foreign key constraint with ON DELETE": [ + { + "table_definition": { + "name": "test_table", + "engine": "InnoDB", + "columns": [ + { + "name": "id", + "type": "int(11)", + "nullable": false, + "extra": "auto_increment" + }, + { + "name": "fk_id", + "type": "int(11)", + "nullable": false + } + ], + "indexes": [ + { + "name": "PRIMARY", + "columns": [ + "id" + ] + }, + { + "name": "idx_fk_id", + "columns": [ + "fk_id" + ] + } + ], + "foreign_key_constraints": [ + { + "columns": [ + "fk_id" + ], + "referenced_table": "ref_table", + "referenced_columns": [ + "id" + ], + "on_delete": "CASCADE" + } + ] + } + } + ], + "Foreign key constraint with ON UPDATE": [ + { + "table_definition": { + "name": "test_table", + "engine": "InnoDB", + "columns": [ + { + "name": "id", + "type": "int(11)", + "nullable": false, + "extra": "auto_increment" + }, + { + "name": "fk_id", + "type": "int(11)", + "nullable": false + } + ], + "indexes": [ + { + "name": "PRIMARY", + "columns": [ + "id" + ] + }, + { + "name": "idx_fk_id", + "columns": [ + "fk_id" + ] + } + ], + "foreign_key_constraints": [ + { + "columns": [ + "fk_id" + ], + "referenced_table": "ref_table", + "referenced_columns": [ + "id" + ], + "on_update": "CASCADE" + } + ] + } + } + ], + "Foreign key constraint with ON UPDATE and ON DELETE": [ + { + "table_definition": { + "name": "test_table", + "engine": "InnoDB", + "columns": [ + { + "name": "id", + "type": "int(11)", + "nullable": false, + "extra": "auto_increment" + }, + { + "name": "fk_id", + "type": "int(11)", + "nullable": false + } + ], + "indexes": [ + { + "name": "PRIMARY", + "columns": [ + "id" + ] + }, + { + "name": "idx_fk_id", + "columns": [ + "fk_id" + ] + } + ], + "foreign_key_constraints": [ + { + "columns": [ + "fk_id" + ], + "referenced_table": "ref_table", + "referenced_columns": [ + "id" + ], + "on_update": "CASCADE", + "on_delete": "CASCADE" + } + ] + } + } + ], + "Named Foreign key constraint": [ + { + "table_definition": { + "name": "test_table", + "engine": "InnoDB", + "columns": [ + { + "name": "id", + "type": "int(11)", + "nullable": false, + "extra": "auto_increment" + }, + { + "name": "fk_id", + "type": "int(11)", + "nullable": false + } + ], + "indexes": [ + { + "name": "PRIMARY", + "columns": [ + "id" + ] + }, + { + "name": "idx_fk_id", + "columns": [ + "fk_id" + ] + } + ], + "foreign_key_constraints": [ + { + "name": "fk_test", + "columns": [ + "fk_id" + ], + "referenced_table": "ref_table", + "referenced_columns": [ + "id" + ] + } + ] + } + } + ], + "Multiple foreign key constraints": [ + { + "table_definition": { + "name": "test_table", + "engine": "InnoDB", + "columns": [ + { + "name": "id", + "type": "int(11)", + "nullable": false, + "extra": "auto_increment" + }, + { + "name": "fk1_id", + "type": "int(11)", + "nullable": false + }, + { + "name": "fk2_id", + "type": "int(11)", + "nullable": false + } + ], + "indexes": [ + { + "name": "PRIMARY", + "columns": [ + "id" + ] + }, + { + "name": "idx_fk1_id", + "columns": [ + "fk1_id" + ] + }, + { + "name": "idx_fk2_id", + "columns": [ + "fk2_id" + ] + } + ], + "foreign_key_constraints": [ + { + "columns": [ + "fk1_id" + ], + "referenced_table": "ref_table1", + "referenced_columns": [ + "id" + ] + }, + { + "columns": [ + "fk2_id" + ], + "referenced_table": "ref_table2", + "referenced_columns": [ + "id" + ] + } + ] + } + } + ] +} diff --git a/tests/unit/lib/ETL/DbModel/DbModelTest.php b/tests/unit/lib/ETL/DbModel/DbModelTest.php index 042881a211..ddd85d1bc1 100644 --- a/tests/unit/lib/ETL/DbModel/DbModelTest.php +++ b/tests/unit/lib/ETL/DbModel/DbModelTest.php @@ -146,44 +146,6 @@ public function testIndexInitializationError() $table->verify(); } - /** - * Test foreign key constraint initialization error. - * - * @expectedException Exception - * @expectedExceptionMessage "columns" must be an array - */ - public function testForeignKeyConstraintInitializationError() - { - $config = (object) [ - 'name' => 'initialize_error', - 'columns' => [ - (object) [ - 'name' => 'column1', - 'type' => 'int(11)', - 'nullable' => false - ] - ], - 'indexes' => [ - (object) [ - 'columns' => [ - 'column1' - ] - ] - ], - 'foreign_key_constraints' => [ - (object) [ - 'referenced_table' => 'other_table', - 'referenced_columns' => [ - 'id' - ] - ] - ] - ]; - - $table = new Table($config); - $table->verify(); - } - /** * Test table verification error * @@ -210,37 +172,6 @@ public function testTableVerificationError() $table->verify(); } - /** - * Test foreign key constraint verification error - * - * @expectedException Exception - * @expectedExceptionMessage Columns in foreign key constraint 'invalid_fk' must be contained at the beginning of an index - */ - - public function testForeignKeyConstraintVerificationError() - { - // Foreign key constraint with no corresponding index. - $config = (object) array( - 'name' => "fk_verification_error", - 'columns' => array( (object) array( - 'name' => 'column1', - 'type' => 'int(11)', - 'nullable' => true, - )), - 'foreign_key_constraints' => array( (object) array( - 'name' => 'invalid_fk', - 'columns' => array('column1'), - 'referenced_table' => 'other_table', - 'referenced_columns' => array( - 'other_column', - ), - )), - ); - - $table = new Table($config); // No logger here - $table->verify(); - } - /** * Verify creating table elements manually. */ @@ -327,38 +258,6 @@ public function testCreateSql() } - /** - * Confirm foreign key contraint changes work. - */ - public function testAlterTableContraints() - { - $config = self::TEST_ARTIFACT_INPUT_PATH . '/table_def-charset.json'; - - $origTable = new Table($config, '`', self::$logger); - $fkconfig = (object) array( - 'columns' => array('new_column'), - 'referenced_table' => 'other_table', - 'referenced_columns' => array('other_column'), - 'on_delete' => 'NO ACTION' - ); - $origTable->addForeignKeyConstraint($fkconfig); - - $targetTable = new Table($config, '`', self::$logger); - $fkconfig1 = (object) array( - 'columns' => array('new_column'), - 'referenced_table' => 'other_table', - 'referenced_columns' => array('other_column'), - 'on_delete' => 'CASCADE' - ); - $targetTable->addForeignKeyConstraint($fkconfig1); - - $sql = $origTable->getAlterSql($targetTable); - - $this->assertCount(2, $sql); - $this->assertEquals("ALTER TABLE `test_db_model`\nDROP FOREIGN KEY `fk_new_column`;", trim($sql[0])); - $this->assertEquals("ALTER TABLE `test_db_model`\nADD CONSTRAINT `fk_new_column` FOREIGN KEY (`new_column`) REFERENCES `other_table` (`other_column`) ON DELETE CASCADE;", trim($sql[1])); - } - /** * Test comparing 2 tables and the ALTER TABLE statement needed to go from one to the other. * Also manually add elements and verify the ALTER TABLE statement generated. diff --git a/tests/unit/lib/ETL/DbModel/ForeignKeyConstraintTest.php b/tests/unit/lib/ETL/DbModel/ForeignKeyConstraintTest.php new file mode 100644 index 0000000000..6cd1f147f8 --- /dev/null +++ b/tests/unit/lib/ETL/DbModel/ForeignKeyConstraintTest.php @@ -0,0 +1,277 @@ +testFiles)) { + $this->testFiles = new TestFiles(__DIR__ . '/../../../..'); + } + return $this->testFiles; + } + + /** + * Test foreign key constraint initialization error. + * + * @expectedException Exception + * @expectedExceptionMessage "columns" must be an array + */ + public function testForeignKeyConstraintInitializationError() + { + $config = (object) [ + 'name' => 'initialize_error', + 'columns' => [ + (object) [ + 'name' => 'column1', + 'type' => 'int(11)', + 'nullable' => false + ] + ], + 'indexes' => [ + (object) [ + 'columns' => [ + 'column1' + ] + ] + ], + 'foreign_key_constraints' => [ + (object) [ + 'referenced_table' => 'other_table', + 'referenced_columns' => [ + 'id' + ] + ] + ] + ]; + + $table = new Table($config); + $table->verify(); + } + + /** + * Test that the given configuration results in a valid table. + * + * @dataProvider verificationProvider + */ + public function testVerification(stdClass $config) + { + $table = new Table($config, '`', self::$logger); + $this->assertTrue($table->verify()); + } + + /** + * Test that the given configuration does not result in a valid table. + * + * @dataProvider verificationFailureProvider + * @expectedException Exception + */ + public function testVerificationFailure(stdClass $config) + { + $table = new Table($config, '`', self::$logger); + $table->verify(); + } + + /** + * Test generating create table SQL. + * + * @dataProvider createTableProvider + */ + public function testCreateTable( + stdClass $tableConfig, + array $expectedSql + ) { + $table = new Table($tableConfig, '`', self::$logger); + $sql = $table->getSql(); + + $this->assertCount( + count($expectedSql), + $sql, + 'Expected SQL statement count' + ); + + foreach ($expectedSql as $i => $expected) { + $this->assertEquals( + $expected, + trim($sql[$i]), + sprintf('SQL statement %d', $i + 1) + ); + } + } + + /** + * Test generating alter table SQL. + * + * @dataProvider alterTableProvider + */ + public function testAlterTable( + stdClass $tableConfig, + stdClass $fk1Config, + stdClass $fk2Config, + array $expectedSql + ) { + $origTable = new Table($tableConfig, '`', self::$logger); + $origTable->addForeignKeyConstraint($fk1Config); + + $targetTable = new Table($tableConfig, '`', self::$logger); + $targetTable->addForeignKeyConstraint($fk2Config); + + $sql = $origTable->getAlterSql($targetTable); + + $this->assertCount( + count($expectedSql), + $sql, + 'Expected SQL statement count' + ); + + foreach ($expectedSql as $i => $expected) { + $this->assertEquals( + $expected, + trim($sql[$i]), + sprintf('SQL statement %d', $i + 1) + ); + } + } + + /** + * Test comparison of foreign key constraints. + */ + public function testCompare() + { + $fk1 = new ForeignKeyConstraint((object) [ + 'schema' => 'my_schema', + 'columns' => ['other_id'], + 'referenced_schema' => 'my_schema', + 'referenced_table' => 'other_table', + 'referenced_columns' => ['id'] + ]); + + $fk2 = new ForeignKeyConstraint((object) [ + 'schema' => 'my_schema', + 'columns' => ['other_id'], + 'referenced_table' => 'other_table', + 'referenced_columns' => ['id'] + ]); + + $this->assertEquals( + 0, + $fk1->compare($fk2), + 'fk1 == fk2 (referenced schema defaults to fk schema)' + ); + $this->assertEquals( + 0, + $fk2->compare($fk1), + 'fk2 == fk1 (referenced schema defaults to fk schema)' + ); + + $fk3 = new ForeignKeyConstraint((object) [ + 'schema' => 'other_schema', + 'columns' => ['other_id'], + 'referenced_table' => 'other_table', + 'referenced_columns' => ['id'] + ]); + + $this->assertNotEquals( + 0, + $fk1->compare($fk3), + 'fk1 != fk3 (fk in different schema is different)' + ); + $this->assertNotEquals( + 0, + $fk3->compare($fk1), + 'fk3 != fk1 (fk in different schema is different)' + ); + } + + /** + * Convert associative arrays to stdClass recursively. + * + * @param array $obj + * @param stdClass + */ + private function arrayToStdClass(array $obj) + { + return json_decode(json_encode($obj)); + } + + /** + * Load test data from file and convert to appropriate format. + * + * PHPUnit expects an associative array for named tests, but the ETL + * classes expect stdClass input. + * + * Data is structured as an associative array with elements that are + * numeric arrays. Each element in the numeric array is converted to a + * stdClass object. + * + * e.g. + * + * { + * "test 1": [ + * { + * "a": "b" + * }, + * { + * "c": "d" + * } + * ], + * "test 2" [ + * + * ] + * } + * + * @param string $name The name of the JSON file (without ".json"). + * @return array + */ + private function loadTestData($name) + { + return array_map( + function ($inputData) { + return array_map(array($this, 'arrayToStdClass'), $inputData); + }, + $this->getTestFiles()->loadJsonFile(self::TEST_GROUP, $name) + ); + } + + public function verificationProvider() + { + return $this->loadTestData('verification'); + } + + public function verificationFailureProvider() + { + return $this->loadTestData('verification-failure'); + } + + public function createTableProvider() + { + return $this->loadTestData('create-table'); + } + + public function alterTableProvider() + { + return $this->loadTestData('alter-table'); + } +}