Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Add insert ignore #336

Merged
merged 4 commits into from
Dec 31, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
21 changes: 21 additions & 0 deletions src/Sql/InsertIgnore.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/

namespace Zend\Db\Sql;

class InsertIgnore extends Insert
{
/**
* @var array Specification array
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please specify the type of the array

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is copied originally from Insert class, do you have sample of "type of the array"?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string[] in this case, or even array<string, string> (unsure if the tooling craps out with that, though)

*/
protected $specifications = [
self::SPECIFICATION_INSERT => 'INSERT IGNORE INTO %1$s (%2$s) VALUES (%3$s)',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are all supported drivers capable of evaluating INSERT IGNORE?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably MySQL only, that's why I didn't add insertIgnore() function to Sql and AbstractTableGateway

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, can we please survey that before introducing it then?

self::SPECIFICATION_SELECT => 'INSERT IGNORE INTO %1$s %2$s %3$s',
];
}
366 changes: 366 additions & 0 deletions test/unit/Sql/InsertIgnoreTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,366 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/

namespace ZendTest\Db\Sql;

use PHPUnit\Framework\TestCase;
use Zend\Db\Sql\Expression;
use Zend\Db\Sql\InsertIgnore;
use Zend\Db\Sql\Select;
use Zend\Db\Sql\TableIdentifier;
use ZendTest\Db\TestAsset\Replace;
use ZendTest\Db\TestAsset\TrustingSql92Platform;

class InsertIgnoreTest extends TestCase
{
/**
* @var InsertIgnore
*/
protected $insert;

/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*/
protected function setUp()
{
$this->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())
);
}
}