diff --git a/CHANGELOG.md b/CHANGELOG.md index a9cfa85cd9..4536073301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ All notable changes to this project will be documented in this file, in reverse - [#157](https://github.com/zendframework/zend-db/pull/157) added support of `Zend\Db\Sql\TableIdentifier` in DDL +- [#336](https://github.com/zendframework/zend-db/pull/336) added `InsertIgnore` class for "INSERT IGNORE" usage (usable in `MySQL` plaftorm) + ### Changed - Nothing. diff --git a/src/Sql/InsertIgnore.php b/src/Sql/InsertIgnore.php new file mode 100644 index 0000000000..cfe20299b2 --- /dev/null +++ b/src/Sql/InsertIgnore.php @@ -0,0 +1,21 @@ + 'INSERT IGNORE INTO %1$s (%2$s) VALUES (%3$s)', + self::SPECIFICATION_SELECT => 'INSERT IGNORE INTO %1$s %2$s %3$s', + ]; +} diff --git a/test/unit/Sql/InsertIgnoreTest.php b/test/unit/Sql/InsertIgnoreTest.php new file mode 100644 index 0000000000..78d7db08a2 --- /dev/null +++ b/test/unit/Sql/InsertIgnoreTest.php @@ -0,0 +1,366 @@ +insert = new InsertIgnore; + } + + public function testInto() + { + $this->insert->into('table', 'schema'); + self::assertEquals('table', $this->insert->getRawState('table')); + + $tableIdentifier = new TableIdentifier('table', 'schema'); + $this->insert->into($tableIdentifier); + self::assertEquals($tableIdentifier, $this->insert->getRawState('table')); + } + + public function testColumns() + { + $columns = ['foo', 'bar']; + $this->insert->columns($columns); + self::assertEquals($columns, $this->insert->getRawState('columns')); + } + + public function testValues() + { + $this->insert->values(['foo' => 'bar']); + self::assertEquals(['foo'], $this->insert->getRawState('columns')); + self::assertEquals(['bar'], $this->insert->getRawState('values')); + + // test will merge cols and values of previously set stuff + $this->insert->values(['foo' => 'bax'], InsertIgnore::VALUES_MERGE); + $this->insert->values(['boom' => 'bam'], InsertIgnore::VALUES_MERGE); + self::assertEquals(['foo', 'boom'], $this->insert->getRawState('columns')); + self::assertEquals(['bax', 'bam'], $this->insert->getRawState('values')); + + $this->insert->values(['foo' => 'bax']); + self::assertEquals(['foo'], $this->insert->getRawState('columns')); + self::assertEquals(['bax'], $this->insert->getRawState('values')); + } + + public function testValuesThrowsExceptionWhenNotArrayOrSelect() + { + $this->expectException('Zend\Db\Sql\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('values() expects an array of values or Zend\Db\Sql\Select instance'); + $this->insert->values(5); + } + + public function testValuesThrowsExceptionWhenSelectMergeOverArray() + { + $this->insert->values(['foo' => 'bar']); + + $this->expectException('Zend\Db\Sql\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('A Zend\Db\Sql\Select instance cannot be provided with the merge flag'); + $this->insert->values(new Select, InsertIgnore::VALUES_MERGE); + } + + public function testValuesThrowsExceptionWhenArrayMergeOverSelect() + { + $this->insert->values(new Select); + + $this->expectException('Zend\Db\Sql\Exception\InvalidArgumentException'); + $this->expectExceptionMessage( + 'An array of values cannot be provided with the merge flag when a Zend\Db\Sql\Select instance already ' + . 'exists as the value source' + ); + $this->insert->values(['foo' => 'bar'], InsertIgnore::VALUES_MERGE); + } + + /** + * @group ZF2-4926 + */ + public function testEmptyArrayValues() + { + $this->insert->values([]); + self::assertEquals([], $this->readAttribute($this->insert, 'columns')); + } + + public function testPrepareStatement() + { + $mockDriver = $this->getMockBuilder('Zend\Db\Adapter\Driver\DriverInterface')->getMock(); + $mockDriver->expects($this->any())->method('getPrepareType')->will($this->returnValue('positional')); + $mockDriver->expects($this->any())->method('formatParameterName')->will($this->returnValue('?')); + $mockAdapter = $this->getMockBuilder('Zend\Db\Adapter\Adapter') + ->setMethods() + ->setConstructorArgs([$mockDriver]) + ->getMock(); + + $mockStatement = $this->getMockBuilder('Zend\Db\Adapter\Driver\StatementInterface')->getMock(); + $pContainer = new \Zend\Db\Adapter\ParameterContainer([]); + $mockStatement->expects($this->any())->method('getParameterContainer')->will($this->returnValue($pContainer)); + $mockStatement->expects($this->at(1)) + ->method('setSql') + ->with($this->equalTo('INSERT IGNORE INTO "foo" ("bar", "boo") VALUES (?, NOW())')); + + $this->insert->into('foo') + ->values(['bar' => 'baz', 'boo' => new Expression('NOW()')]); + + $this->insert->prepareStatement($mockAdapter, $mockStatement); + + // with TableIdentifier + $this->insert = new InsertIgnore; + $mockDriver = $this->getMockBuilder('Zend\Db\Adapter\Driver\DriverInterface')->getMock(); + $mockDriver->expects($this->any())->method('getPrepareType')->will($this->returnValue('positional')); + $mockDriver->expects($this->any())->method('formatParameterName')->will($this->returnValue('?')); + $mockAdapter = $this->getMockBuilder('Zend\Db\Adapter\Adapter') + ->setMethods() + ->setConstructorArgs([$mockDriver]) + ->getMock(); + + $mockStatement = $this->getMockBuilder('Zend\Db\Adapter\Driver\StatementInterface')->getMock(); + $pContainer = new \Zend\Db\Adapter\ParameterContainer([]); + $mockStatement->expects($this->any())->method('getParameterContainer')->will($this->returnValue($pContainer)); + $mockStatement->expects($this->at(1)) + ->method('setSql') + ->with($this->equalTo('INSERT IGNORE INTO "sch"."foo" ("bar", "boo") VALUES (?, NOW())')); + + $this->insert->into(new TableIdentifier('foo', 'sch')) + ->values(['bar' => 'baz', 'boo' => new Expression('NOW()')]); + + $this->insert->prepareStatement($mockAdapter, $mockStatement); + } + + public function testPrepareStatementWithSelect() + { + $mockDriver = $this->getMockBuilder('Zend\Db\Adapter\Driver\DriverInterface')->getMock(); + $mockDriver->expects($this->any())->method('getPrepareType')->will($this->returnValue('positional')); + $mockDriver->expects($this->any())->method('formatParameterName')->will($this->returnValue('?')); + $mockAdapter = $this->getMockBuilder('Zend\Db\Adapter\Adapter') + ->setMethods() + ->setConstructorArgs([$mockDriver]) + ->getMock(); + + $mockStatement = new \Zend\Db\Adapter\StatementContainer(); + + $select = new Select('bar'); + $this->insert + ->into('foo') + ->columns(['col1']) + ->select($select->where(['x' => 5])) + ->prepareStatement($mockAdapter, $mockStatement); + + self::assertEquals( + 'INSERT IGNORE INTO "foo" ("col1") SELECT "bar".* FROM "bar" WHERE "x" = ?', + $mockStatement->getSql() + ); + $parameters = $mockStatement->getParameterContainer()->getNamedArray(); + self::assertSame(['subselect1where1' => 5], $parameters); + } + + public function testGetSqlString() + { + $this->insert->into('foo') + ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); + + self::assertEquals( + 'INSERT IGNORE INTO "foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', + $this->insert->getSqlString(new TrustingSql92Platform()) + ); + + // with TableIdentifier + $this->insert = new InsertIgnore; + $this->insert->into(new TableIdentifier('foo', 'sch')) + ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); + + self::assertEquals( + 'INSERT IGNORE INTO "sch"."foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', + $this->insert->getSqlString(new TrustingSql92Platform()) + ); + + // with Select + $this->insert = new InsertIgnore; + $select = new Select(); + $this->insert->into('foo')->select($select->from('bar')); + + self::assertEquals( + 'INSERT IGNORE INTO "foo" SELECT "bar".* FROM "bar"', + $this->insert->getSqlString(new TrustingSql92Platform()) + ); + + // with Select and columns + $this->insert->columns(['col1', 'col2']); + self::assertEquals( + 'INSERT IGNORE INTO "foo" ("col1", "col2") SELECT "bar".* FROM "bar"', + $this->insert->getSqlString(new TrustingSql92Platform()) + ); + } + + public function testGetSqlStringUsingColumnsAndValuesMethods() + { + // With columns() and values() + $this->insert + ->into('foo') + ->columns(['col1', 'col2', 'col3']) + ->values(['val1', 'val2', 'val3']); + self::assertEquals( + 'INSERT IGNORE INTO "foo" ("col1", "col2", "col3") VALUES (\'val1\', \'val2\', \'val3\')', + $this->insert->getSqlString(new TrustingSql92Platform()) + ); + } + + // @codingStandardsIgnoreStart + public function test__set() + { + // @codingStandardsIgnoreEnd + $this->insert->foo = 'bar'; + self::assertEquals(['foo'], $this->insert->getRawState('columns')); + self::assertEquals(['bar'], $this->insert->getRawState('values')); + } + + // @codingStandardsIgnoreStart + public function test__unset() + { + // @codingStandardsIgnoreEnd + $this->insert->foo = 'bar'; + self::assertEquals(['foo'], $this->insert->getRawState('columns')); + self::assertEquals(['bar'], $this->insert->getRawState('values')); + unset($this->insert->foo); + self::assertEquals([], $this->insert->getRawState('columns')); + self::assertEquals([], $this->insert->getRawState('values')); + + $this->insert->foo = null; + self::assertEquals(['foo'], $this->insert->getRawState('columns')); + self::assertEquals([null], $this->insert->getRawState('values')); + + unset($this->insert->foo); + self::assertEquals([], $this->insert->getRawState('columns')); + self::assertEquals([], $this->insert->getRawState('values')); + } + + // @codingStandardsIgnoreStart + public function test__isset() + { + // @codingStandardsIgnoreEnd + $this->insert->foo = 'bar'; + self::assertTrue(isset($this->insert->foo)); + + $this->insert->foo = null; + self::assertTrue(isset($this->insert->foo)); + } + + // @codingStandardsIgnoreStart + public function test__get() + { + // @codingStandardsIgnoreEnd + $this->insert->foo = 'bar'; + self::assertEquals('bar', $this->insert->foo); + + $this->insert->foo = null; + self::assertNull($this->insert->foo); + } + + /** + * @group ZF2-536 + */ + public function testValuesMerge() + { + $this->insert->into('foo') + ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); + $this->insert->into('foo') + ->values(['qux' => 100], InsertIgnore::VALUES_MERGE); + + self::assertEquals( + 'INSERT IGNORE INTO "foo" ("bar", "boo", "bam", "qux") VALUES (\'baz\', NOW(), NULL, \'100\')', + $this->insert->getSqlString(new TrustingSql92Platform()) + ); + } + + public function testSpecificationconstantsCouldBeOverridedByExtensionInPrepareStatement() + { + $replace = new Replace(); + + $mockDriver = $this->getMockBuilder('Zend\Db\Adapter\Driver\DriverInterface')->getMock(); + $mockDriver->expects($this->any())->method('getPrepareType')->will($this->returnValue('positional')); + $mockDriver->expects($this->any())->method('formatParameterName')->will($this->returnValue('?')); + $mockAdapter = $this->getMockBuilder('Zend\Db\Adapter\Adapter') + ->setMethods() + ->setConstructorArgs([$mockDriver]) + ->getMock(); + + $mockStatement = $this->getMockBuilder('Zend\Db\Adapter\Driver\StatementInterface')->getMock(); + $pContainer = new \Zend\Db\Adapter\ParameterContainer([]); + $mockStatement->expects($this->any())->method('getParameterContainer')->will($this->returnValue($pContainer)); + $mockStatement->expects($this->at(1)) + ->method('setSql') + ->with($this->equalTo('REPLACE INTO "foo" ("bar", "boo") VALUES (?, NOW())')); + + $replace->into('foo') + ->values(['bar' => 'baz', 'boo' => new Expression('NOW()')]); + + $replace->prepareStatement($mockAdapter, $mockStatement); + + // with TableIdentifier + $replace = new Replace(); + + $mockDriver = $this->getMockBuilder('Zend\Db\Adapter\Driver\DriverInterface')->getMock(); + $mockDriver->expects($this->any())->method('getPrepareType')->will($this->returnValue('positional')); + $mockDriver->expects($this->any())->method('formatParameterName')->will($this->returnValue('?')); + $mockAdapter = $this->getMockBuilder('Zend\Db\Adapter\Adapter') + ->setMethods() + ->setConstructorArgs([$mockDriver]) + ->getMock(); + + $mockStatement = $this->getMockBuilder('Zend\Db\Adapter\Driver\StatementInterface')->getMock(); + $pContainer = new \Zend\Db\Adapter\ParameterContainer([]); + $mockStatement->expects($this->any())->method('getParameterContainer')->will($this->returnValue($pContainer)); + $mockStatement->expects($this->at(1)) + ->method('setSql') + ->with($this->equalTo('REPLACE INTO "sch"."foo" ("bar", "boo") VALUES (?, NOW())')); + + $replace->into(new TableIdentifier('foo', 'sch')) + ->values(['bar' => 'baz', 'boo' => new Expression('NOW()')]); + + $replace->prepareStatement($mockAdapter, $mockStatement); + } + + public function testSpecificationconstantsCouldBeOverridedByExtensionInGetSqlString() + { + $replace = new Replace(); + $replace->into('foo') + ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); + + self::assertEquals( + 'REPLACE INTO "foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', + $replace->getSqlString(new TrustingSql92Platform()) + ); + + // with TableIdentifier + $replace = new Replace(); + $replace->into(new TableIdentifier('foo', 'sch')) + ->values(['bar' => 'baz', 'boo' => new Expression('NOW()'), 'bam' => null]); + + self::assertEquals( + 'REPLACE INTO "sch"."foo" ("bar", "boo", "bam") VALUES (\'baz\', NOW(), NULL)', + $replace->getSqlString(new TrustingSql92Platform()) + ); + } +}